From c746cbc686c46904a5d381725079a69e38b201cd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:06:25 +0100 Subject: [PATCH 001/294] codegen: move gen logic for typed values, consts and decl ref to common codegen --- src/arch/aarch64/CodeGen.zig | 6 +- src/arch/arm/CodeGen.zig | 6 +- src/arch/riscv64/CodeGen.zig | 14 +- src/arch/sparc64/CodeGen.zig | 6 +- src/arch/wasm/CodeGen.zig | 2 - src/arch/x86_64/CodeGen.zig | 216 +++--------------------- src/codegen.zig | 307 ++++++++++++++++++++++++++++++++--- src/link/Coff.zig | 2 +- src/link/Elf.zig | 2 +- src/link/MachO.zig | 2 +- src/link/Plan9.zig | 2 +- src/link/Wasm.zig | 2 +- src/register_manager.zig | 3 + 13 files changed, 323 insertions(+), 247 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 818b04f890..23f458f910 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -41,11 +41,7 @@ const c_abi_int_param_regs = abi.c_abi_int_param_regs; const c_abi_int_return_regs = abi.c_abi_int_return_regs; const gp = abi.RegisterClass.gp; -const InnerError = error{ - OutOfMemory, - CodegenFail, - OutOfRegisters, -}; +const InnerError = codegen.CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index ceabe70438..87806223e3 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -42,11 +42,7 @@ const c_abi_int_param_regs = abi.c_abi_int_param_regs; const c_abi_int_return_regs = abi.c_abi_int_return_regs; const gp = abi.RegisterClass.gp; -const InnerError = error{ - OutOfMemory, - CodegenFail, - OutOfRegisters, -}; +const InnerError = codegen.CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index afcf4b0bb7..fad5482cbc 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -21,10 +21,10 @@ const DW = std.dwarf; const leb128 = std.leb; const log = std.log.scoped(.codegen); const build_options = @import("build_options"); +const codegen = @import("../../codegen.zig"); -const Result = @import("../../codegen.zig").Result; -const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError; -const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; +const Result = codegen.Result; +const DebugInfoOutput = codegen.DebugInfoOutput; const bits = @import("bits.zig"); const abi = @import("abi.zig"); @@ -35,11 +35,7 @@ const Instruction = abi.Instruction; const callee_preserved_regs = abi.callee_preserved_regs; const gp = abi.RegisterClass.gp; -const InnerError = error{ - OutOfMemory, - CodegenFail, - OutOfRegisters, -}; +const InnerError = codegen.CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, @@ -225,7 +221,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) GenerateSymbolError!Result { +) codegen.CodeGenError!Result { if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index c8f77fe702..5a108eca85 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -38,11 +38,7 @@ const gp = abi.RegisterClass.gp; const Self = @This(); -const InnerError = error{ - OutOfMemory, - CodegenFail, - OutOfRegisters, -}; +const InnerError = codegen.CodeGenError || error{OutOfRegisters}; const RegisterView = enum(u1) { caller, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 2f191fd834..511a10769e 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -733,8 +733,6 @@ const InnerError = error{ OutOfMemory, /// An error occurred when trying to lower AIR to MIR. CodegenFail, - /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed - AnalysisFail, /// Compiler implementation could not handle a large integer. Overflow, }; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 53d38f520a..2ec1a33619 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -40,11 +40,7 @@ const Register = bits.Register; const gp = abi.RegisterClass.gp; const sse = abi.RegisterClass.sse; -const InnerError = error{ - OutOfMemory, - CodegenFail, - OutOfRegisters, -}; +const InnerError = codegen.CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, @@ -6683,7 +6679,7 @@ fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand }); } -pub fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { +fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); if (ref_int < Air.Inst.Ref.typed_value_map.len) { @@ -6752,200 +6748,26 @@ fn limitImmediateType(self: *Self, operand: Air.Inst.Ref, comptime T: type) !MCV return mcv; } -fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { - log.debug("lowerDeclRef: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() }); - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - - // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? - if (tv.ty.zigTypeTag() == .Pointer) blk: { - if (tv.ty.castPtrToFn()) |_| break :blk; - if (!tv.ty.elemType2().hasRuntimeBits()) { - return MCValue.none; - } - } - - const module = self.bin_file.options.module.?; - const decl = module.declPtr(decl_index); - module.markDeclAlive(decl); - - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index); - const atom = elf_file.getAtom(atom_index); - return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) }; - } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index); - const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; - return MCValue{ .linker_load = .{ - .type = .got, - .sym_index = sym_index, - } }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const atom_index = try coff_file.getOrCreateAtomForDecl(decl_index); - const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; - return MCValue{ .linker_load = .{ - .type = .got, - .sym_index = sym_index, - } }; - } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl_block_index = try p9.seeDecl(decl_index); - const decl_block = p9.getDeclBlock(decl_block_index); - const got_addr = p9.bases.data + decl_block.got_index.? * ptr_bytes; - return MCValue{ .memory = got_addr }; - } else { - return self.fail("TODO codegen non-ELF const Decl pointer", .{}); - } -} - -fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue { - log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() }); - const local_sym_index = self.bin_file.lowerUnnamedConst(tv, self.mod_fn.owner_decl) catch |err| { - return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)}); - }; - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - return MCValue{ .memory = elf_file.getSymbol(local_sym_index).st_value }; - } else if (self.bin_file.cast(link.File.MachO)) |_| { - return MCValue{ .linker_load = .{ - .type = .direct, - .sym_index = local_sym_index, - } }; - } else if (self.bin_file.cast(link.File.Coff)) |_| { - return MCValue{ .linker_load = .{ - .type = .direct, - .sym_index = local_sym_index, - } }; - } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - const got_index = local_sym_index; // the plan9 backend returns the got_index - const got_addr = p9.bases.data + got_index * ptr_bytes; - return MCValue{ .memory = got_addr }; - } else { - return self.fail("TODO lower unnamed const", .{}); - } -} - fn genTypedValue(self: *Self, arg_tv: TypedValue) InnerError!MCValue { - var typed_value = arg_tv; - if (typed_value.val.castTag(.runtime_value)) |rt| { - typed_value.val = rt.data; - } - log.debug("genTypedValue: ty = {}, val = {}", .{ typed_value.ty.fmtDebug(), typed_value.val.fmtDebug() }); - if (typed_value.val.isUndef()) - return MCValue{ .undef = {} }; - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - - if (typed_value.val.castTag(.decl_ref)) |payload| { - return self.lowerDeclRef(typed_value, payload.data); - } - if (typed_value.val.castTag(.decl_ref_mut)) |payload| { - return self.lowerDeclRef(typed_value, payload.data.decl_index); - } - - const target = self.target.*; - - switch (typed_value.ty.zigTypeTag()) { - .Void => return MCValue{ .none = {} }, - .Pointer => switch (typed_value.ty.ptrSize()) { - .Slice => {}, - else => { - switch (typed_value.val.tag()) { - .int_u64 => { - return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) }; - }, - else => {}, - } - }, + const mcv: MCValue = switch (try codegen.genTypedValue( + self.bin_file, + self.src_loc, + arg_tv, + self.mod_fn.owner_decl, + )) { + .mcv => |mcv| switch (mcv) { + .none => .none, + .undef => .undef, + .linker_load => |ll| .{ .linker_load = ll }, + .immediate => |imm| .{ .immediate = imm }, + .memory => |addr| .{ .memory = addr }, }, - .Int => { - const info = typed_value.ty.intInfo(self.target.*); - if (info.bits <= ptr_bits and info.signedness == .signed) { - return MCValue{ .immediate = @bitCast(u64, typed_value.val.toSignedInt(target)) }; - } - if (!(info.bits > ptr_bits or info.signedness == .signed)) { - return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) }; - } + .fail => |msg| { + self.err_msg = msg; + return error.CodegenFail; }, - .Bool => { - return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; - }, - .Optional => { - if (typed_value.ty.isPtrLikeOptional()) { - if (typed_value.val.isNull()) - return MCValue{ .immediate = 0 }; - - var buf: Type.Payload.ElemType = undefined; - return self.genTypedValue(.{ - .ty = typed_value.ty.optionalChild(&buf), - .val = typed_value.val, - }); - } else if (typed_value.ty.abiSize(self.target.*) == 1) { - return MCValue{ .immediate = @boolToInt(!typed_value.val.isNull()) }; - } - }, - .Enum => { - if (typed_value.val.castTag(.enum_field_index)) |field_index| { - switch (typed_value.ty.tag()) { - .enum_simple => { - return MCValue{ .immediate = field_index.data }; - }, - .enum_full, .enum_nonexhaustive => { - const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - const tag_val = enum_full.values.keys()[field_index.data]; - return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); - } else { - return MCValue{ .immediate = field_index.data }; - } - }, - else => unreachable, - } - } else { - var int_tag_buffer: Type.Payload.Bits = undefined; - const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); - return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val }); - } - }, - .ErrorSet => { - switch (typed_value.val.tag()) { - .@"error" => { - const err_name = typed_value.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - else => { - // In this case we are rendering an error union which has a 0 bits payload. - return MCValue{ .immediate = 0 }; - }, - } - }, - .ErrorUnion => { - const error_type = typed_value.ty.errorUnionSet(); - const payload_type = typed_value.ty.errorUnionPayload(); - const is_pl = typed_value.val.errorUnionIsPayload(); - - if (!payload_type.hasRuntimeBitsIgnoreComptime()) { - // We use the error type directly as the type. - const err_val = if (!is_pl) typed_value.val else Value.initTag(.zero); - return self.genTypedValue(.{ .ty = error_type, .val = err_val }); - } - }, - - .ComptimeInt => unreachable, - .ComptimeFloat => unreachable, - .Type => unreachable, - .EnumLiteral => unreachable, - .NoReturn => unreachable, - .Undefined => unreachable, - .Null => unreachable, - .Opaque => unreachable, - - else => {}, - } - - return self.lowerUnnamedConst(typed_value); + }; + return mcv; } const CallMCValues = struct { diff --git a/src/codegen.zig b/src/codegen.zig index df7ceff1f0..245745d6f6 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -29,13 +29,14 @@ pub const Result = union(enum) { fail: *ErrorMsg, }; -pub const GenerateSymbolError = error{ +pub const CodeGenError = error{ OutOfMemory, Overflow, - /// A Decl that this symbol depends on had a semantic analysis failure. - AnalysisFail, + CodegenFail, }; +pub const GenerateSymbolError = CodeGenError; + pub const DebugInfoOutput = union(enum) { dwarf: *link.File.Dwarf.DeclState, /// the plan9 debuginfo output is a bytecode with 4 opcodes @@ -63,19 +64,6 @@ pub const DebugInfoOutput = union(enum) { none, }; -/// Helper struct to denote that the value is in memory but requires a linker relocation fixup: -/// * got - the value is referenced indirectly via GOT entry index (the linker emits a got-type reloc) -/// * direct - the value is referenced directly via symbol index index (the linker emits a displacement reloc) -/// * import - the value is referenced indirectly via import entry index (the linker emits an import-type reloc) -pub const LinkerLoad = struct { - type: enum { - got, - direct, - import, - }, - sym_index: u32, -}; - pub fn generateFunction( bin_file: *link.File, src_loc: Module.SrcLoc, @@ -84,7 +72,7 @@ pub fn generateFunction( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) GenerateSymbolError!Result { +) CodeGenError!Result { switch (bin_file.options.target.cpu.arch) { .arm, .armeb, @@ -120,7 +108,7 @@ pub fn generateSymbol( code: *std.ArrayList(u8), debug_output: DebugInfoOutput, reloc_info: RelocInfo, -) GenerateSymbolError!Result { +) CodeGenError!Result { const tracy = trace(@src()); defer tracy.end(); @@ -823,7 +811,7 @@ fn lowerDeclRef( code: *std.ArrayList(u8), debug_output: DebugInfoOutput, reloc_info: RelocInfo, -) GenerateSymbolError!Result { +) CodeGenError!Result { const target = bin_file.options.target; const module = bin_file.options.module.?; if (typed_value.ty.isSlice()) { @@ -880,6 +868,287 @@ fn lowerDeclRef( return Result.ok; } +/// Helper struct to denote that the value is in memory but requires a linker relocation fixup: +/// * got - the value is referenced indirectly via GOT entry index (the linker emits a got-type reloc) +/// * direct - the value is referenced directly via symbol index index (the linker emits a displacement reloc) +/// * import - the value is referenced indirectly via import entry index (the linker emits an import-type reloc) +pub const LinkerLoad = struct { + type: enum { + got, + direct, + import, + }, + sym_index: u32, +}; + +pub const GenResult = union(enum) { + mcv: MCValue, + fail: *ErrorMsg, + + const MCValue = union(enum) { + none, + undef, + /// The bit-width of the immediate may be smaller than `u64`. For example, on 32-bit targets + /// such as ARM, the immediate will never exceed 32-bits. + immediate: u64, + linker_load: LinkerLoad, + /// Direct by-address reference to memory location. + memory: u64, + }; + + fn mcv(val: MCValue) GenResult { + return .{ .mcv = val }; + } + + fn fail( + gpa: Allocator, + src_loc: Module.SrcLoc, + comptime format: []const u8, + args: anytype, + ) Allocator.Error!GenResult { + const msg = try ErrorMsg.create(gpa, src_loc, format, args); + return .{ .fail = msg }; + } +}; + +fn genDeclRef( + bin_file: *link.File, + src_loc: Module.SrcLoc, + tv: TypedValue, + decl_index: Module.Decl.Index, +) CodeGenError!GenResult { + log.debug("genDeclRef: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() }); + + const target = bin_file.options.target; + const ptr_bits = target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + + const module = bin_file.options.module.?; + const decl = module.declPtr(decl_index); + + if (decl.ty.zigTypeTag() != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime()) { + const imm: u64 = switch (ptr_bytes) { + 1 => 0xaa, + 2 => 0xaaaa, + 4 => 0xaaaaaaaa, + 8 => 0xaaaaaaaaaaaaaaaa, + else => unreachable, + }; + return GenResult.mcv(.{ .immediate = imm }); + } + + // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? + if (tv.ty.zigTypeTag() == .Pointer) blk: { + if (tv.ty.castPtrToFn()) |_| break :blk; + if (!tv.ty.elemType2().hasRuntimeBits()) { + return GenResult.mcv(.none); + } + } + + module.markDeclAlive(decl); + + if (bin_file.cast(link.File.Elf)) |elf_file| { + const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index); + const atom = elf_file.getAtom(atom_index); + return GenResult.mcv(.{ .memory = atom.getOffsetTableAddress(elf_file) }); + } else if (bin_file.cast(link.File.MachO)) |macho_file| { + const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index); + const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; + return GenResult.mcv(.{ .linker_load = .{ + .type = .got, + .sym_index = sym_index, + } }); + } else if (bin_file.cast(link.File.Coff)) |coff_file| { + const atom_index = try coff_file.getOrCreateAtomForDecl(decl_index); + const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; + return GenResult.mcv(.{ .linker_load = .{ + .type = .got, + .sym_index = sym_index, + } }); + } else if (bin_file.cast(link.File.Plan9)) |p9| { + const decl_block_index = try p9.seeDecl(decl_index); + const decl_block = p9.getDeclBlock(decl_block_index); + const got_addr = p9.bases.data + decl_block.got_index.? * ptr_bytes; + return GenResult.mcv(.{ .memory = got_addr }); + } else { + return GenResult.fail(bin_file.allocator, src_loc, "TODO genDeclRef for target {}", .{target}); + } +} + +fn genUnnamedConst( + bin_file: *link.File, + src_loc: Module.SrcLoc, + tv: TypedValue, + owner_decl_index: Module.Decl.Index, +) CodeGenError!GenResult { + log.debug("genUnnamedConst: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() }); + + const target = bin_file.options.target; + const local_sym_index = bin_file.lowerUnnamedConst(tv, owner_decl_index) catch |err| { + return GenResult.fail(bin_file.allocator, src_loc, "lowering unnamed constant failed: {s}", .{@errorName(err)}); + }; + if (bin_file.cast(link.File.Elf)) |elf_file| { + return GenResult.mcv(.{ .memory = elf_file.getSymbol(local_sym_index).st_value }); + } else if (bin_file.cast(link.File.MachO)) |_| { + return GenResult.mcv(.{ .linker_load = .{ + .type = .direct, + .sym_index = local_sym_index, + } }); + } else if (bin_file.cast(link.File.Coff)) |_| { + return GenResult.mcv(.{ .linker_load = .{ + .type = .direct, + .sym_index = local_sym_index, + } }); + } else if (bin_file.cast(link.File.Plan9)) |p9| { + const ptr_bits = target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + const got_index = local_sym_index; // the plan9 backend returns the got_index + const got_addr = p9.bases.data + got_index * ptr_bytes; + return GenResult.mcv(.{ .memory = got_addr }); + } else { + return GenResult.fail(bin_file.allocator, src_loc, "TODO genUnnamedConst for target {}", .{target}); + } +} + +pub fn genTypedValue( + bin_file: *link.File, + src_loc: Module.SrcLoc, + arg_tv: TypedValue, + owner_decl_index: Module.Decl.Index, +) CodeGenError!GenResult { + var typed_value = arg_tv; + if (typed_value.val.castTag(.runtime_value)) |rt| { + typed_value.val = rt.data; + } + + log.debug("genTypedValue: ty = {}, val = {}", .{ typed_value.ty.fmtDebug(), typed_value.val.fmtDebug() }); + + if (typed_value.val.isUndef()) + return GenResult.mcv(.undef); + + const target = bin_file.options.target; + const ptr_bits = target.cpu.arch.ptrBitWidth(); + + if (typed_value.val.castTag(.decl_ref)) |payload| { + return genDeclRef(bin_file, src_loc, typed_value, payload.data); + } + if (typed_value.val.castTag(.decl_ref_mut)) |payload| { + return genDeclRef(bin_file, src_loc, typed_value, payload.data.decl_index); + } + + switch (typed_value.ty.zigTypeTag()) { + .Void => return GenResult.mcv(.none), + .Pointer => switch (typed_value.ty.ptrSize()) { + .Slice => {}, + else => { + switch (typed_value.val.tag()) { + .int_u64 => { + return GenResult.mcv(.{ .immediate = typed_value.val.toUnsignedInt(target) }); + }, + else => {}, + } + }, + }, + .Int => { + const info = typed_value.ty.intInfo(target); + if (info.bits <= ptr_bits and info.signedness == .signed) { + return GenResult.mcv(.{ .immediate = @bitCast(u64, typed_value.val.toSignedInt(target)) }); + } + if (!(info.bits > ptr_bits or info.signedness == .signed)) { + return GenResult.mcv(.{ .immediate = typed_value.val.toUnsignedInt(target) }); + } + }, + .Bool => { + return GenResult.mcv(.{ .immediate = @boolToInt(typed_value.val.toBool()) }); + }, + .Optional => { + if (typed_value.ty.isPtrLikeOptional()) { + if (typed_value.val.isNull()) + return GenResult.mcv(.{ .immediate = 0 }); + + var buf: Type.Payload.ElemType = undefined; + return genTypedValue(bin_file, src_loc, .{ + .ty = typed_value.ty.optionalChild(&buf), + .val = typed_value.val, + }, owner_decl_index); + } else if (typed_value.ty.abiSize(target) == 1) { + return GenResult.mcv(.{ .immediate = @boolToInt(!typed_value.val.isNull()) }); + } + }, + .Enum => { + if (typed_value.val.castTag(.enum_field_index)) |field_index| { + switch (typed_value.ty.tag()) { + .enum_simple => { + return GenResult.mcv(.{ .immediate = field_index.data }); + }, + .enum_full, .enum_nonexhaustive => { + const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; + if (enum_full.values.count() != 0) { + const tag_val = enum_full.values.keys()[field_index.data]; + return genTypedValue(bin_file, src_loc, .{ + .ty = enum_full.tag_ty, + .val = tag_val, + }, owner_decl_index); + } else { + return GenResult.mcv(.{ .immediate = field_index.data }); + } + }, + else => unreachable, + } + } else { + var int_tag_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); + return genTypedValue(bin_file, src_loc, .{ + .ty = int_tag_ty, + .val = typed_value.val, + }, owner_decl_index); + } + }, + .ErrorSet => { + switch (typed_value.val.tag()) { + .@"error" => { + const err_name = typed_value.val.castTag(.@"error").?.data.name; + const module = bin_file.options.module.?; + const global_error_set = module.global_error_set; + const error_index = global_error_set.get(err_name).?; + return GenResult.mcv(.{ .immediate = error_index }); + }, + else => { + // In this case we are rendering an error union which has a 0 bits payload. + return GenResult.mcv(.{ .immediate = 0 }); + }, + } + }, + .ErrorUnion => { + const error_type = typed_value.ty.errorUnionSet(); + const payload_type = typed_value.ty.errorUnionPayload(); + const is_pl = typed_value.val.errorUnionIsPayload(); + + if (!payload_type.hasRuntimeBitsIgnoreComptime()) { + // We use the error type directly as the type. + const err_val = if (!is_pl) typed_value.val else Value.initTag(.zero); + return genTypedValue(bin_file, src_loc, .{ + .ty = error_type, + .val = err_val, + }, owner_decl_index); + } + }, + + .ComptimeInt => unreachable, + .ComptimeFloat => unreachable, + .Type => unreachable, + .EnumLiteral => unreachable, + .NoReturn => unreachable, + .Undefined => unreachable, + .Null => unreachable, + .Opaque => unreachable, + + else => {}, + } + + return genUnnamedConst(bin_file, src_loc, typed_value, owner_decl_index); +} + pub fn errUnionPayloadOffset(payload_ty: Type, target: std.Target) u64 { const payload_align = payload_ty.abiAlignment(target); const error_align = Type.anyerror.abiAlignment(target); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index c0ac7e0b88..f210f2f2b3 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1060,7 +1060,7 @@ pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.In decl.analysis = .codegen_failure; try mod.failed_decls.put(mod.gpa, decl_index, em); log.err("{s}", .{em.msg}); - return error.AnalysisFail; + return error.CodegenFail; }, }; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 1a9d594c56..f499a9952a 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2618,7 +2618,7 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl_index: Module decl.analysis = .codegen_failure; try mod.failed_decls.put(mod.gpa, decl_index, em); log.err("{s}", .{em.msg}); - return error.AnalysisFail; + return error.CodegenFail; }, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 7c1d4776af..eaf16e4009 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -2089,7 +2089,7 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: Modu decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl_index, em); log.err("{s}", .{em.msg}); - return error.AnalysisFail; + return error.CodegenFail; }, }; diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 87e3ca5c22..cf6e4f8418 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -377,7 +377,7 @@ pub fn lowerUnnamedConst(self: *Plan9, tv: TypedValue, decl_index: Module.Decl.I decl.analysis = .codegen_failure; try mod.failed_decls.put(mod.gpa, decl_index, em); log.err("{s}", .{em.msg}); - return error.AnalysisFail; + return error.CodegenFail; }, }; // duped_code is freed when the unnamed const is freed diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 00a52177f7..ac0c8e9ca5 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1255,7 +1255,7 @@ pub fn lowerUnnamedConst(wasm: *Wasm, tv: TypedValue, decl_index: Module.Decl.In .fail => |em| { decl.analysis = .codegen_failure; try mod.failed_decls.put(mod.gpa, decl_index, em); - return error.AnalysisFail; + return error.CodegenFail; }, }; }; diff --git a/src/register_manager.zig b/src/register_manager.zig index 2fe0cd2b6a..4d16348c27 100644 --- a/src/register_manager.zig +++ b/src/register_manager.zig @@ -19,6 +19,9 @@ pub const AllocateRegistersError = error{ /// Can happen when spilling an instruction in codegen runs out of /// memory, so we propagate that error OutOfMemory, + /// Can happen when spilling an instruction in codegen triggers integer + /// overflow, so we propagate that error + Overflow, /// Can happen when spilling an instruction triggers a codegen /// error, so we propagate that error CodegenFail, From 1024332adc88928299dfc07426f11624ae8ba18b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:24:58 +0100 Subject: [PATCH 002/294] arm: use common implementation of genTypedValue helper --- src/arch/arm/CodeGen.zig | 192 ++++-------------------------------- src/arch/x86_64/CodeGen.zig | 6 +- 2 files changed, 23 insertions(+), 175 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 87806223e3..7d8708c44d 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -24,7 +24,7 @@ const log = std.log.scoped(.codegen); const build_options = @import("build_options"); const Result = codegen.Result; -const GenerateSymbolError = codegen.GenerateSymbolError; +const CodeGenError = codegen.CodeGenError; const DebugInfoOutput = codegen.DebugInfoOutput; const bits = @import("bits.zig"); @@ -42,7 +42,7 @@ const c_abi_int_param_regs = abi.c_abi_int_param_regs; const c_abi_int_return_regs = abi.c_abi_int_return_regs; const gp = abi.RegisterClass.gp; -const InnerError = codegen.CodeGenError || error{OutOfRegisters}; +const InnerError = CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, @@ -339,7 +339,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) GenerateSymbolError!Result { +) CodeGenError!Result { if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } @@ -6083,178 +6083,26 @@ fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue { } } -fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - - const mod = self.bin_file.options.module.?; - const decl = mod.declPtr(decl_index); - mod.markDeclAlive(decl); - - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index); - const atom = elf_file.getAtom(atom_index); - return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) }; - } else if (self.bin_file.cast(link.File.MachO)) |_| { - unreachable; // unsupported architecture for MachO - } else if (self.bin_file.cast(link.File.Coff)) |_| { - return self.fail("TODO codegen COFF const Decl pointer", .{}); - } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl_block_index = try p9.seeDecl(decl_index); - const decl_block = p9.getDeclBlock(decl_block_index); - const got_addr = p9.bases.data + decl_block.got_index.? * ptr_bytes; - return MCValue{ .memory = got_addr }; - } else { - return self.fail("TODO codegen non-ELF const Decl pointer", .{}); - } - - _ = tv; -} - -fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue { - const local_sym_index = self.bin_file.lowerUnnamedConst(tv, self.mod_fn.owner_decl) catch |err| { - return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)}); - }; - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - return MCValue{ .memory = elf_file.getSymbol(local_sym_index).st_value }; - } else if (self.bin_file.cast(link.File.MachO)) |_| { - unreachable; - } else if (self.bin_file.cast(link.File.Coff)) |_| { - return self.fail("TODO lower unnamed const in COFF", .{}); - } else if (self.bin_file.cast(link.File.Plan9)) |_| { - return self.fail("TODO lower unnamed const in Plan9", .{}); - } else { - return self.fail("TODO lower unnamed const", .{}); - } -} - fn genTypedValue(self: *Self, arg_tv: TypedValue) InnerError!MCValue { - var typed_value = arg_tv; - if (typed_value.val.castTag(.runtime_value)) |rt| { - typed_value.val = rt.data; - } - log.debug("genTypedValue: ty = {}, val = {}", .{ typed_value.ty.fmtDebug(), typed_value.val.fmtDebug() }); - if (typed_value.val.isUndef()) - return MCValue{ .undef = {} }; - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - - if (typed_value.val.castTag(.decl_ref)) |payload| { - return self.lowerDeclRef(typed_value, payload.data); - } - if (typed_value.val.castTag(.decl_ref_mut)) |payload| { - return self.lowerDeclRef(typed_value, payload.data.decl_index); - } - const target = self.target.*; - - switch (typed_value.ty.zigTypeTag()) { - .Pointer => switch (typed_value.ty.ptrSize()) { - .Slice => {}, - else => { - switch (typed_value.val.tag()) { - .int_u64 => { - return MCValue{ .immediate = @intCast(u32, typed_value.val.toUnsignedInt(target)) }; - }, - else => {}, - } - }, + const mcv: MCValue = switch (try codegen.genTypedValue( + self.bin_file, + self.src_loc, + arg_tv, + self.mod_fn.owner_decl, + )) { + .mcv => |mcv| switch (mcv) { + .none => .none, + .undef => .undef, + .linker_load => unreachable, // TODO + .immediate => |imm| .{ .immediate = @truncate(u32, imm) }, + .memory => |addr| .{ .memory = addr }, }, - .Int => { - const info = typed_value.ty.intInfo(self.target.*); - if (info.bits <= ptr_bits) { - const unsigned = switch (info.signedness) { - .signed => blk: { - const signed = @intCast(i32, typed_value.val.toSignedInt(target)); - break :blk @bitCast(u32, signed); - }, - .unsigned => @intCast(u32, typed_value.val.toUnsignedInt(target)), - }; - - return MCValue{ .immediate = unsigned }; - } else { - return self.lowerUnnamedConst(typed_value); - } + .fail => |msg| { + self.err_msg = msg; + return error.CodegenFail; }, - .Bool => { - return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; - }, - .Optional => { - if (typed_value.ty.isPtrLikeOptional()) { - if (typed_value.val.isNull()) - return MCValue{ .immediate = 0 }; - - var buf: Type.Payload.ElemType = undefined; - return self.genTypedValue(.{ - .ty = typed_value.ty.optionalChild(&buf), - .val = typed_value.val, - }); - } else if (typed_value.ty.abiSize(self.target.*) == 1) { - return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) }; - } - }, - .Enum => { - if (typed_value.val.castTag(.enum_field_index)) |field_index| { - switch (typed_value.ty.tag()) { - .enum_simple => { - return MCValue{ .immediate = field_index.data }; - }, - .enum_full, .enum_nonexhaustive => { - const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - const tag_val = enum_full.values.keys()[field_index.data]; - return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); - } else { - return MCValue{ .immediate = field_index.data }; - } - }, - else => unreachable, - } - } else { - var int_tag_buffer: Type.Payload.Bits = undefined; - const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); - return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val }); - } - }, - .ErrorSet => { - switch (typed_value.val.tag()) { - .@"error" => { - const err_name = typed_value.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - else => { - // In this case we are rendering an error union which has a 0 bits payload. - return MCValue{ .immediate = 0 }; - }, - } - }, - .ErrorUnion => { - const error_type = typed_value.ty.errorUnionSet(); - const payload_type = typed_value.ty.errorUnionPayload(); - const is_pl = typed_value.val.errorUnionIsPayload(); - - if (!payload_type.hasRuntimeBitsIgnoreComptime()) { - // We use the error type directly as the type. - const err_val = if (!is_pl) typed_value.val else Value.initTag(.zero); - return self.genTypedValue(.{ .ty = error_type, .val = err_val }); - } - }, - - .ComptimeInt => unreachable, // semantic analysis prevents this - .ComptimeFloat => unreachable, // semantic analysis prevents this - .Type => unreachable, - .EnumLiteral => unreachable, - .Void => unreachable, - .NoReturn => unreachable, - .Undefined => unreachable, - .Null => unreachable, - .Opaque => unreachable, - - else => {}, - } - - return self.lowerUnnamedConst(typed_value); + }; + return mcv; } const CallMCValues = struct { diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 2ec1a33619..a2c11b332b 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -12,12 +12,12 @@ const trace = @import("../../tracy.zig").trace; const Air = @import("../../Air.zig"); const Allocator = mem.Allocator; +const CodeGenError = codegen.CodeGenError; const Compilation = @import("../../Compilation.zig"); const DebugInfoOutput = codegen.DebugInfoOutput; const DW = std.dwarf; const ErrorMsg = Module.ErrorMsg; const Result = codegen.Result; -const GenerateSymbolError = codegen.GenerateSymbolError; const Emit = @import("Emit.zig"); const Liveness = @import("../../Liveness.zig"); const Mir = @import("Mir.zig"); @@ -40,7 +40,7 @@ const Register = bits.Register; const gp = abi.RegisterClass.gp; const sse = abi.RegisterClass.sse; -const InnerError = codegen.CodeGenError || error{OutOfRegisters}; +const InnerError = CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, @@ -253,7 +253,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) GenerateSymbolError!Result { +) CodeGenError!Result { if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } From c413ac100fa5a4cece5702d3afb6b0898e9c6214 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:40:16 +0100 Subject: [PATCH 003/294] codegen: refactor generating Int as immediate where appropriate --- src/codegen.zig | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 245745d6f6..7e7f34f992 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1051,11 +1051,12 @@ pub fn genTypedValue( }, .Int => { const info = typed_value.ty.intInfo(target); - if (info.bits <= ptr_bits and info.signedness == .signed) { - return GenResult.mcv(.{ .immediate = @bitCast(u64, typed_value.val.toSignedInt(target)) }); - } - if (!(info.bits > ptr_bits or info.signedness == .signed)) { - return GenResult.mcv(.{ .immediate = typed_value.val.toUnsignedInt(target) }); + if (info.bits <= ptr_bits) { + const unsigned = switch (info.signedness) { + .signed => @bitCast(u64, typed_value.val.toSignedInt(target)), + .unsigned => typed_value.val.toUnsignedInt(target), + }; + return GenResult.mcv(.{ .immediate = unsigned }); } }, .Bool => { From d8d8842190214cf727611b965e830ccbfffb52d1 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:42:29 +0100 Subject: [PATCH 004/294] arm: skip unimplemented behavior test for @fieldParentPtr --- test/behavior/field_parent_ptr.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/behavior/field_parent_ptr.zig b/test/behavior/field_parent_ptr.zig index 6bbd6ad7ef..bf99fd1795 100644 --- a/test/behavior/field_parent_ptr.zig +++ b/test/behavior/field_parent_ptr.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); test "@fieldParentPtr non-first field" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testParentFieldPtr(&foo.c); comptime try testParentFieldPtr(&foo.c); @@ -10,6 +11,7 @@ test "@fieldParentPtr non-first field" { test "@fieldParentPtr first field" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testParentFieldPtrFirst(&foo.a); comptime try testParentFieldPtrFirst(&foo.a); @@ -47,6 +49,7 @@ fn testParentFieldPtrFirst(a: *const bool) !void { test "@fieldParentPtr untagged union" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -73,6 +76,7 @@ fn testFieldParentPtrUnion(c: *const i32) !void { test "@fieldParentPtr tagged union" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -99,6 +103,7 @@ fn testFieldParentPtrTaggedUnion(c: *const i32) !void { test "@fieldParentPtr extern union" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO From 0d2c25ca9d0794b1c822a12f3bdf8e57ede4c840 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:46:08 +0100 Subject: [PATCH 005/294] aarch64: use common implementation of genTypedValue --- src/arch/aarch64/CodeGen.zig | 215 ++++------------------------------- 1 file changed, 20 insertions(+), 195 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 23f458f910..28f8370bd9 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -23,7 +23,7 @@ const leb128 = std.leb; const log = std.log.scoped(.codegen); const build_options = @import("build_options"); -const GenerateSymbolError = codegen.GenerateSymbolError; +const CodeGenError = codegen.CodeGenError; const Result = codegen.Result; const DebugInfoOutput = codegen.DebugInfoOutput; @@ -41,7 +41,7 @@ const c_abi_int_param_regs = abi.c_abi_int_param_regs; const c_abi_int_return_regs = abi.c_abi_int_return_regs; const gp = abi.RegisterClass.gp; -const InnerError = codegen.CodeGenError || error{OutOfRegisters}; +const InnerError = CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, @@ -333,7 +333,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) GenerateSymbolError!Result { +) CodeGenError!Result { if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } @@ -6133,201 +6133,26 @@ fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue { } } -fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - - // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? - if (tv.ty.zigTypeTag() == .Pointer) blk: { - if (tv.ty.castPtrToFn()) |_| break :blk; - if (!tv.ty.elemType2().hasRuntimeBits()) { - return MCValue.none; - } - } - - const mod = self.bin_file.options.module.?; - const decl = mod.declPtr(decl_index); - mod.markDeclAlive(decl); - - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index); - const atom = elf_file.getAtom(atom_index); - return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) }; - } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const atom = try macho_file.getOrCreateAtomForDecl(decl_index); - const sym_index = macho_file.getAtom(atom).getSymbolIndex().?; - return MCValue{ .linker_load = .{ - .type = .got, - .sym_index = sym_index, - } }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const atom_index = try coff_file.getOrCreateAtomForDecl(decl_index); - const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; - return MCValue{ .linker_load = .{ - .type = .got, - .sym_index = sym_index, - } }; - } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl_block_index = try p9.seeDecl(decl_index); - const decl_block = p9.getDeclBlock(decl_block_index); - const got_addr = p9.bases.data + decl_block.got_index.? * ptr_bytes; - return MCValue{ .memory = got_addr }; - } else { - return self.fail("TODO codegen non-ELF const Decl pointer", .{}); - } -} - -fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue { - log.debug("lowerUnnamedConst: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() }); - const local_sym_index = self.bin_file.lowerUnnamedConst(tv, self.mod_fn.owner_decl) catch |err| { - return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)}); - }; - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - return MCValue{ .memory = elf_file.getSymbol(local_sym_index).st_value }; - } else if (self.bin_file.cast(link.File.MachO)) |_| { - return MCValue{ .linker_load = .{ - .type = .direct, - .sym_index = local_sym_index, - } }; - } else if (self.bin_file.cast(link.File.Coff)) |_| { - return MCValue{ .linker_load = .{ - .type = .direct, - .sym_index = local_sym_index, - } }; - } else if (self.bin_file.cast(link.File.Plan9)) |_| { - return self.fail("TODO lower unnamed const in Plan9", .{}); - } else { - return self.fail("TODO lower unnamed const", .{}); - } -} - fn genTypedValue(self: *Self, arg_tv: TypedValue) InnerError!MCValue { - var typed_value = arg_tv; - if (typed_value.val.castTag(.runtime_value)) |rt| { - typed_value.val = rt.data; - } - log.debug("genTypedValue: ty = {}, val = {}", .{ typed_value.ty.fmtDebug(), typed_value.val.fmtDebug() }); - if (typed_value.val.isUndef()) - return MCValue{ .undef = {} }; - - if (typed_value.val.castTag(.decl_ref)) |payload| { - return self.lowerDeclRef(typed_value, payload.data); - } - if (typed_value.val.castTag(.decl_ref_mut)) |payload| { - return self.lowerDeclRef(typed_value, payload.data.decl_index); - } - const target = self.target.*; - - switch (typed_value.ty.zigTypeTag()) { - .Pointer => switch (typed_value.ty.ptrSize()) { - .Slice => {}, - else => { - switch (typed_value.val.tag()) { - .int_u64 => { - return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) }; - }, - else => {}, - } - }, + const mcv: MCValue = switch (try codegen.genTypedValue( + self.bin_file, + self.src_loc, + arg_tv, + self.mod_fn.owner_decl, + )) { + .mcv => |mcv| switch (mcv) { + .none => .none, + .undef => .undef, + .linker_load => |ll| .{ .linker_load = ll }, + .immediate => |imm| .{ .immediate = imm }, + .memory => |addr| .{ .memory = addr }, }, - .Int => { - const info = typed_value.ty.intInfo(self.target.*); - if (info.bits <= 64) { - const unsigned = switch (info.signedness) { - .signed => blk: { - const signed = typed_value.val.toSignedInt(target); - break :blk @bitCast(u64, signed); - }, - .unsigned => typed_value.val.toUnsignedInt(target), - }; - - return MCValue{ .immediate = unsigned }; - } + .fail => |msg| { + self.err_msg = msg; + return error.CodegenFail; }, - .Bool => { - return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; - }, - .Optional => { - if (typed_value.ty.isPtrLikeOptional()) { - if (typed_value.val.isNull()) - return MCValue{ .immediate = 0 }; - - var buf: Type.Payload.ElemType = undefined; - return self.genTypedValue(.{ - .ty = typed_value.ty.optionalChild(&buf), - .val = typed_value.val, - }); - } else if (typed_value.ty.abiSize(self.target.*) == 1) { - return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) }; - } - }, - .Enum => { - if (typed_value.val.castTag(.enum_field_index)) |field_index| { - switch (typed_value.ty.tag()) { - .enum_simple => { - return MCValue{ .immediate = field_index.data }; - }, - .enum_full, .enum_nonexhaustive => { - const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - const tag_val = enum_full.values.keys()[field_index.data]; - return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); - } else { - return MCValue{ .immediate = field_index.data }; - } - }, - else => unreachable, - } - } else { - var int_tag_buffer: Type.Payload.Bits = undefined; - const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); - return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val }); - } - }, - .ErrorSet => { - switch (typed_value.val.tag()) { - .@"error" => { - const err_name = typed_value.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - else => { - // In this case we are rendering an error union which has a 0 bits payload. - return MCValue{ .immediate = 0 }; - }, - } - }, - .ErrorUnion => { - const error_type = typed_value.ty.errorUnionSet(); - const payload_type = typed_value.ty.errorUnionPayload(); - - const is_pl = typed_value.val.errorUnionIsPayload(); - - if (!payload_type.hasRuntimeBitsIgnoreComptime()) { - // We use the error type directly as the type. - const err_val = if (!is_pl) typed_value.val else Value.initTag(.zero); - return self.genTypedValue(.{ .ty = error_type, .val = err_val }); - } - - return self.lowerUnnamedConst(typed_value); - }, - - .ComptimeInt => unreachable, // semantic analysis prevents this - .ComptimeFloat => unreachable, // semantic analysis prevents this - .Type => unreachable, - .EnumLiteral => unreachable, - .Void => unreachable, - .NoReturn => unreachable, - .Undefined => unreachable, - .Null => unreachable, - .Opaque => unreachable, - - else => {}, - } - - return self.lowerUnnamedConst(typed_value); + }; + return mcv; } const CallMCValues = struct { From 5b3ea49806f5d0b9034e3eacbef9e19428a5db8a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:53:13 +0100 Subject: [PATCH 006/294] riscv64: use common implementation of genTypedValue --- src/arch/riscv64/CodeGen.zig | 158 +++++------------------------------ 1 file changed, 20 insertions(+), 138 deletions(-) diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index fad5482cbc..c7191145f9 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -23,6 +23,7 @@ const log = std.log.scoped(.codegen); const build_options = @import("build_options"); const codegen = @import("../../codegen.zig"); +const CodeGenError = codegen.CodeGenError; const Result = codegen.Result; const DebugInfoOutput = codegen.DebugInfoOutput; @@ -35,7 +36,7 @@ const Instruction = abi.Instruction; const callee_preserved_regs = abi.callee_preserved_regs; const gp = abi.RegisterClass.gp; -const InnerError = codegen.CodeGenError || error{OutOfRegisters}; +const InnerError = CodeGenError || error{OutOfRegisters}; gpa: Allocator, air: Air, @@ -221,7 +222,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) codegen.CodeGenError!Result { +) CodeGenError!Result { if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } @@ -2548,145 +2549,26 @@ fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue { } } -fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - const mod = self.bin_file.options.module.?; - const decl = mod.declPtr(decl_index); - mod.markDeclAlive(decl); - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index); - const atom = elf_file.getAtom(atom_index); - return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) }; - } else if (self.bin_file.cast(link.File.MachO)) |_| { - unreachable; - } else if (self.bin_file.cast(link.File.Coff)) |_| { - return self.fail("TODO codegen COFF const Decl pointer", .{}); - } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl_block_index = try p9.seeDecl(decl_index); - const decl_block = p9.getDeclBlock(decl_block_index); - const got_addr = p9.bases.data + decl_block.got_index.? * ptr_bytes; - return MCValue{ .memory = got_addr }; - } else { - return self.fail("TODO codegen non-ELF const Decl pointer", .{}); - } - _ = tv; -} - fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { - if (typed_value.val.isUndef()) - return MCValue{ .undef = {} }; - - if (typed_value.val.castTag(.decl_ref)) |payload| { - return self.lowerDeclRef(typed_value, payload.data); - } - if (typed_value.val.castTag(.decl_ref_mut)) |payload| { - return self.lowerDeclRef(typed_value, payload.data.decl_index); - } - const target = self.target.*; - const ptr_bits = self.target.cpu.arch.ptrBitWidth(); - switch (typed_value.ty.zigTypeTag()) { - .Pointer => switch (typed_value.ty.ptrSize()) { - .Slice => { - var buf: Type.SlicePtrFieldTypeBuffer = undefined; - const ptr_type = typed_value.ty.slicePtrFieldType(&buf); - const ptr_mcv = try self.genTypedValue(.{ .ty = ptr_type, .val = typed_value.val }); - const mod = self.bin_file.options.module.?; - const slice_len = typed_value.val.sliceLen(mod); - // Codegen can't handle some kinds of indirection. If the wrong union field is accessed here it may mean - // the Sema code needs to use anonymous Decls or alloca instructions to store data. - const ptr_imm = ptr_mcv.memory; - _ = slice_len; - _ = ptr_imm; - // We need more general support for const data being stored in memory to make this work. - return self.fail("TODO codegen for const slices", .{}); - }, - else => { - if (typed_value.val.tag() == .int_u64) { - return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) }; - } - return self.fail("TODO codegen more kinds of const pointers", .{}); - }, + const mcv: MCValue = switch (try codegen.genTypedValue( + self.bin_file, + self.src_loc, + typed_value, + self.mod_fn.owner_decl, + )) { + .mcv => |mcv| switch (mcv) { + .none => .none, + .undef => .undef, + .linker_load => unreachable, // TODO + .immediate => |imm| .{ .immediate = imm }, + .memory => |addr| .{ .memory = addr }, }, - .Int => { - const info = typed_value.ty.intInfo(self.target.*); - if (info.bits > ptr_bits or info.signedness == .signed) { - return self.fail("TODO const int bigger than ptr and signed int", .{}); - } - return MCValue{ .immediate = typed_value.val.toUnsignedInt(target) }; + .fail => |msg| { + self.err_msg = msg; + return error.CodegenFail; }, - .Bool => { - return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; - }, - .ComptimeInt => unreachable, // semantic analysis prevents this - .ComptimeFloat => unreachable, // semantic analysis prevents this - .Optional => { - if (typed_value.ty.isPtrLikeOptional()) { - if (typed_value.val.isNull()) - return MCValue{ .immediate = 0 }; - - var buf: Type.Payload.ElemType = undefined; - return self.genTypedValue(.{ - .ty = typed_value.ty.optionalChild(&buf), - .val = typed_value.val, - }); - } else if (typed_value.ty.abiSize(self.target.*) == 1) { - return MCValue{ .immediate = @boolToInt(typed_value.val.isNull()) }; - } - return self.fail("TODO non pointer optionals", .{}); - }, - .Enum => { - if (typed_value.val.castTag(.enum_field_index)) |field_index| { - switch (typed_value.ty.tag()) { - .enum_simple => { - return MCValue{ .immediate = field_index.data }; - }, - .enum_full, .enum_nonexhaustive => { - const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - const tag_val = enum_full.values.keys()[field_index.data]; - return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); - } else { - return MCValue{ .immediate = field_index.data }; - } - }, - else => unreachable, - } - } else { - var int_tag_buffer: Type.Payload.Bits = undefined; - const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); - return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val }); - } - }, - .ErrorSet => { - switch (typed_value.val.tag()) { - .@"error" => { - const err_name = typed_value.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - else => { - // In this case we are rendering an error union which has a 0 bits payload. - return MCValue{ .immediate = 0 }; - }, - } - }, - .ErrorUnion => { - const error_type = typed_value.ty.errorUnionSet(); - const payload_type = typed_value.ty.errorUnionPayload(); - const sub_val = typed_value.val.castTag(.eu_payload).?.data; - - if (!payload_type.hasRuntimeBits()) { - // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); - } - - return self.fail("TODO implement error union const of type '{}'", .{typed_value.ty.fmtDebug()}); - }, - else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty.fmtDebug()}), - } + }; + return mcv; } const CallMCValues = struct { From f6eeb6c8ce83af392dc075e3f80846aefc791f42 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:53:30 +0100 Subject: [PATCH 007/294] sparc64: use common implementation of genTypedValue --- src/arch/sparc64/CodeGen.zig | 170 +++++------------------------------ 1 file changed, 20 insertions(+), 150 deletions(-) diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 5a108eca85..dc1a450e9a 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -19,7 +19,7 @@ const Mir = @import("Mir.zig"); const Emit = @import("Emit.zig"); const Liveness = @import("../../Liveness.zig"); const Type = @import("../../type.zig").Type; -const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError; +const CodeGenError = codegen.CodeGenError; const Result = @import("../../codegen.zig").Result; const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; @@ -38,7 +38,7 @@ const gp = abi.RegisterClass.gp; const Self = @This(); -const InnerError = codegen.CodeGenError || error{OutOfRegisters}; +const InnerError = CodeGenError || error{OutOfRegisters}; const RegisterView = enum(u1) { caller, @@ -261,7 +261,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, -) GenerateSymbolError!Result { +) CodeGenError!Result { if (build_options.skip_non_native and builtin.cpu.arch != bin_file.options.target.cpu.arch) { @panic("Attempted to compile for architecture that was disabled by build configuration"); } @@ -3894,133 +3894,25 @@ fn genStore(self: *Self, value_reg: Register, addr_reg: Register, comptime off_t } fn genTypedValue(self: *Self, typed_value: TypedValue) InnerError!MCValue { - var tv = typed_value; - log.debug("genTypedValue: ty = {}, val = {}", .{ tv.ty.fmtDebug(), tv.val.fmtDebug() }); - - if (tv.val.castTag(.runtime_value)) |rt| { - tv.val = rt.data; - } - - if (tv.val.isUndef()) - return MCValue{ .undef = {} }; - - if (tv.val.castTag(.decl_ref)) |payload| { - return self.lowerDeclRef(tv, payload.data); - } - if (tv.val.castTag(.decl_ref_mut)) |payload| { - return self.lowerDeclRef(tv, payload.data.decl_index); - } - const target = self.target.*; - - switch (tv.ty.zigTypeTag()) { - .Pointer => switch (tv.ty.ptrSize()) { - .Slice => {}, - else => { - switch (tv.val.tag()) { - .int_u64 => { - return MCValue{ .immediate = tv.val.toUnsignedInt(target) }; - }, - else => {}, - } - }, + const mcv: MCValue = switch (try codegen.genTypedValue( + self.bin_file, + self.src_loc, + typed_value, + self.mod_fn.owner_decl, + )) { + .mcv => |mcv| switch (mcv) { + .none => .none, + .undef => .undef, + .linker_load => unreachable, // TODO + .immediate => |imm| .{ .immediate = imm }, + .memory => |addr| .{ .memory = addr }, }, - .Bool => { - return MCValue{ .immediate = @boolToInt(tv.val.toBool()) }; + .fail => |msg| { + self.err_msg = msg; + return error.CodegenFail; }, - .Int => { - const info = tv.ty.intInfo(self.target.*); - if (info.bits <= 64) { - const unsigned = switch (info.signedness) { - .signed => blk: { - const signed = tv.val.toSignedInt(target); - break :blk @bitCast(u64, signed); - }, - .unsigned => tv.val.toUnsignedInt(target), - }; - - return MCValue{ .immediate = unsigned }; - } else { - return self.fail("TODO implement int genTypedValue of > 64 bits", .{}); - } - }, - .Optional => { - if (tv.ty.isPtrLikeOptional()) { - if (tv.val.isNull()) - return MCValue{ .immediate = 0 }; - - var buf: Type.Payload.ElemType = undefined; - return self.genTypedValue(.{ - .ty = tv.ty.optionalChild(&buf), - .val = tv.val, - }); - } else if (tv.ty.abiSize(self.target.*) == 1) { - return MCValue{ .immediate = @boolToInt(tv.val.isNull()) }; - } - }, - .Enum => { - if (tv.val.castTag(.enum_field_index)) |field_index| { - switch (tv.ty.tag()) { - .enum_simple => { - return MCValue{ .immediate = field_index.data }; - }, - .enum_full, .enum_nonexhaustive => { - const enum_full = tv.ty.cast(Type.Payload.EnumFull).?.data; - if (enum_full.values.count() != 0) { - const tag_val = enum_full.values.keys()[field_index.data]; - return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); - } else { - return MCValue{ .immediate = field_index.data }; - } - }, - else => unreachable, - } - } else { - var int_tag_buffer: Type.Payload.Bits = undefined; - const int_tag_ty = tv.ty.intTagType(&int_tag_buffer); - return self.genTypedValue(.{ .ty = int_tag_ty, .val = tv.val }); - } - }, - .ErrorSet => { - const err_name = tv.val.castTag(.@"error").?.data.name; - const module = self.bin_file.options.module.?; - const global_error_set = module.global_error_set; - const error_index = global_error_set.get(err_name).?; - return MCValue{ .immediate = error_index }; - }, - .ErrorUnion => { - const error_type = tv.ty.errorUnionSet(); - const payload_type = tv.ty.errorUnionPayload(); - - if (tv.val.castTag(.eu_payload)) |pl| { - if (!payload_type.hasRuntimeBits()) { - // We use the error type directly as the type. - return MCValue{ .immediate = 0 }; - } - - _ = pl; - return self.fail("TODO implement error union const of type '{}' (non-error)", .{tv.ty.fmtDebug()}); - } else { - if (!payload_type.hasRuntimeBits()) { - // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = tv.val }); - } - - return self.fail("TODO implement error union const of type '{}' (error)", .{tv.ty.fmtDebug()}); - } - }, - .ComptimeInt => unreachable, // semantic analysis prevents this - .ComptimeFloat => unreachable, // semantic analysis prevents this - .Type => unreachable, - .EnumLiteral => unreachable, - .Void => unreachable, - .NoReturn => unreachable, - .Undefined => unreachable, - .Null => unreachable, - .Opaque => unreachable, - else => {}, - } - - return self.fail("TODO implement const of type '{}'", .{tv.ty.fmtDebug()}); + }; + return mcv; } fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue { @@ -4196,28 +4088,6 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo } } -fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!MCValue { - // TODO this feels clunky. Perhaps we should check for it in `genTypedValue`? - if (tv.ty.zigTypeTag() == .Pointer) blk: { - if (tv.ty.castPtrToFn()) |_| break :blk; - if (!tv.ty.elemType2().hasRuntimeBits()) { - return MCValue.none; - } - } - - const mod = self.bin_file.options.module.?; - const decl = mod.declPtr(decl_index); - - mod.markDeclAlive(decl); - if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index); - const atom = elf_file.getAtom(atom_index); - return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) }; - } else { - return self.fail("TODO codegen non-ELF const Decl pointer", .{}); - } -} - fn minMax( self: *Self, tag: Air.Inst.Tag, From d23472747eb288e4c2332e03f6185c69e864f67d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:53:47 +0100 Subject: [PATCH 008/294] elf: fully zero out symbol when appending to freelist --- src/link/Elf.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index f499a9952a..a91722d072 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2097,9 +2097,16 @@ fn freeAtom(self: *Elf, atom_index: Atom.Index) void { // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. const local_sym_index = atom.getSymbolIndex().?; + log.debug("adding %{d} to local symbols free list", .{local_sym_index}); self.local_symbol_free_list.append(gpa, local_sym_index) catch {}; - self.local_symbols.items[local_sym_index].st_info = 0; - self.local_symbols.items[local_sym_index].st_shndx = 0; + self.local_symbols.items[local_sym_index] = .{ + .st_name = 0, + .st_info = 0, + .st_other = 0, + .st_shndx = 0, + .st_value = 0, + .st_size = 0, + }; _ = self.atom_by_index_table.remove(local_sym_index); self.getAtomPtr(atom_index).local_sym_index = 0; From dc709fbf48798ae74d5c7763cf99dffeb8143795 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 3 Mar 2023 18:56:57 +0100 Subject: [PATCH 009/294] codegen: rename GenerateSymbolError to CodeGenError --- src/arch/wasm/CodeGen.zig | 2 +- src/codegen.zig | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 511a10769e..5cd6c95690 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1162,7 +1162,7 @@ pub fn generate( liveness: Liveness, code: *std.ArrayList(u8), debug_output: codegen.DebugInfoOutput, -) codegen.GenerateSymbolError!codegen.Result { +) codegen.CodeGenError!codegen.Result { _ = src_loc; var code_gen: CodeGen = .{ .gpa = bin_file.allocator, diff --git a/src/codegen.zig b/src/codegen.zig index 7e7f34f992..a91795841c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -35,8 +35,6 @@ pub const CodeGenError = error{ CodegenFail, }; -pub const GenerateSymbolError = CodeGenError; - pub const DebugInfoOutput = union(enum) { dwarf: *link.File.Dwarf.DeclState, /// the plan9 debuginfo output is a bytecode with 4 opcodes From d6bd00e85500fa1a7909695ae5943be438f7521d Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 3 Mar 2023 17:30:18 +0100 Subject: [PATCH 010/294] Zir: move set_cold from Inst.Tag to Inst.Extended If I could mark a builtin function as cold, I would mark @setCold as cold. We have run out of `Zir.Inst.Tag`s so I had to move a tag from Zir.Inst.Tag to Zir.Inst.Extended. This is because a new noreturn builtin will be added and noreturn builtins cannot be part of Inst.Tag: ``` /// `noreturn` instructions may not go here; they must be part of the main `Tag` enum. pub const Extended = enum(u16) { ``` Here's another reason I went for @setCold: ``` $ git grep setRuntimeSafety | wc -l 322 $ git grep setCold | wc -l 79 $ git grep setEvalBranchQuota | wc -l 82 ``` This also simply removes @setCold from Autodoc and the docs frontend because as far as I could understand it, builtins represented using Zir extended instructions are not yet supported because I couldn't find @setStackAlign or @setFloatMode there, either. --- lib/docs/main.js | 4 ---- src/AstGen.zig | 13 ++++++++++--- src/Autodoc.zig | 1 - src/Sema.zig | 18 +++++++++--------- src/Zir.zig | 10 ++++------ src/print_zir.zig | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index a0647bbe61..fc99b2f861 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -1187,10 +1187,6 @@ const NAV_MODES = { payloadHtml += "panic"; break; } - case "set_cold": { - payloadHtml += "setCold"; - break; - } case "set_runtime_safety": { payloadHtml += "setRuntimeSafety"; break; diff --git a/src/AstGen.zig b/src/AstGen.zig index 41a8ccadb2..679fc2df0c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2609,8 +2609,9 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) { .breakpoint, .fence, - .set_align_stack, .set_float_mode, + .set_align_stack, + .set_cold, => break :b true, else => break :b false, }, @@ -2658,7 +2659,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .validate_struct_init_comptime, .validate_array_init, .validate_array_init_comptime, - .set_cold, .set_runtime_safety, .closure_capture, .memcpy, @@ -8078,6 +8078,14 @@ fn builtinCall( }); return rvalue(gz, ri, result, node); }, + .set_cold => { + const order = try expr(gz, scope, ri, params[0]); + const result = try gz.addExtendedPayload(.set_cold, Zir.Inst.UnNode{ + .node = gz.nodeIndexToRelative(node), + .operand = order, + }); + return rvalue(gz, ri, result, node); + }, .src => { const token_starts = tree.tokens.items(.start); @@ -8111,7 +8119,6 @@ fn builtinCall( .bool_to_int => return simpleUnOp(gz, scope, ri, node, bool_ri, params[0], .bool_to_int), .embed_file => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[0], .embed_file), .error_name => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .anyerror_type } }, params[0], .error_name), - .set_cold => return simpleUnOp(gz, scope, ri, node, bool_ri, params[0], .set_cold), .set_runtime_safety => return simpleUnOp(gz, scope, ri, node, bool_ri, params[0], .set_runtime_safety), .sqrt => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sqrt), .sin => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .sin), diff --git a/src/Autodoc.zig b/src/Autodoc.zig index 3cf3fff4c0..15d90b104b 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -1338,7 +1338,6 @@ fn walkInstruction( .embed_file, .error_name, .panic, - .set_cold, // @check .set_runtime_safety, // @check .sqrt, .sin, diff --git a/src/Sema.zig b/src/Sema.zig index f9a6f39867..4702d10688 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1167,6 +1167,11 @@ fn analyzeBodyInner( i += 1; continue; }, + .set_cold => { + try sema.zirSetCold(block, extended); + i += 1; + continue; + }, .breakpoint => { if (!block.is_comptime) { _ = try block.addNoOp(.breakpoint); @@ -1304,11 +1309,6 @@ fn analyzeBodyInner( i += 1; continue; }, - .set_cold => { - try sema.zirSetCold(block, inst); - i += 1; - continue; - }, .set_runtime_safety => { try sema.zirSetRuntimeSafety(block, inst); i += 1; @@ -5721,10 +5721,10 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst gop.value_ptr.* = .{ .alignment = alignment, .src = src }; } -fn zirSetCold(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const is_cold = try sema.resolveConstBool(block, operand_src, inst_data.operand, "operand to @setCold must be comptime-known"); +fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { + const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const is_cold = try sema.resolveConstBool(block, operand_src, extra.operand, "operand to @setCold must be comptime-known"); const func = sema.func orelse return; // does nothing outside a function func.is_cold = is_cold; } diff --git a/src/Zir.zig b/src/Zir.zig index 4dd2386c51..c7f2141dcc 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -808,8 +808,6 @@ pub const Inst = struct { panic, /// Same as `panic` but forces comptime. panic_comptime, - /// Implement builtin `@setCold`. Uses `un_node`. - set_cold, /// Implement builtin `@setRuntimeSafety`. Uses `un_node`. set_runtime_safety, /// Implement builtin `@sqrt`. Uses `un_node`. @@ -1187,7 +1185,6 @@ pub const Inst = struct { .bool_to_int, .embed_file, .error_name, - .set_cold, .set_runtime_safety, .sqrt, .sin, @@ -1323,7 +1320,6 @@ pub const Inst = struct { .validate_deref, .@"export", .export_value, - .set_cold, .set_runtime_safety, .memcpy, .memset, @@ -1561,7 +1557,7 @@ pub const Inst = struct { => false, .extended => switch (data.extended.opcode) { - .breakpoint, .fence => true, + .fence, .set_cold, .breakpoint => true, else => false, }, }; @@ -1750,7 +1746,6 @@ pub const Inst = struct { .error_name = .un_node, .panic = .un_node, .panic_comptime = .un_node, - .set_cold = .un_node, .set_runtime_safety = .un_node, .sqrt = .un_node, .sin = .un_node, @@ -1979,6 +1974,9 @@ pub const Inst = struct { /// Implement builtin `@setAlignStack`. /// `operand` is payload index to `UnNode`. set_align_stack, + /// Implements `@setCold`. + /// `operand` is payload index to `UnNode`. + set_cold, /// Implements the `@errSetCast` builtin. /// `operand` is payload index to `BinNode`. `lhs` is dest type, `rhs` is operand. err_set_cast, diff --git a/src/print_zir.zig b/src/print_zir.zig index fb9031296d..5ec9fbcdfc 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -196,7 +196,6 @@ const Writer = struct { .error_name, .panic, .panic_comptime, - .set_cold, .set_runtime_safety, .sqrt, .sin, @@ -503,6 +502,7 @@ const Writer = struct { .fence, .set_float_mode, .set_align_stack, + .set_cold, .wasm_memory_size, .error_to_int, .int_to_error, From e0d390463865340adc8055d1e34c0bc7acf4e4c3 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 3 Mar 2023 09:42:34 +0100 Subject: [PATCH 011/294] Ast: properly handle sentinel-terminated slices in tuple Co-authored-by: Veikka Tuominen --- lib/std/zig/Ast.zig | 9 ++++++--- test/behavior/tuple.zig | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index f99d58aafa..cb86696e13 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -1407,7 +1407,8 @@ pub fn containerField(tree: Ast, node: Node.Index) full.ContainerField { .type_expr = data.lhs, .value_expr = extra.value_expr, .align_expr = extra.align_expr, - .tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon, + .tuple_like = tree.tokens.items(.tag)[main_token] != .identifier or + tree.tokens.items(.tag)[main_token + 1] != .colon, }); } @@ -1420,7 +1421,8 @@ pub fn containerFieldInit(tree: Ast, node: Node.Index) full.ContainerField { .type_expr = data.lhs, .value_expr = data.rhs, .align_expr = 0, - .tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon, + .tuple_like = tree.tokens.items(.tag)[main_token] != .identifier or + tree.tokens.items(.tag)[main_token + 1] != .colon, }); } @@ -1433,7 +1435,8 @@ pub fn containerFieldAlign(tree: Ast, node: Node.Index) full.ContainerField { .type_expr = data.lhs, .value_expr = 0, .align_expr = data.rhs, - .tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon, + .tuple_like = tree.tokens.items(.tag)[main_token] != .identifier or + tree.tokens.items(.tag)[main_token + 1] != .colon, }); } diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index 13b02b40e8..f7860be34e 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -397,3 +397,22 @@ test "nested runtime conditionals in tuple initializer" { }; try expectEqualStrings("up", x[0]); } + +test "sentinel slice in tuple with other fields" { + const S = struct { + a: u32, + b: u32, + }; + + const Submission = union(enum) { + open: struct { *S, [:0]const u8, u32 }, + }; + + _ = Submission; +} + +test "sentinel slice in tuple" { + const S = struct { [:0]const u8 }; + + _ = S; +} From 653814f76ba5d678ebad91f140417cd5829c6aad Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 16:17:23 -0700 Subject: [PATCH 012/294] std.Build.addModule: return the created module --- lib/std/Build.zig | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 26919962e3..120196f972 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -550,17 +550,13 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep { return obj_step; } -pub const AddModuleOptions = struct { - name: []const u8, - source_file: FileSource, - dependencies: []const ModuleDependency = &.{}, -}; - -pub fn addModule(b: *Build, options: AddModuleOptions) void { - b.modules.put(b.dupe(options.name), b.createModule(.{ - .source_file = options.source_file, - .dependencies = options.dependencies, - })) catch @panic("OOM"); +/// This function creates a module and adds it to the package's module set, making +/// it available to other packages which depend on this one. +/// `createModule` can be used instead to create a private module. +pub fn addModule(b: *Build, name: []const u8, options: CreateModuleOptions) *Module { + const module = b.createModule(options); + b.modules.put(b.dupe(name), module) catch @panic("OOM"); + return module; } pub const ModuleDependency = struct { @@ -573,8 +569,9 @@ pub const CreateModuleOptions = struct { dependencies: []const ModuleDependency = &.{}, }; -/// Prefer to use `addModule` which will make the module available to other -/// packages which depend on this package. +/// This function creates a private module, to be used by the current package, +/// but not exposed to other packages depending on this one. +/// `addModule` can be used instead to create a public module. pub fn createModule(b: *Build, options: CreateModuleOptions) *Module { const module = b.allocator.create(Module) catch @panic("OOM"); module.* = .{ From 65368683ad92b858d0a391cb29d37c0476784b40 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 3 Mar 2023 18:35:03 +0100 Subject: [PATCH 013/294] add @trap builtin This introduces a new builtin function that compiles down to something that results in an illegal instruction exception/interrupt. It can be used to exit a program abnormally. This implements the builtin for all backends. --- doc/langref.html.in | 17 ++++++++++++++++- lib/zig.h | 10 ++++++++-- src/Air.zig | 10 +++++++++- src/AstGen.zig | 8 +++++++- src/BuiltinFn.zig | 8 ++++++++ src/Liveness.zig | 2 ++ src/Sema.zig | 9 +++++++++ src/Zir.zig | 11 +++++++++-- src/arch/aarch64/CodeGen.zig | 11 ++++++++++- src/arch/arm/CodeGen.zig | 9 +++++++++ src/arch/arm/Emit.zig | 9 +++++++-- src/arch/arm/Mir.zig | 2 ++ src/arch/arm/bits.zig | 11 +++++++++++ src/arch/riscv64/CodeGen.zig | 9 +++++++++ src/arch/riscv64/Emit.zig | 2 ++ src/arch/riscv64/Mir.zig | 1 + src/arch/riscv64/bits.zig | 1 + src/arch/sparc64/CodeGen.zig | 16 ++++++++++++++++ src/arch/wasm/CodeGen.zig | 6 ++++++ src/arch/x86_64/CodeGen.zig | 10 ++++++++++ src/arch/x86_64/Emit.zig | 7 +++++++ src/arch/x86_64/Mir.zig | 3 +++ src/codegen/c.zig | 6 ++++++ src/codegen/llvm.zig | 8 ++++++++ src/print_air.zig | 1 + src/print_zir.zig | 1 + 26 files changed, 178 insertions(+), 10 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index e016ef13f8..0290d3acd6 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7818,12 +7818,14 @@ comptime {

This function inserts a platform-specific debug trap instruction which causes debuggers to break there. + Unlike for {#syntax#}@trap(){#endsyntax#}, execution may continue after this point if the program is resumed.

This function is only valid within function scope.

- + {#see_also|@trap#} {#header_close#} + {#header_open|@mulAdd#}
{#syntax#}@mulAdd(comptime T: type, a: T, b: T, c: T) T{#endsyntax#}

@@ -9393,6 +9395,19 @@ fn List(comptime T: type) type {

{#header_close#} + {#header_open|@trap#} +
{#syntax#}@trap() noreturn{#endsyntax#}
+

+ This function inserts a platform-specific trap/jam instruction which can be used to exit the program abnormally. + This may be implemented by explicitly emitting an invalid instruction which may cause an illegal instruction exception of some sort. + Unlike for {#syntax#}@breakpoint(){#endsyntax#}, execution does not continue after this point. +

+

+ This function is only valid within function scope. +

+ {#see_also|@breakpoint#} + {#header_close#} + {#header_open|@truncate#}
{#syntax#}@truncate(comptime T: type, integer: anytype) T{#endsyntax#}

diff --git a/lib/zig.h b/lib/zig.h index c10720d1bd..f3ad7db8a1 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -180,10 +180,16 @@ typedef char bool; #define zig_export(sig, symbol, name) __asm(name " = " symbol) #endif +#if zig_has_builtin(trap) +#define zig_trap() __builtin_trap() +#elif defined(__i386__) || defined(__x86_64__) +#define zig_trap() __asm__ volatile("ud2"); +#else +#define zig_trap() raise(SIGILL) +#endif + #if zig_has_builtin(debugtrap) #define zig_breakpoint() __builtin_debugtrap() -#elif zig_has_builtin(trap) || defined(zig_gnuc) -#define zig_breakpoint() __builtin_trap() #elif defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__) #define zig_breakpoint() __debugbreak() #elif defined(__i386__) || defined(__x86_64__) diff --git a/src/Air.zig b/src/Air.zig index 3ebdd319de..4646dcc89e 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -232,7 +232,14 @@ pub const Inst = struct { /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `br` field. br, - /// Lowers to a hardware trap instruction, or the next best thing. + /// Lowers to a trap/jam instruction causing program abortion. + /// This may lower to an instruction known to be invalid. + /// Sometimes, for the lack of a better instruction, `trap` and `breakpoint` may compile down to the same code. + /// Result type is always noreturn; no instructions in a block follow this one. + trap, + /// Lowers to a trap instruction causing debuggers to break here, or the next best thing. + /// The debugger or something else may allow the program to resume after this point. + /// Sometimes, for the lack of a better instruction, `trap` and `breakpoint` may compile down to the same code. /// Result type is always void. breakpoint, /// Yields the return address of the current function. @@ -1186,6 +1193,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .ret, .ret_load, .unreach, + .trap, => return Type.initTag(.noreturn), .breakpoint, diff --git a/src/AstGen.zig b/src/AstGen.zig index 679fc2df0c..fd51e73cf9 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2631,6 +2631,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .repeat_inline, .panic, .panic_comptime, + .trap, .check_comptime_control_flow, => { noreturn_src_node = statement; @@ -8105,7 +8106,7 @@ fn builtinCall( .error_return_trace => return rvalue(gz, ri, try gz.addNodeExtended(.error_return_trace, node), node), .frame => return rvalue(gz, ri, try gz.addNodeExtended(.frame, node), node), .frame_address => return rvalue(gz, ri, try gz.addNodeExtended(.frame_address, node), node), - .breakpoint => return rvalue(gz, ri, try gz.addNodeExtended(.breakpoint, node), node), + .breakpoint => return rvalue(gz, ri, try gz.addNodeExtended(.breakpoint, node), node), .type_info => return simpleUnOpType(gz, scope, ri, node, params[0], .type_info), .size_of => return simpleUnOpType(gz, scope, ri, node, params[0], .size_of), @@ -8178,6 +8179,11 @@ fn builtinCall( try emitDbgNode(gz, node); return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[0], if (gz.force_comptime) .panic_comptime else .panic); }, + .trap => { + try emitDbgNode(gz, node); + _ = try gz.addNode(.trap, node); + return rvalue(gz, ri, .void_value, node); + }, .error_to_int => { const operand = try expr(gz, scope, .{ .rl = .none }, params[0]); const result = try gz.addExtendedPayload(.error_to_int, Zir.Inst.UnNode{ diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index 20edbabe47..79c6617483 100644 --- a/src/BuiltinFn.zig +++ b/src/BuiltinFn.zig @@ -109,6 +109,7 @@ pub const Tag = enum { sub_with_overflow, tag_name, This, + trap, truncate, Type, type_info, @@ -915,6 +916,13 @@ pub const list = list: { .param_count = 0, }, }, + .{ + "@trap", + .{ + .tag = .trap, + .param_count = 0, + }, + }, .{ "@truncate", .{ diff --git a/src/Liveness.zig b/src/Liveness.zig index 481cf25d04..8dc81aa165 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -226,6 +226,7 @@ pub fn categorizeOperand( .ret_ptr, .constant, .const_ty, + .trap, .breakpoint, .dbg_stmt, .dbg_inline_begin, @@ -848,6 +849,7 @@ fn analyzeInst( .ret_ptr, .constant, .const_ty, + .trap, .breakpoint, .dbg_stmt, .dbg_inline_begin, diff --git a/src/Sema.zig b/src/Sema.zig index 4702d10688..8940527bc0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1101,6 +1101,7 @@ fn analyzeBodyInner( .@"unreachable" => break sema.zirUnreachable(block, inst), .panic => break sema.zirPanic(block, inst, false), .panic_comptime => break sema.zirPanic(block, inst, true), + .trap => break sema.zirTrap(block, inst), // zig fmt: on .extended => ext: { @@ -5144,6 +5145,14 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index, force_comptime: bo return always_noreturn; } +fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index { + const src_node = sema.code.instructions.items(.data)[inst].node; + const src = LazySrcLoc.nodeOffset(src_node); + sema.src = src; + _ = try block.addNoOp(.trap); + return always_noreturn; +} + fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); diff --git a/src/Zir.zig b/src/Zir.zig index c7f2141dcc..b8ea2ea295 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -617,7 +617,7 @@ pub const Inst = struct { /// Uses the `un_node` field. typeof_log2_int_type, /// Asserts control-flow will not reach this instruction (`unreachable`). - /// Uses the `unreachable` union field. + /// Uses the `@"unreachable"` union field. @"unreachable", /// Bitwise XOR. `^` /// Uses the `pl_node` union field. Payload is `Bin`. @@ -808,6 +808,9 @@ pub const Inst = struct { panic, /// Same as `panic` but forces comptime. panic_comptime, + /// Implements `@trap`. + /// Uses the `node` field. + trap, /// Implement builtin `@setRuntimeSafety`. Uses `un_node`. set_runtime_safety, /// Implement builtin `@sqrt`. Uses `un_node`. @@ -1274,6 +1277,7 @@ pub const Inst = struct { .repeat_inline, .panic, .panic_comptime, + .trap, .check_comptime_control_flow, => true, }; @@ -1549,6 +1553,7 @@ pub const Inst = struct { .repeat_inline, .panic, .panic_comptime, + .trap, .for_len, .@"try", .try_ptr, @@ -1746,6 +1751,7 @@ pub const Inst = struct { .error_name = .un_node, .panic = .un_node, .panic_comptime = .un_node, + .trap = .node, .set_runtime_safety = .un_node, .sqrt = .un_node, .sin = .un_node, @@ -1982,6 +1988,7 @@ pub const Inst = struct { err_set_cast, /// `operand` is payload index to `UnNode`. await_nosuspend, + /// Implements `@breakpoint`. /// `operand` is `src_node: i32`. breakpoint, /// Implements the `@select` builtin. @@ -1995,7 +2002,7 @@ pub const Inst = struct { int_to_error, /// Implement builtin `@Type`. /// `operand` is payload index to `UnNode`. - /// `small` contains `NameStrategy + /// `small` contains `NameStrategy`. reify, /// Implements the `@asyncCall` builtin. /// `operand` is payload index to `AsyncCall`. diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 818b04f890..a42d0539f2 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -737,6 +737,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), + .trap => try self.airTrap(), .breakpoint => try self.airBreakpoint(), .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), @@ -4198,10 +4199,18 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } +fn airTrap(self: *Self) !void { + _ = try self.addInst(.{ + .tag = .brk, + .data = .{ .imm16 = 0x0001 }, + }); + return self.finishAirBookkeeping(); +} + fn airBreakpoint(self: *Self) !void { _ = try self.addInst(.{ .tag = .brk, - .data = .{ .imm16 = 1 }, + .data = .{ .imm16 = 0xf000 }, }); return self.finishAirBookkeeping(); } diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index ceabe70438..cecda8fd4a 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -721,6 +721,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), + .trap => try self.airTrap(), .breakpoint => try self.airBreakpoint(), .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), @@ -4146,6 +4147,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ .none, .none, .none }); } +fn airTrap(self: *Self) !void { + _ = try self.addInst(.{ + .tag = .undefined_instruction, + .data = .{ .nop = {} }, + }); + return self.finishAirBookkeeping(); +} + fn airBreakpoint(self: *Self) !void { _ = try self.addInst(.{ .tag = .bkpt, diff --git a/src/arch/arm/Emit.zig b/src/arch/arm/Emit.zig index 17540f0968..17415318de 100644 --- a/src/arch/arm/Emit.zig +++ b/src/arch/arm/Emit.zig @@ -1,4 +1,4 @@ -//! This file contains the functionality for lowering AArch64 MIR into +//! This file contains the functionality for lowering AArch32 MIR into //! machine code const Emit = @This(); @@ -15,7 +15,7 @@ const Target = std.Target; const assert = std.debug.assert; const Instruction = bits.Instruction; const Register = bits.Register; -const log = std.log.scoped(.aarch64_emit); +const log = std.log.scoped(.aarch32_emit); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const CodeGen = @import("CodeGen.zig"); @@ -100,6 +100,7 @@ pub fn emitMir( .b => try emit.mirBranch(inst), + .undefined_instruction => try emit.mirUndefinedInstruction(), .bkpt => try emit.mirExceptionGeneration(inst), .blx => try emit.mirBranchExchange(inst), @@ -494,6 +495,10 @@ fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { } } +fn mirUndefinedInstruction(emit: *Emit) !void { + try emit.writeInstruction(Instruction.undefinedInstruction()); +} + fn mirExceptionGeneration(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; const imm16 = emit.mir.instructions.items(.data)[inst].imm16; diff --git a/src/arch/arm/Mir.zig b/src/arch/arm/Mir.zig index 07a8384c2c..736d0574bb 100644 --- a/src/arch/arm/Mir.zig +++ b/src/arch/arm/Mir.zig @@ -35,6 +35,8 @@ pub const Inst = struct { asr, /// Branch b, + /// Undefined instruction + undefined_instruction, /// Breakpoint bkpt, /// Branch with Link and Exchange diff --git a/src/arch/arm/bits.zig b/src/arch/arm/bits.zig index 8e76ae9409..185c4ed921 100644 --- a/src/arch/arm/bits.zig +++ b/src/arch/arm/bits.zig @@ -307,6 +307,9 @@ pub const Instruction = union(enum) { fixed: u4 = 0b1111, cond: u4, }, + undefined_instruction: packed struct { + imm32: u32 = 0xe7ffdefe, + }, breakpoint: packed struct { imm4: u4, fixed_1: u4 = 0b0111, @@ -613,6 +616,7 @@ pub const Instruction = union(enum) { .branch => |v| @bitCast(u32, v), .branch_exchange => |v| @bitCast(u32, v), .supervisor_call => |v| @bitCast(u32, v), + .undefined_instruction => |v| v.imm32, .breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20), }; } @@ -890,6 +894,13 @@ pub const Instruction = union(enum) { }; } + // This instruction has no official mnemonic equivalent so it is public as-is. + pub fn undefinedInstruction() Instruction { + return Instruction{ + .undefined_instruction = .{}, + }; + } + fn breakpoint(imm: u16) Instruction { return Instruction{ .breakpoint = .{ diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index afcf4b0bb7..0b45982fb3 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -550,6 +550,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), + .trap => try self.airTrap(), .breakpoint => try self.airBreakpoint(), .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), @@ -1652,6 +1653,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, mcv, .{ .none, .none, .none }); } +fn airTrap(self: *Self) !void { + _ = try self.addInst(.{ + .tag = .unimp, + .data = .{ .nop = {} }, + }); + return self.finishAirBookkeeping(); +} + fn airBreakpoint(self: *Self) !void { _ = try self.addInst(.{ .tag = .ebreak, diff --git a/src/arch/riscv64/Emit.zig b/src/arch/riscv64/Emit.zig index 387c735896..3b330cbd3f 100644 --- a/src/arch/riscv64/Emit.zig +++ b/src/arch/riscv64/Emit.zig @@ -51,6 +51,7 @@ pub fn emitMir( .ebreak => try emit.mirSystem(inst), .ecall => try emit.mirSystem(inst), + .unimp => try emit.mirSystem(inst), .dbg_line => try emit.mirDbgLine(inst), @@ -153,6 +154,7 @@ fn mirSystem(emit: *Emit, inst: Mir.Inst.Index) !void { switch (tag) { .ebreak => try emit.writeInstruction(Instruction.ebreak), .ecall => try emit.writeInstruction(Instruction.ecall), + .unimp => try emit.writeInstruction(Instruction.unimp), else => unreachable, } } diff --git a/src/arch/riscv64/Mir.zig b/src/arch/riscv64/Mir.zig index 97accb7642..8905b24c3c 100644 --- a/src/arch/riscv64/Mir.zig +++ b/src/arch/riscv64/Mir.zig @@ -32,6 +32,7 @@ pub const Inst = struct { dbg_epilogue_begin, /// Pseudo-instruction: Update debug line dbg_line, + unimp, ebreak, ecall, jalr, diff --git a/src/arch/riscv64/bits.zig b/src/arch/riscv64/bits.zig index 6b94927df8..7b3ff0bfe9 100644 --- a/src/arch/riscv64/bits.zig +++ b/src/arch/riscv64/bits.zig @@ -380,6 +380,7 @@ pub const Instruction = union(enum) { pub const ecall = iType(0b1110011, 0b000, .zero, .zero, 0x000); pub const ebreak = iType(0b1110011, 0b000, .zero, .zero, 0x001); + pub const unimp = iType(0, 0, .zero, .zero, 0); }; pub const Register = enum(u6) { diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index c8f77fe702..1b7290ddce 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -566,6 +566,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), + .trap => try self.airTrap(), .breakpoint => try self.airBreakpoint(), .ret_addr => @panic("TODO try self.airRetAddr(inst)"), .frame_addr => @panic("TODO try self.airFrameAddress(inst)"), @@ -1160,6 +1161,21 @@ fn airBr(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, .dead, .{ branch.operand, .none, .none }); } +fn airTrap(self: *Self) !void { + // ta 0x05 + _ = try self.addInst(.{ + .tag = .tcc, + .data = .{ + .trap = .{ + .is_imm = true, + .cond = .al, + .rs2_or_imm = .{ .imm = 0x05 }, + }, + }, + }); + return self.finishAirBookkeeping(); +} + fn airBreakpoint(self: *Self) !void { // ta 0x01 _ = try self.addInst(.{ diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 2f191fd834..d388bc8fab 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1829,6 +1829,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .arg => func.airArg(inst), .bitcast => func.airBitcast(inst), .block => func.airBlock(inst), + .trap => func.airTrap(inst), .breakpoint => func.airBreakpoint(inst), .br => func.airBr(inst), .bool_to_int => func.airBoolToInt(inst), @@ -3289,6 +3290,11 @@ fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { func.finishAir(inst, result, &.{ty_op.operand}); } +fn airTrap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { + try func.addTag(.@"unreachable"); + func.finishAir(inst, .none, &.{}); +} + fn airBreakpoint(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { // unsupported by wasm itfunc. Can be implemented once we support DWARF // for wasm diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 53d38f520a..70b51e50fd 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -638,6 +638,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), + .trap => try self.airTrap(), .breakpoint => try self.airBreakpoint(), .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), @@ -3917,6 +3918,15 @@ fn genVarDbgInfo( } } +fn airTrap(self: *Self) !void { + _ = try self.addInst(.{ + .tag = .ud, + .ops = Mir.Inst.Ops.encode(.{}), + .data = undefined, + }); + return self.finishAirBookkeeping(); +} + fn airBreakpoint(self: *Self) !void { _ = try self.addInst(.{ .tag = .interrupt, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 12c19915c6..e521de4bd4 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -166,6 +166,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .@"test" => try emit.mirTest(inst), + .ud => try emit.mirUndefinedInstruction(), .interrupt => try emit.mirInterrupt(inst), .nop => {}, // just skip it @@ -234,6 +235,10 @@ fn fixupRelocs(emit: *Emit) InnerError!void { } } +fn mirUndefinedInstruction(emit: *Emit) InnerError!void { + return lowerToZoEnc(.ud2, emit.code); +} + fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .interrupt); @@ -1279,6 +1284,7 @@ const Tag = enum { push, pop, @"test", + ud2, int3, nop, imul, @@ -1571,6 +1577,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) OpCode { .zo => return switch (tag) { .ret_near => OpCode.init(&.{0xc3}), .ret_far => OpCode.init(&.{0xcb}), + .ud2 => OpCode.init(&.{ 0x0F, 0x0B }), .int3 => OpCode.init(&.{0xcc}), .nop => OpCode.init(&.{0x90}), .syscall => OpCode.init(&.{ 0x0f, 0x05 }), diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 112d9a5982..ba71f4cddd 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -329,6 +329,9 @@ pub const Inst = struct { /// TODO handle more cases @"test", + /// Undefined Instruction + ud, + /// Breakpoint form: /// 0b00 int3 interrupt, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index cf428d4bd6..c0585c3a4a 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2741,6 +2741,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .const_ty => unreachable, // excluded from function bodies .arg => try airArg(f, inst), + .trap => try airTrap(f.object.writer()), .breakpoint => try airBreakpoint(f.object.writer()), .ret_addr => try airRetAddr(f, inst), .frame_addr => try airFrameAddress(f, inst), @@ -4428,6 +4429,11 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { return local; } +fn airTrap(writer: anytype) !CValue { + try writer.writeAll("zig_trap();\n"); + return .none; +} + fn airBreakpoint(writer: anytype) !CValue { try writer.writeAll("zig_breakpoint();\n"); return .none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6f240b88f5..1f8473ac32 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4590,6 +4590,7 @@ pub const FuncGen = struct { .block => try self.airBlock(inst), .br => try self.airBr(inst), .switch_br => try self.airSwitchBr(inst), + .trap => try self.airTrap(inst), .breakpoint => try self.airBreakpoint(inst), .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), @@ -8256,6 +8257,13 @@ pub const FuncGen = struct { return fg.load(ptr, ptr_ty); } + fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + _ = inst; + const llvm_fn = self.getIntrinsic("llvm.trap", &.{}); + _ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, undefined, 0, .Cold, .Auto, ""); + return null; + } + fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { _ = inst; const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{}); diff --git a/src/print_air.zig b/src/print_air.zig index 447af5a9c7..f5c06daae2 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -194,6 +194,7 @@ const Writer = struct { .c_va_end, => try w.writeUnOp(s, inst), + .trap, .breakpoint, .unreach, .ret_addr, diff --git a/src/print_zir.zig b/src/print_zir.zig index 5ec9fbcdfc..5e7d0d45de 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -410,6 +410,7 @@ const Writer = struct { .alloc_inferred_comptime_mut, .ret_ptr, .ret_type, + .trap, => try self.writeNode(stream, inst), .error_value, From 4eb3f50fcf6fcfb6b8013571be00b9eeeb909833 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Fri, 3 Mar 2023 19:59:18 +0100 Subject: [PATCH 014/294] Wasm @breakpoint: emit unreachable This should improve the developer debugging experience. --- src/arch/wasm/CodeGen.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index d388bc8fab..dbabb436c8 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3298,6 +3298,7 @@ fn airTrap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { fn airBreakpoint(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { // unsupported by wasm itfunc. Can be implemented once we support DWARF // for wasm + try func.addTag(.@"unreachable"); func.finishAir(inst, .none, &.{}); } From 2cf27c571880a607401dca181f8103e855d0c46d Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 4 Mar 2023 02:11:04 -0500 Subject: [PATCH 015/294] llvm: fix incorrectly annotated DIType Closes #14715 Closes #14783 --- src/codegen/llvm.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6f240b88f5..937c1cf120 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1773,7 +1773,7 @@ pub const Object = struct { if (ty.optionalReprIsPayload()) { const ptr_di_ty = try o.lowerDebugType(child_ty, resolve); // The recursive call to `lowerDebugType` means we can't use `gop` anymore. - try o.di_type_map.putContext(gpa, ty, AnnotatedDITypePtr.initFull(ptr_di_ty), .{ .mod = o.module }); + try o.di_type_map.putContext(gpa, ty, AnnotatedDITypePtr.init(ptr_di_ty, resolve), .{ .mod = o.module }); return ptr_di_ty; } From 010596c93054543c3c218e7d4b045d5e46384dab Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sat, 4 Mar 2023 12:51:16 +0100 Subject: [PATCH 016/294] AstGen: compile-error on primitive value export Fixes #14778 Co-authored-by: Veikka Tuominen --- src/AstGen.zig | 5 +++- .../exporting_primitive_values.zig | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/exporting_primitive_values.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 41a8ccadb2..8e3f11df76 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7976,6 +7976,9 @@ fn builtinCall( switch (node_tags[params[0]]) { .identifier => { const ident_token = main_tokens[params[0]]; + if (isPrimitive(tree.tokenSlice(ident_token))) { + return astgen.failTok(ident_token, "unable to export primitive value", .{}); + } decl_name = try astgen.identAsString(ident_token); var s = scope; @@ -8988,7 +8991,7 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{ }); comptime { - // These checks ensure that std.zig.primitives stays in synce with the primitive->Zir map. + // These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map. const primitives = std.zig.primitives; for (primitive_instrs.kvs) |kv| { if (!primitives.isPrimitive(kv.key)) { diff --git a/test/cases/compile_errors/exporting_primitive_values.zig b/test/cases/compile_errors/exporting_primitive_values.zig new file mode 100644 index 0000000000..bf3c38a553 --- /dev/null +++ b/test/cases/compile_errors/exporting_primitive_values.zig @@ -0,0 +1,29 @@ +pub export fn entry1() void { + @export(u100, .{ .name = "a" }); +} +pub export fn entry3() void { + @export(undefined, .{ .name = "b" }); +} +pub export fn entry4() void { + @export(null, .{ .name = "c" }); +} +pub export fn entry5() void { + @export(false, .{ .name = "d" }); +} +pub export fn entry6() void { + @export(u8, .{ .name = "e" }); +} +pub export fn entry7() void { + @export(u65535, .{ .name = "f" }); +} + +// error +// backend=llvm +// target=native +// +// :2:13: error: unable to export primitive value +// :5:13: error: unable to export primitive value +// :8:13: error: unable to export primitive value +// :11:13: error: unable to export primitive value +// :14:13: error: unable to export primitive value +// :17:13: error: unable to export primitive value From 16302578d5a0ca226c7db76bc8e39574dea1dc1d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Mar 2023 14:04:58 -0700 Subject: [PATCH 017/294] add behavior test case for previous commit --- test/behavior/slice.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 435e1887bb..ed5e2a721d 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -747,3 +747,18 @@ test "slice decays to many pointer" { const p: [*:0]const u8 = buf[0..7 :0]; try expectEqualStrings(buf[0..7], std.mem.span(p)); } + +test "write through pointer to optional slice arg" { + const S = struct { + fn bar(foo: *?[]const u8) !void { + foo.* = try baz(); + } + + fn baz() ![]const u8 { + return "ok"; + } + }; + var foo: ?[]const u8 = null; + try S.bar(&foo); + try expectEqualStrings(foo.?, "ok"); +} From c9d990d79083f117564837f762c3e225d7fbc5cf Mon Sep 17 00:00:00 2001 From: tranquillity-codes Date: Sat, 4 Mar 2023 10:10:07 +0100 Subject: [PATCH 018/294] fix doc Build Mode --- doc/langref.html.in | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index e016ef13f8..71d99b3aae 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9565,9 +9565,10 @@ pub fn build(b: *std.Build) void { This causes these options to be available:

-
-Drelease-safe=[bool]
Optimizations on and safety on
-
-Drelease-fast=[bool]
Optimizations on and safety off
-
-Drelease-small=[bool]
Size optimizations on and safety off
+
-Doptimize=Debug
Optimizations off and safety on (default)
+
-Doptimize=ReleaseSafe
Optimizations on and safety on
+
-Doptimize=ReleaseFast
Optimizations on and safety off
+
-Doptimize=ReleaseSmall
Size optimizations on and safety off
{#header_open|Debug#} {#shell_samp#}$ zig build-exe example.zig{#end_shell_samp#} From 874ae81f1b2ae76cea6f5c79203f4baa68263163 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 3 Mar 2023 00:18:34 -0500 Subject: [PATCH 019/294] CBE: implement big integer literals --- lib/std/math/big/int.zig | 1 + lib/zig.h | 12 +- src/codegen/c.zig | 361 +++++++++++++++++++------------------- src/codegen/c/type.zig | 348 ++++++++++++++++++++++++++++++++---- test/behavior/bitcast.zig | 1 - 5 files changed, 502 insertions(+), 221 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index b7725b9ae9..4e4e7c489e 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -1674,6 +1674,7 @@ pub const Mutable = struct { /// If a is positive, this passes through to truncate. /// If a is negative, then r is set to positive with the bit pattern ~(a - 1). + /// r may alias a. /// /// Asserts `r` has enough storage to store the result. /// The upper bound is `calcTwosCompLimbCount(a.len)`. diff --git a/lib/zig.h b/lib/zig.h index f3ad7db8a1..7353ea935d 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -1360,8 +1360,8 @@ typedef signed __int128 zig_i128; #define zig_make_u128(hi, lo) ((zig_u128)(hi)<<64|(lo)) #define zig_make_i128(hi, lo) ((zig_i128)zig_make_u128(hi, lo)) -#define zig_make_constant_u128(hi, lo) zig_make_u128(hi, lo) -#define zig_make_constant_i128(hi, lo) zig_make_i128(hi, lo) +#define zig_init_u128(hi, lo) zig_make_u128(hi, lo) +#define zig_init_i128(hi, lo) zig_make_i128(hi, lo) #define zig_hi_u128(val) ((uint64_t)((val) >> 64)) #define zig_lo_u128(val) ((uint64_t)((val) >> 0)) #define zig_hi_i128(val) (( int64_t)((val) >> 64)) @@ -1391,11 +1391,11 @@ typedef struct { zig_align(16) int64_t hi; uint64_t lo; } zig_i128; #define zig_make_i128(hi, lo) ((zig_i128){ .h##i = (hi), .l##o = (lo) }) #if _MSC_VER /* MSVC doesn't allow struct literals in constant expressions */ -#define zig_make_constant_u128(hi, lo) { .h##i = (hi), .l##o = (lo) } -#define zig_make_constant_i128(hi, lo) { .h##i = (hi), .l##o = (lo) } +#define zig_init_u128(hi, lo) { .h##i = (hi), .l##o = (lo) } +#define zig_init_i128(hi, lo) { .h##i = (hi), .l##o = (lo) } #else /* But non-MSVC doesn't like the unprotected commas */ -#define zig_make_constant_u128(hi, lo) zig_make_u128(hi, lo) -#define zig_make_constant_i128(hi, lo) zig_make_i128(hi, lo) +#define zig_init_u128(hi, lo) zig_make_u128(hi, lo) +#define zig_init_i128(hi, lo) zig_make_i128(hi, lo) #endif #define zig_hi_u128(val) ((val).hi) #define zig_lo_u128(val) ((val).lo) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c0585c3a4a..addd3c8332 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -449,7 +449,7 @@ pub const Function = struct { } fn fmtIntLiteral(f: *Function, ty: Type, val: Value) !std.fmt.Formatter(formatIntLiteral) { - return f.object.dg.fmtIntLiteral(ty, val); + return f.object.dg.fmtIntLiteral(ty, val, .Other); } fn getLazyFnName(f: *Function, key: LazyFnKey, data: LazyFnValue.Data) ![]const u8 { @@ -574,9 +574,9 @@ pub const DeclGen = struct { const len_val = Value.initPayload(&len_pl.base); if (location == .StaticInitializer) { - return writer.print(", {} }}", .{try dg.fmtIntLiteral(Type.usize, len_val)}); + return writer.print(", {} }}", .{try dg.fmtIntLiteral(Type.usize, len_val, .Other)}); } else { - return writer.print(", .len = {} }}", .{try dg.fmtIntLiteral(Type.usize, len_val)}); + return writer.print(", .len = {} }}", .{try dg.fmtIntLiteral(Type.usize, len_val, .Other)}); } } @@ -606,7 +606,7 @@ pub const DeclGen = struct { try writer.writeByte(')'); } switch (ptr_val.tag()) { - .int_u64, .one => try writer.print("{x}", .{try dg.fmtIntLiteral(Type.usize, ptr_val)}), + .int_u64, .one => try writer.print("{x}", .{try dg.fmtIntLiteral(Type.usize, ptr_val, .Other)}), .decl_ref_mut, .decl_ref, .variable => { const decl_index = switch (ptr_val.tag()) { .decl_ref => ptr_val.castTag(.decl_ref).?.data, @@ -670,7 +670,9 @@ pub const DeclGen = struct { container_ptr_ty, location, ); - try writer.print(" + {})", .{try dg.fmtIntLiteral(Type.usize, byte_offset_val)}); + try writer.print(" + {})", .{ + try dg.fmtIntLiteral(Type.usize, byte_offset_val, .Other), + }); }, .end => { try writer.writeAll("(("); @@ -680,7 +682,9 @@ pub const DeclGen = struct { container_ptr_ty, location, ); - try writer.print(") + {})", .{try dg.fmtIntLiteral(Type.usize, Value.one)}); + try writer.print(") + {})", .{ + try dg.fmtIntLiteral(Type.usize, Value.one, .Other), + }); }, } }, @@ -746,7 +750,7 @@ pub const DeclGen = struct { return writer.writeAll("false"); } }, - .Int, .Enum, .ErrorSet => return writer.print("{x}", .{try dg.fmtIntLiteralLoc(ty, val, location)}), + .Int, .Enum, .ErrorSet => return writer.print("{x}", .{try dg.fmtIntLiteral(ty, val, location)}), .Float => { const bits = ty.floatBits(target); var int_pl = Type.Payload.Bits{ .base = .{ .tag = .int_signed }, .data = bits }; @@ -780,11 +784,11 @@ pub const DeclGen = struct { var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_ty = ty.slicePtrFieldType(&buf); try dg.renderType(writer, ptr_ty); - return writer.print("){x}, {0x}}}", .{try dg.fmtIntLiteral(Type.usize, val)}); + return writer.print("){x}, {0x}}}", .{try dg.fmtIntLiteral(Type.usize, val, .Other)}); } else { try writer.writeAll("(("); try dg.renderType(writer, ty); - return writer.print("){x})", .{try dg.fmtIntLiteral(Type.usize, val)}); + return writer.print("){x})", .{try dg.fmtIntLiteral(Type.usize, val, .Other)}); }, .Optional => { var opt_buf: Type.Payload.ElemType = undefined; @@ -831,7 +835,7 @@ pub const DeclGen = struct { return writer.writeByte('}'); }, - .Packed => return writer.print("{x}", .{try dg.fmtIntLiteral(ty, Value.undef)}), + .Packed => return writer.print("{x}", .{try dg.fmtIntLiteral(ty, Value.undef, .Other)}), }, .Union => { if (!location.isInitializer()) { @@ -854,7 +858,7 @@ pub const DeclGen = struct { if (!field.ty.hasRuntimeBits()) continue; try dg.renderValue(writer, field.ty, val, initializer_type); break; - } else try writer.print("{x}", .{try dg.fmtIntLiteral(Type.u8, Value.undef)}); + } else try writer.print("{x}", .{try dg.fmtIntLiteral(Type.u8, Value.undef, .Other)}); if (ty.unionTagTypeSafety()) |_| try writer.writeByte('}'); return writer.writeByte('}'); }, @@ -868,7 +872,7 @@ pub const DeclGen = struct { try writer.writeAll("{ .payload = "); try dg.renderValue(writer, ty.errorUnionPayload(), val, initializer_type); return writer.print(", .error = {x} }}", .{ - try dg.fmtIntLiteral(ty.errorUnionSet(), val), + try dg.fmtIntLiteral(ty.errorUnionSet(), val, .Other), }); }, .Array, .Vector => { @@ -927,7 +931,7 @@ pub const DeclGen = struct { .decl_ref_mut, .decl_ref, => try dg.renderParentPtr(writer, val, ty, location), - else => try writer.print("{}", .{try dg.fmtIntLiteralLoc(ty, val, location)}), + else => try writer.print("{}", .{try dg.fmtIntLiteral(ty, val, location)}), }, .Float => { const bits = ty.floatBits(target); @@ -1020,7 +1024,7 @@ pub const DeclGen = struct { try writer.writeAll(", "); empty = false; } - try writer.print("{x}", .{try dg.fmtIntLiteralLoc(int_ty, int_val, location)}); + try writer.print("{x}", .{try dg.fmtIntLiteral(int_ty, int_val, location)}); if (!empty) try writer.writeByte(')'); return; }, @@ -1069,7 +1073,7 @@ pub const DeclGen = struct { .int_u64, .one => { try writer.writeAll("(("); try dg.renderType(writer, ty); - return writer.print("){x})", .{try dg.fmtIntLiteral(Type.usize, val)}); + return writer.print("){x})", .{try dg.fmtIntLiteral(Type.usize, val, .Other)}); }, .field_ptr, .elem_ptr, @@ -1889,11 +1893,11 @@ pub const DeclGen = struct { const int_info = ty.intInfo(target); if (int_info.signedness == .signed) { const min_val = try ty.minInt(stack.get(), target); - try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, min_val)}); + try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, min_val, .Other)}); } const max_val = try ty.maxInt(stack.get(), target); - try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, max_val)}); + try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, max_val, .Other)}); }, .Bits => { var bits_pl = Value.Payload.U64{ @@ -1901,7 +1905,7 @@ pub const DeclGen = struct { .data = ty.bitSize(target), }; const bits_val = Value.initPayload(&bits_pl.base); - try writer.print(", {}", .{try dg.fmtIntLiteral(Type.u8, bits_val)}); + try writer.print(", {}", .{try dg.fmtIntLiteral(Type.u8, bits_val, .Other)}); }, } } @@ -1910,30 +1914,21 @@ pub const DeclGen = struct { dg: *DeclGen, ty: Type, val: Value, + loc: ValueRenderLocation, ) !std.fmt.Formatter(formatIntLiteral) { - const int_info = ty.intInfo(dg.module.getTarget()); - const c_bits = toCIntBits(int_info.bits); - if (c_bits == null or c_bits.? > 128) - return dg.fail("TODO implement integer constants larger than 128 bits", .{}); + const kind: CType.Kind = switch (loc) { + .FunctionArgument => .parameter, + .Initializer, .Other => .complete, + .StaticInitializer => .global, + }; return std.fmt.Formatter(formatIntLiteral){ .data = .{ - .ty = ty, + .dg = dg, + .int_info = ty.intInfo(dg.module.getTarget()), + .kind = kind, + .cty = try dg.typeToCType(ty, kind), .val = val, - .mod = dg.module, } }; } - - fn fmtIntLiteralLoc( - dg: *DeclGen, - ty: Type, - val: Value, - location: ValueRenderLocation, // TODO: Instead add this as optional arg to fmtIntLiteral - ) !std.fmt.Formatter(formatIntLiteral) { - const int_info = ty.intInfo(dg.module.getTarget()); - const c_bits = toCIntBits(int_info.bits); - if (c_bits == null or c_bits.? > 128) - return dg.fail("TODO implement integer constants larger than 128 bits", .{}); - return std.fmt.Formatter(formatIntLiteral){ .data = .{ .ty = ty, .val = val, .mod = dg.module, .location = location } }; - } }; const CTypeFix = enum { prefix, suffix }; @@ -2450,7 +2445,7 @@ pub fn genErrDecls(o: *Object) !void { const len_val = Value.initPayload(&len_pl.base); try writer.print("{{" ++ name_prefix ++ "{}, {}}}", .{ - fmtIdent(name), try o.dg.fmtIntLiteral(Type.usize, len_val), + fmtIdent(name), try o.dg.fmtIntLiteral(Type.usize, len_val, .Other), }); } try writer.writeAll("};\n"); @@ -2501,7 +2496,10 @@ pub fn genLazyFn(o: *Object, lazy_fn: LazyFnMap.Entry) !void { var int_pl: Value.Payload.U64 = undefined; const int_val = tag_val.enumToInt(enum_ty, &int_pl); - var name_ty_pl = Type.Payload.Len{ .base = .{ .tag = .array_u8_sentinel_0 }, .data = name.len }; + var name_ty_pl = Type.Payload.Len{ + .base = .{ .tag = .array_u8_sentinel_0 }, + .data = name.len, + }; const name_ty = Type.initPayload(&name_ty_pl.base); var name_pl = Value.Payload.Bytes{ .base = .{ .tag = .bytes }, .data = name }; @@ -2510,14 +2508,16 @@ pub fn genLazyFn(o: *Object, lazy_fn: LazyFnMap.Entry) !void { var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = name.len }; const len_val = Value.initPayload(&len_pl.base); - try w.print(" case {}: {{\n static ", .{try o.dg.fmtIntLiteral(enum_ty, int_val)}); + try w.print(" case {}: {{\n static ", .{ + try o.dg.fmtIntLiteral(enum_ty, int_val, .Other), + }); try o.dg.renderTypeAndName(w, name_ty, .{ .identifier = "name" }, Const, 0, .complete); try w.writeAll(" = "); try o.dg.renderValue(w, name_ty, name_val, .Initializer); try w.writeAll(";\n return ("); try o.dg.renderType(w, name_slice_ty); try w.print("){{{}, {}}};\n", .{ - fmtIdent("name"), try o.dg.fmtIntLiteral(Type.usize, len_val), + fmtIdent("name"), try o.dg.fmtIntLiteral(Type.usize, len_val, .Other), }); try w.writeAll(" }\n"); @@ -2535,7 +2535,12 @@ pub fn genLazyFn(o: *Object, lazy_fn: LazyFnMap.Entry) !void { const fwd_decl_writer = o.dg.fwd_decl.writer(); try fwd_decl_writer.print("static zig_{s} ", .{@tagName(key)}); - try o.dg.renderFunctionSignature(fwd_decl_writer, fn_decl_index, .forward, .{ .string = fn_name }); + try o.dg.renderFunctionSignature( + fwd_decl_writer, + fn_decl_index, + .forward, + .{ .string = fn_name }, + ); try fwd_decl_writer.writeAll(";\n"); try w.print("static zig_{s} ", .{@tagName(key)}); @@ -7177,30 +7182,33 @@ fn undefPattern(comptime IntType: type) IntType { return @bitCast(IntType, @as(UnsignedType, (1 << (int_info.bits | 1)) / 3)); } -const FormatIntLiteralContext = struct { ty: Type, val: Value, mod: *Module, location: ?ValueRenderLocation = null }; +const FormatIntLiteralContext = struct { + dg: *DeclGen, + int_info: std.builtin.Type.Int, + kind: CType.Kind, + cty: CType, + val: Value, +}; fn formatIntLiteral( data: FormatIntLiteralContext, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { - const target = data.mod.getTarget(); - const int_info = data.ty.intInfo(target); + const target = data.dg.module.getTarget(); const ExpectedContents = struct { const base = 10; - const limbs_count_128 = BigInt.calcTwosCompLimbCount(128); - const expected_needed_limbs_count = BigInt.calcToStringLimbsBufferLen(limbs_count_128, base); - const worst_case_int = BigInt.Const{ - .limbs = &([1]BigIntLimb{std.math.maxInt(BigIntLimb)} ** expected_needed_limbs_count), - .positive = false, - }; + const bits = 128; + const limbs_count = BigInt.calcTwosCompLimbCount(bits); - undef_limbs: [limbs_count_128]BigIntLimb, - wrap_limbs: [limbs_count_128]BigIntLimb, + undef_limbs: [limbs_count]BigIntLimb, + wrap_limbs: [limbs_count]BigIntLimb, + to_string_buf: [bits]u8, + to_string_limbs: [BigInt.calcToStringLimbsBufferLen(limbs_count, base)]BigIntLimb, }; var stack align(@alignOf(ExpectedContents)) = - std.heap.stackFallback(@sizeOf(ExpectedContents), data.mod.gpa); + std.heap.stackFallback(@sizeOf(ExpectedContents), data.dg.gpa); const allocator = stack.get(); var undef_limbs: []BigIntLimb = &.{}; @@ -7208,7 +7216,7 @@ fn formatIntLiteral( var int_buf: Value.BigIntSpace = undefined; const int = if (data.val.isUndefDeep()) blk: { - undef_limbs = try allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(int_info.bits)); + undef_limbs = try allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(data.int_info.bits)); std.mem.set(BigIntLimb, undef_limbs, undefPattern(BigIntLimb)); var undef_int = BigInt.Mutable{ @@ -7216,163 +7224,150 @@ fn formatIntLiteral( .len = undef_limbs.len, .positive = true, }; - undef_int.truncate(undef_int.toConst(), int_info.signedness, int_info.bits); + undef_int.truncate(undef_int.toConst(), data.int_info.signedness, data.int_info.bits); break :blk undef_int.toConst(); } else data.val.toBigInt(&int_buf, target); - assert(int.fitsInTwosComp(int_info.signedness, int_info.bits)); + assert(int.fitsInTwosComp(data.int_info.signedness, data.int_info.bits)); - const c_bits = toCIntBits(int_info.bits) orelse unreachable; + const c_bits = @intCast(usize, data.cty.byteSize(data.dg.ctypes.set, target) * 8); var one_limbs: [BigInt.calcLimbLen(1)]BigIntLimb = undefined; const one = BigInt.Mutable.init(&one_limbs, 1).toConst(); - const wrap_limbs = try allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(c_bits)); - defer allocator.free(wrap_limbs); - var wrap = BigInt.Mutable{ .limbs = wrap_limbs, .len = undefined, .positive = undefined }; - if (wrap.addWrap(int, one, int_info.signedness, c_bits) or - int_info.signedness == .signed and wrap.subWrap(int, one, int_info.signedness, c_bits)) - { - const abbrev = switch (data.ty.tag()) { - .c_short, .c_ushort => "SHRT", - .c_int, .c_uint => "INT", - .c_long, .c_ulong => "LONG", - .c_longlong, .c_ulonglong => "LLONG", - .isize, .usize => "INTPTR", - else => return writer.print("zig_{s}Int_{c}{d}", .{ - if (int.positive) "max" else "min", signAbbrev(int_info.signedness), c_bits, + var wrap = BigInt.Mutable{ + .limbs = try allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(c_bits)), + .len = undefined, + .positive = undefined, + }; + defer allocator.free(wrap.limbs); + if (wrap.addWrap(int, one, data.int_info.signedness, c_bits) or + data.int_info.signedness == .signed and wrap.subWrap(int, one, data.int_info.signedness, c_bits)) + return writer.print("{s}_{s}", .{ + data.cty.getStandardDefineAbbrev() orelse return writer.print("zig_{s}Int_{c}{d}", .{ + if (int.positive) "max" else "min", signAbbrev(data.int_info.signedness), c_bits, }), - }; - if (int_info.signedness == .unsigned) try writer.writeByte('U'); - return writer.print("{s}_{s}", .{ abbrev, if (int.positive) "MAX" else "MIN" }); - } + if (int.positive) "MAX" else "MIN", + }); - var use_twos_comp = false; - if (!int.positive) { - if (c_bits > 64) { - // TODO: Can this be done for decimal literals as well? - if (fmt.len == 1 and fmt[0] != 'd') { - use_twos_comp = true; - } else { - // TODO: Use fmtIntLiteral for 0? - try writer.print("zig_sub_{c}{d}(zig_make_{c}{d}(0, 0), ", .{ signAbbrev(int_info.signedness), c_bits, signAbbrev(int_info.signedness), c_bits }); - } - } else { - try writer.writeByte('-'); - } - } - - switch (data.ty.tag()) { - .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong => {}, - else => { - if (int_info.bits <= 64) { - try writer.print("{s}INT{d}_C(", .{ switch (int_info.signedness) { - .signed => "", - .unsigned => "U", - }, c_bits }); - } else if (data.location != null and data.location.? == .StaticInitializer) { - // MSVC treats casting the struct initializer as not constant (C2099), so an alternate form is used in global initializers - try writer.print("zig_make_constant_{c}{d}(", .{ signAbbrev(int_info.signedness), c_bits }); - } else { - try writer.print("zig_make_{c}{d}(", .{ signAbbrev(int_info.signedness), c_bits }); - } + const c_limb_info: struct { + cty: CType, + count: usize, + endian: std.builtin.Endian, + homogeneous: bool, + } = switch (data.cty.tag()) { + else => .{ + .cty = CType.initTag(.void), + .count = 1, + .endian = .Little, + .homogeneous = true, }, - } + .zig_u128, .zig_i128 => .{ + .cty = CType.initTag(.uint64_t), + .count = 2, + .endian = .Big, + .homogeneous = false, + }, + .array => info: { + const array_data = data.cty.castTag(.array).?.data; + break :info .{ + .cty = data.dg.indexToCType(array_data.elem_type), + .count = @intCast(usize, array_data.len), + .endian = target.cpu.arch.endian(), + .homogeneous = true, + }; + }, + }; + if (c_limb_info.count == 1) { + if (!int.positive) try writer.writeByte('-'); + try data.cty.renderLiteralPrefix(writer, data.kind); - const limbs_count_64 = @divExact(64, @bitSizeOf(BigIntLimb)); - if (c_bits <= 64) { - var base: u8 = undefined; - var case: std.fmt.Case = undefined; - switch (fmt.len) { - 0 => base = 10, + const style: struct { base: u8, case: std.fmt.Case = undefined } = switch (fmt.len) { + 0 => .{ .base = 10 }, 1 => switch (fmt[0]) { - 'b' => { - base = 2; + 'b' => style: { try writer.writeAll("0b"); + break :style .{ .base = 2 }; }, - 'o' => { - base = 8; + 'o' => style: { try writer.writeByte('0'); + break :style .{ .base = 8 }; }, - 'd' => base = 10, - 'x' => { - base = 16; - case = .lower; - try writer.writeAll("0x"); - }, - 'X' => { - base = 16; - case = .upper; + 'd' => .{ .base = 10 }, + 'x', 'X' => |base| style: { try writer.writeAll("0x"); + break :style .{ .base = 16, .case = switch (base) { + 'x' => .lower, + 'X' => .upper, + else => unreachable, + } }; }, else => @compileError("Invalid fmt: " ++ fmt), }, else => @compileError("Invalid fmt: " ++ fmt), - } + }; - var str: [64]u8 = undefined; - var limbs_buf: [BigInt.calcToStringLimbsBufferLen(limbs_count_64, 10)]BigIntLimb = undefined; - try writer.writeAll(str[0..int.abs().toString(&str, base, case, &limbs_buf)]); + const string = try int.abs().toStringAlloc(allocator, style.base, style.case); + defer allocator.free(string); + try writer.writeAll(string); } else { - assert(c_bits == 128); - const split = std.math.min(int.limbs.len, limbs_count_64); - var twos_comp_limbs: [BigInt.calcTwosCompLimbCount(128)]BigIntLimb = undefined; + try data.cty.renderLiteralPrefix(writer, data.kind); + wrap.convertToTwosComplement(int, .unsigned, data.int_info.bits); + std.mem.set(BigIntLimb, wrap.limbs[wrap.len..], 0); + wrap.len = wrap.limbs.len; + const limbs_per_c_limb = @divExact(wrap.len, c_limb_info.count); - // Adding a negation in the C code before the doesn't work in all cases: - // - struct versions would require an extra zig_sub_ call to negate, which wouldn't work in constant expressions - // - negating the f80 int representation (i128) doesn't make sense - // Instead we write out the literal as a negative number in twos complement - var limbs = int.limbs; + var c_limb_int_info = std.builtin.Type.Int{ + .signedness = undefined, + .bits = @intCast(u16, @divExact(c_bits, c_limb_info.count)), + }; + var c_limb_cty: CType = undefined; - if (use_twos_comp) { - var twos_comp = BigInt.Mutable{ - .limbs = &twos_comp_limbs, - .positive = undefined, + var limb_offset: usize = 0; + const most_significant_limb_i = wrap.len - limbs_per_c_limb; + while (limb_offset < wrap.len) : (limb_offset += limbs_per_c_limb) { + const limb_i = switch (c_limb_info.endian) { + .Little => limb_offset, + .Big => most_significant_limb_i - limb_offset, + }; + var c_limb_mut = BigInt.Mutable{ + .limbs = wrap.limbs[limb_i..][0..limbs_per_c_limb], .len = undefined, + .positive = true, + }; + c_limb_mut.normalize(limbs_per_c_limb); + + if (limb_i == most_significant_limb_i and + !c_limb_info.homogeneous and data.int_info.signedness == .signed) + { + // most significant limb is actually signed + c_limb_int_info.signedness = .signed; + c_limb_cty = c_limb_info.cty.toSigned(); + + c_limb_mut.positive = wrap.positive; + c_limb_mut.convertToTwosComplement( + c_limb_mut.toConst(), + .signed, + data.int_info.bits - limb_i * @bitSizeOf(BigIntLimb), + ); + } else { + c_limb_int_info.signedness = .unsigned; + c_limb_cty = c_limb_info.cty; + } + var c_limb_val_pl = Value.Payload.BigInt{ + .base = .{ .tag = if (c_limb_mut.positive) .int_big_positive else .int_big_negative }, + .data = c_limb_mut.limbs[0..c_limb_mut.len], }; - twos_comp.convertToTwosComplement(int, .signed, int_info.bits); - limbs = twos_comp.limbs; + if (limb_offset > 0) try writer.writeAll(", "); + try formatIntLiteral(.{ + .dg = data.dg, + .int_info = c_limb_int_info, + .kind = data.kind, + .cty = c_limb_cty, + .val = Value.initPayload(&c_limb_val_pl.base), + }, fmt, options, writer); } - - var upper_pl = Value.Payload.BigInt{ - .base = .{ .tag = .int_big_positive }, - .data = limbs[split..], - }; - const upper_val = Value.initPayload(&upper_pl.base); - try formatIntLiteral(.{ - .ty = switch (int_info.signedness) { - .unsigned => Type.u64, - .signed => if (use_twos_comp) Type.u64 else Type.i64, - }, - .val = upper_val, - .mod = data.mod, - }, fmt, options, writer); - - try writer.writeAll(", "); - - var lower_pl = Value.Payload.BigInt{ - .base = .{ .tag = .int_big_positive }, - .data = limbs[0..split], - }; - const lower_val = Value.initPayload(&lower_pl.base); - try formatIntLiteral(.{ - .ty = Type.u64, - .val = lower_val, - .mod = data.mod, - }, fmt, options, writer); - - if (!int.positive and c_bits > 64 and !use_twos_comp) try writer.writeByte(')'); - return writer.writeByte(')'); - } - - switch (data.ty.tag()) { - .c_short, .c_ushort, .c_int => {}, - .c_uint => try writer.writeAll("u"), - .c_long => try writer.writeAll("l"), - .c_ulong => try writer.writeAll("ul"), - .c_longlong => try writer.writeAll("ll"), - .c_ulonglong => try writer.writeAll("ull"), - else => try writer.writeByte(')'), } + try data.cty.renderLiteralSuffix(writer); } fn isByRef(ty: Type) bool { diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig index 1f1a220cd2..a1b11df315 100644 --- a/src/codegen/c/type.zig +++ b/src/codegen/c/type.zig @@ -496,6 +496,296 @@ pub const CType = extern union { } }; + pub fn toSigned(self: CType) CType { + return CType.initTag(switch (self.tag()) { + .char, .@"signed char", .@"unsigned char" => .@"signed char", + .short, .@"unsigned short" => .short, + .int, .@"unsigned int" => .int, + .long, .@"unsigned long" => .long, + .@"long long", .@"unsigned long long" => .@"long long", + .size_t, .ptrdiff_t => .ptrdiff_t, + .uint8_t, .int8_t => .int8_t, + .uint16_t, .int16_t => .int16_t, + .uint32_t, .int32_t => .int32_t, + .uint64_t, .int64_t => .int64_t, + .uintptr_t, .intptr_t => .intptr_t, + .zig_u128, .zig_i128 => .zig_i128, + .float, + .double, + .@"long double", + .zig_f16, + .zig_f32, + .zig_f80, + .zig_f128, + .zig_c_longdouble, + => |t| t, + else => unreachable, + }); + } + + pub fn toUnsigned(self: CType) CType { + return CType.initTag(switch (self.tag()) { + .char, .@"signed char", .@"unsigned char" => .@"unsigned char", + .short, .@"unsigned short" => .@"unsigned short", + .int, .@"unsigned int" => .@"unsigned int", + .long, .@"unsigned long" => .@"unsigned long", + .@"long long", .@"unsigned long long" => .@"unsigned long long", + .size_t, .ptrdiff_t => .size_t, + .uint8_t, .int8_t => .uint8_t, + .uint16_t, .int16_t => .uint16_t, + .uint32_t, .int32_t => .uint32_t, + .uint64_t, .int64_t => .uint64_t, + .uintptr_t, .intptr_t => .uintptr_t, + .zig_u128, .zig_i128 => .zig_u128, + else => unreachable, + }); + } + + pub fn getStandardDefineAbbrev(self: CType) ?[]const u8 { + return switch (self.tag()) { + .char => "CHAR", + .@"signed char" => "SCHAR", + .short => "SHRT", + .int => "INT", + .long => "LONG", + .@"long long" => "LLONG", + .@"unsigned char" => "UCHAR", + .@"unsigned short" => "USHRT", + .@"unsigned int" => "UINT", + .@"unsigned long" => "ULONG", + .@"unsigned long long" => "ULLONG", + .float => "FLT", + .double => "DBL", + .@"long double" => "LDBL", + .size_t => "SIZE", + .ptrdiff_t => "PTRDIFF", + .uint8_t => "UINT8", + .int8_t => "INT8", + .uint16_t => "UINT16", + .int16_t => "INT16", + .uint32_t => "UINT32", + .int32_t => "INT32", + .uint64_t => "UINT64", + .int64_t => "INT64", + .uintptr_t => "UINTPTR", + .intptr_t => "INTPTR", + else => null, + }; + } + + pub fn renderLiteralPrefix(self: CType, writer: anytype, kind: Kind) @TypeOf(writer).Error!void { + switch (self.tag()) { + .void => unreachable, + ._Bool, + .char, + .@"signed char", + .short, + .@"unsigned short", + .bool, + .size_t, + .ptrdiff_t, + .uintptr_t, + .intptr_t, + => |t| switch (kind) { + else => try writer.print("({s})", .{@tagName(t)}), + .global => {}, + }, + .int, + .long, + .@"long long", + .@"unsigned char", + .@"unsigned int", + .@"unsigned long", + .@"unsigned long long", + .float, + .double, + .@"long double", + => {}, + .uint8_t, + .int8_t, + .uint16_t, + .int16_t, + .uint32_t, + .int32_t, + .uint64_t, + .int64_t, + => try writer.print("{s}_C(", .{self.getStandardDefineAbbrev().?}), + .zig_u128, + .zig_i128, + .zig_f16, + .zig_f32, + .zig_f64, + .zig_f80, + .zig_f128, + .zig_c_longdouble, + => |t| try writer.print("zig_{s}_{s}(", .{ + switch (kind) { + else => "make", + .global => "init", + }, + @tagName(t)["zig_".len..], + }), + .pointer, + .pointer_const, + .pointer_volatile, + .pointer_const_volatile, + => unreachable, + .array, + .vector, + => try writer.writeByte('{'), + .fwd_anon_struct, + .fwd_anon_union, + .fwd_struct, + .fwd_union, + .unnamed_struct, + .unnamed_union, + .packed_unnamed_struct, + .packed_unnamed_union, + .anon_struct, + .anon_union, + .@"struct", + .@"union", + .packed_struct, + .packed_union, + .function, + .varargs_function, + => unreachable, + } + } + + pub fn renderLiteralSuffix(self: CType, writer: anytype) @TypeOf(writer).Error!void { + switch (self.tag()) { + .void => unreachable, + ._Bool => {}, + .char, + .@"signed char", + .short, + .int, + => {}, + .long => try writer.writeByte('l'), + .@"long long" => try writer.writeAll("ll"), + .@"unsigned char", + .@"unsigned short", + .@"unsigned int", + => try writer.writeByte('u'), + .@"unsigned long", + .size_t, + .uintptr_t, + => try writer.writeAll("ul"), + .@"unsigned long long" => try writer.writeAll("ull"), + .float => try writer.writeByte('f'), + .double => {}, + .@"long double" => try writer.writeByte('l'), + .bool, + .ptrdiff_t, + .intptr_t, + => {}, + .uint8_t, + .int8_t, + .uint16_t, + .int16_t, + .uint32_t, + .int32_t, + .uint64_t, + .int64_t, + .zig_u128, + .zig_i128, + .zig_f16, + .zig_f32, + .zig_f64, + .zig_f80, + .zig_f128, + .zig_c_longdouble, + => try writer.writeByte(')'), + .pointer, + .pointer_const, + .pointer_volatile, + .pointer_const_volatile, + => unreachable, + .array, + .vector, + => try writer.writeByte('}'), + .fwd_anon_struct, + .fwd_anon_union, + .fwd_struct, + .fwd_union, + .unnamed_struct, + .unnamed_union, + .packed_unnamed_struct, + .packed_unnamed_union, + .anon_struct, + .anon_union, + .@"struct", + .@"union", + .packed_struct, + .packed_union, + .function, + .varargs_function, + => unreachable, + } + } + + pub fn byteSize(self: CType, store: Store.Set, target: Target) u64 { + return switch (self.tag()) { + .void => 0, + .char, .@"signed char", ._Bool, .@"unsigned char", .bool, .uint8_t, .int8_t => 1, + .short => target.c_type_byte_size(.short), + .int => target.c_type_byte_size(.int), + .long => target.c_type_byte_size(.long), + .@"long long" => target.c_type_byte_size(.longlong), + .@"unsigned short" => target.c_type_byte_size(.ushort), + .@"unsigned int" => target.c_type_byte_size(.uint), + .@"unsigned long" => target.c_type_byte_size(.ulong), + .@"unsigned long long" => target.c_type_byte_size(.ulonglong), + .float => target.c_type_byte_size(.float), + .double => target.c_type_byte_size(.double), + .@"long double" => target.c_type_byte_size(.longdouble), + .size_t, + .ptrdiff_t, + .uintptr_t, + .intptr_t, + .pointer, + .pointer_const, + .pointer_volatile, + .pointer_const_volatile, + => @divExact(target.cpu.arch.ptrBitWidth(), 8), + .uint16_t, .int16_t, .zig_f16 => 2, + .uint32_t, .int32_t, .zig_f32 => 4, + .uint64_t, .int64_t, .zig_f64 => 8, + .zig_u128, .zig_i128, .zig_f128 => 16, + .zig_f80 => if (target.c_type_bit_size(.longdouble) == 80) + target.c_type_byte_size(.longdouble) + else + 16, + .zig_c_longdouble => target.c_type_byte_size(.longdouble), + + .array, + .vector, + => { + const data = self.cast(Payload.Sequence).?.data; + return data.len * store.indexToCType(data.elem_type).byteSize(store, target); + }, + + .fwd_anon_struct, + .fwd_anon_union, + .fwd_struct, + .fwd_union, + .unnamed_struct, + .unnamed_union, + .packed_unnamed_struct, + .packed_unnamed_union, + .anon_struct, + .anon_union, + .@"struct", + .@"union", + .packed_struct, + .packed_union, + .function, + .varargs_function, + => unreachable, + }; + } + pub fn isPacked(self: CType) bool { return switch (self.tag()) { else => false, @@ -787,26 +1077,26 @@ pub const CType = extern union { }; } - fn tagFromIntInfo(signedness: std.builtin.Signedness, bits: u16) Tag { - return switch (bits) { + fn tagFromIntInfo(int_info: std.builtin.Type.Int) Tag { + return switch (int_info.bits) { 0 => .void, - 1...8 => switch (signedness) { + 1...8 => switch (int_info.signedness) { .unsigned => .uint8_t, .signed => .int8_t, }, - 9...16 => switch (signedness) { + 9...16 => switch (int_info.signedness) { .unsigned => .uint16_t, .signed => .int16_t, }, - 17...32 => switch (signedness) { + 17...32 => switch (int_info.signedness) { .unsigned => .uint32_t, .signed => .int32_t, }, - 33...64 => switch (signedness) { + 33...64 => switch (int_info.signedness) { .unsigned => .uint64_t, .signed => .int64_t, }, - 65...128 => switch (signedness) { + 65...128 => switch (int_info.signedness) { .unsigned => .zig_u128, .signed => .zig_i128, }, @@ -945,31 +1235,27 @@ pub const CType = extern union { .c_ulong => self.init(.@"unsigned long"), .c_longlong => self.init(.@"long long"), .c_ulonglong => self.init(.@"unsigned long long"), - else => { - const info = ty.intInfo(target); - const t = tagFromIntInfo(info.signedness, info.bits); - switch (t) { - .void => unreachable, - else => self.init(t), - .array => switch (kind) { - .forward, .complete, .global => { - const abi_size = ty.abiSize(target); - const abi_align = ty.abiAlignment(target); - self.storage = .{ .seq = .{ .base = .{ .tag = .array }, .data = .{ - .len = @divExact(abi_size, abi_align), - .elem_type = tagFromIntInfo( - .unsigned, - @intCast(u16, abi_align * 8), - ).toIndex(), - } } }; - self.value = .{ .cty = initPayload(&self.storage.seq) }; - }, - .forward_parameter, - .parameter, - => try self.initArrayParameter(ty, kind, lookup), - .payload => unreachable, + else => switch (tagFromIntInfo(ty.intInfo(target))) { + .void => unreachable, + else => |t| self.init(t), + .array => switch (kind) { + .forward, .complete, .global => { + const abi_size = ty.abiSize(target); + const abi_align = ty.abiAlignment(target); + self.storage = .{ .seq = .{ .base = .{ .tag = .array }, .data = .{ + .len = @divExact(abi_size, abi_align), + .elem_type = tagFromIntInfo(.{ + .signedness = .unsigned, + .bits = @intCast(u16, abi_align * 8), + }).toIndex(), + } } }; + self.value = .{ .cty = initPayload(&self.storage.seq) }; }, - } + .forward_parameter, + .parameter, + => try self.initArrayParameter(ty, kind, lookup), + .payload => unreachable, + }, }, } else switch (ty.zigTypeTag()) { .Frame => unreachable, diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index f8a1928dd1..70ac38d6fa 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -368,7 +368,6 @@ test "comptime @bitCast packed struct to int and back" { } test "comptime bitcast with fields following f80" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; From a8f4ac2b94e7945a5a1623547f258f5f32f12674 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 3 Mar 2023 00:18:35 -0500 Subject: [PATCH 020/294] CBE: implement big integer and vector comparisons --- lib/zig.h | 313 +++++++++++++++++++++++++++------- src/codegen/c.zig | 342 ++++++++++++++++++++++++-------------- src/codegen/c/type.zig | 124 ++++++++++++++ src/type.zig | 2 +- test/behavior/bitcast.zig | 2 - test/behavior/math.zig | 1 - test/behavior/vector.zig | 2 - 7 files changed, 595 insertions(+), 191 deletions(-) diff --git a/lib/zig.h b/lib/zig.h index 7353ea935d..c39cffee24 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -37,6 +37,14 @@ typedef char bool; #define zig_has_attribute(attribute) 0 #endif +#if __LITTLE_ENDIAN__ || _MSC_VER +#define zig_little_endian 1 +#define zig_big_endian 0 +#else +#define zig_little_endian 0 +#define zig_big_endian 1 +#endif + #if __STDC_VERSION__ >= 201112L #define zig_threadlocal _Thread_local #elif defined(__GNUC__) @@ -1379,7 +1387,7 @@ typedef signed __int128 zig_i128; #else /* zig_has_int128 */ -#if __LITTLE_ENDIAN__ || _MSC_VER +#if zig_little_endian typedef struct { zig_align(16) uint64_t lo; uint64_t hi; } zig_u128; typedef struct { zig_align(16) uint64_t lo; int64_t hi; } zig_i128; #else @@ -1909,6 +1917,177 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { return zig_bitcast_i128(zig_bit_reverse_u128(zig_bitcast_u128(val), bits)); } +/* ========================== Big Integer Support =========================== */ + +static inline uint16_t zig_big_bytes(uint16_t bits) { + uint16_t bytes = (bits + CHAR_BIT - 1) / CHAR_BIT; + uint16_t alignment = 16; + while (alignment / 2 >= bytes) alignment /= 2; + return (bytes + alignment - 1) / alignment * alignment; +} + +static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + const uint8_t *lhs_bytes = lhs; + const uint8_t *rhs_bytes = rhs; + uint16_t byte_offset = 0; + bool do_signed = is_signed; + uint16_t remaining_bytes = zig_big_bytes(bits); + +#if zig_little_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { + int32_t limb_cmp; + +#if zig_little_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + if (do_signed) { + zig_i128 lhs_limb; + zig_i128 rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_cmp = zig_cmp_i128(lhs_limb, rhs_limb); + do_signed = false; + } else { + zig_u128 lhs_limb; + zig_u128 rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_cmp = zig_cmp_u128(lhs_limb, rhs_limb); + } + + if (limb_cmp != 0) return limb_cmp; + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + if (do_signed) { + int64_t lhs_limb; + int64_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint64_t lhs_limb; + uint64_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + if (do_signed) { + int32_t lhs_limb; + int32_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint32_t lhs_limb; + uint32_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + if (do_signed) { + int16_t lhs_limb; + int16_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint16_t lhs_limb; + uint16_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + if (do_signed) { + int8_t lhs_limb; + int8_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint8_t lhs_limb; + uint8_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + } + + return 0; +} + /* ========================= Floating Point Support ========================= */ #if _MSC_VER @@ -1933,7 +2112,6 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { #define zig_make_special_f64(sign, name, arg, repr) sign zig_make_f64(__builtin_##name, )(arg) #define zig_make_special_f80(sign, name, arg, repr) sign zig_make_f80(__builtin_##name, )(arg) #define zig_make_special_f128(sign, name, arg, repr) sign zig_make_f128(__builtin_##name, )(arg) -#define zig_make_special_c_longdouble(sign, name, arg, repr) sign zig_make_c_longdouble(__builtin_##name, )(arg) #else #define zig_has_float_builtins 0 #define zig_make_special_f16(sign, name, arg, repr) zig_float_from_repr_f16(repr) @@ -1941,13 +2119,13 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { #define zig_make_special_f64(sign, name, arg, repr) zig_float_from_repr_f64(repr) #define zig_make_special_f80(sign, name, arg, repr) zig_float_from_repr_f80(repr) #define zig_make_special_f128(sign, name, arg, repr) zig_float_from_repr_f128(repr) -#define zig_make_special_c_longdouble(sign, name, arg, repr) zig_float_from_repr_c_longdouble(repr) #endif #define zig_has_f16 1 #define zig_bitSizeOf_f16 16 +typedef int16_t zig_repr_f16; #define zig_libc_name_f16(name) __##name##h -#define zig_make_special_constant_f16(sign, name, arg, repr) zig_make_special_f16(sign, name, arg, repr) +#define zig_init_special_f16(sign, name, arg, repr) zig_make_special_f16(sign, name, arg, repr) #if FLT_MANT_DIG == 11 typedef float zig_f16; #define zig_make_f16(fp, repr) fp##f @@ -1956,7 +2134,9 @@ typedef double zig_f16; #define zig_make_f16(fp, repr) fp #elif LDBL_MANT_DIG == 11 #define zig_bitSizeOf_c_longdouble 16 -typedef uint16_t zig_repr_c_longdouble; +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f16 zig_repr_c_longdouble; +#endif typedef long double zig_f16; #define zig_make_f16(fp, repr) fp##l #elif FLT16_MANT_DIG == 11 && (zig_has_builtin(inff16) || defined(zig_gnuc)) @@ -1973,17 +2153,18 @@ typedef int16_t zig_f16; #define zig_make_f16(fp, repr) repr #undef zig_make_special_f16 #define zig_make_special_f16(sign, name, arg, repr) repr -#undef zig_make_special_constant_f16 -#define zig_make_special_constant_f16(sign, name, arg, repr) repr +#undef zig_init_special_f16 +#define zig_init_special_f16(sign, name, arg, repr) repr #endif #define zig_has_f32 1 #define zig_bitSizeOf_f32 32 +typedef int32_t zig_repr_f32; #define zig_libc_name_f32(name) name##f #if _MSC_VER -#define zig_make_special_constant_f32(sign, name, arg, repr) sign zig_make_f32(zig_msvc_flt_##name, ) +#define zig_init_special_f32(sign, name, arg, repr) sign zig_make_f32(zig_msvc_flt_##name, ) #else -#define zig_make_special_constant_f32(sign, name, arg, repr) zig_make_special_f32(sign, name, arg, repr) +#define zig_init_special_f32(sign, name, arg, repr) zig_make_special_f32(sign, name, arg, repr) #endif #if FLT_MANT_DIG == 24 typedef float zig_f32; @@ -1993,7 +2174,9 @@ typedef double zig_f32; #define zig_make_f32(fp, repr) fp #elif LDBL_MANT_DIG == 24 #define zig_bitSizeOf_c_longdouble 32 -typedef uint32_t zig_repr_c_longdouble; +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f32 zig_repr_c_longdouble; +#endif typedef long double zig_f32; #define zig_make_f32(fp, repr) fp##l #elif FLT32_MANT_DIG == 24 @@ -2007,21 +2190,24 @@ typedef int32_t zig_f32; #define zig_make_f32(fp, repr) repr #undef zig_make_special_f32 #define zig_make_special_f32(sign, name, arg, repr) repr -#undef zig_make_special_constant_f32 -#define zig_make_special_constant_f32(sign, name, arg, repr) repr +#undef zig_init_special_f32 +#define zig_init_special_f32(sign, name, arg, repr) repr #endif #define zig_has_f64 1 #define zig_bitSizeOf_f64 64 +typedef int64_t zig_repr_f64; #define zig_libc_name_f64(name) name #if _MSC_VER #ifdef ZIG_TARGET_ABI_MSVC #define zig_bitSizeOf_c_longdouble 64 -typedef uint64_t zig_repr_c_longdouble; +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f64 zig_repr_c_longdouble; #endif -#define zig_make_special_constant_f64(sign, name, arg, repr) sign zig_make_f64(zig_msvc_flt_##name, ) +#endif +#define zig_init_special_f64(sign, name, arg, repr) sign zig_make_f64(zig_msvc_flt_##name, ) #else /* _MSC_VER */ -#define zig_make_special_constant_f64(sign, name, arg, repr) zig_make_special_f64(sign, name, arg, repr) +#define zig_init_special_f64(sign, name, arg, repr) zig_make_special_f64(sign, name, arg, repr) #endif /* _MSC_VER */ #if FLT_MANT_DIG == 53 typedef float zig_f64; @@ -2031,7 +2217,9 @@ typedef double zig_f64; #define zig_make_f64(fp, repr) fp #elif LDBL_MANT_DIG == 53 #define zig_bitSizeOf_c_longdouble 64 -typedef uint64_t zig_repr_c_longdouble; +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f64 zig_repr_c_longdouble; +#endif typedef long double zig_f64; #define zig_make_f64(fp, repr) fp##l #elif FLT64_MANT_DIG == 53 @@ -2048,14 +2236,15 @@ typedef int64_t zig_f64; #define zig_make_f64(fp, repr) repr #undef zig_make_special_f64 #define zig_make_special_f64(sign, name, arg, repr) repr -#undef zig_make_special_constant_f64 -#define zig_make_special_constant_f64(sign, name, arg, repr) repr +#undef zig_init_special_f64 +#define zig_init_special_f64(sign, name, arg, repr) repr #endif #define zig_has_f80 1 #define zig_bitSizeOf_f80 80 +typedef zig_i128 zig_repr_f80; #define zig_libc_name_f80(name) __##name##x -#define zig_make_special_constant_f80(sign, name, arg, repr) zig_make_special_f80(sign, name, arg, repr) +#define zig_init_special_f80(sign, name, arg, repr) zig_make_special_f80(sign, name, arg, repr) #if FLT_MANT_DIG == 64 typedef float zig_f80; #define zig_make_f80(fp, repr) fp##f @@ -2064,7 +2253,9 @@ typedef double zig_f80; #define zig_make_f80(fp, repr) fp #elif LDBL_MANT_DIG == 64 #define zig_bitSizeOf_c_longdouble 80 -typedef zig_u128 zig_repr_c_longdouble; +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f80 zig_repr_c_longdouble; +#endif typedef long double zig_f80; #define zig_make_f80(fp, repr) fp##l #elif FLT80_MANT_DIG == 64 @@ -2084,14 +2275,15 @@ typedef zig_i128 zig_f80; #define zig_make_f80(fp, repr) repr #undef zig_make_special_f80 #define zig_make_special_f80(sign, name, arg, repr) repr -#undef zig_make_special_constant_f80 -#define zig_make_special_constant_f80(sign, name, arg, repr) repr +#undef zig_init_special_f80 +#define zig_init_special_f80(sign, name, arg, repr) repr #endif #define zig_has_f128 1 #define zig_bitSizeOf_f128 128 +typedef zig_i128 zig_repr_f128; #define zig_libc_name_f128(name) name##q -#define zig_make_special_constant_f128(sign, name, arg, repr) zig_make_special_f128(sign, name, arg, repr) +#define zig_init_special_f128(sign, name, arg, repr) zig_make_special_f128(sign, name, arg, repr) #if FLT_MANT_DIG == 113 typedef float zig_f128; #define zig_make_f128(fp, repr) fp##f @@ -2100,7 +2292,9 @@ typedef double zig_f128; #define zig_make_f128(fp, repr) fp #elif LDBL_MANT_DIG == 113 #define zig_bitSizeOf_c_longdouble 128 -typedef zig_u128 zig_repr_c_longdouble; +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f128 zig_repr_c_longdouble; +#endif typedef long double zig_f128; #define zig_make_f128(fp, repr) fp##l #elif FLT128_MANT_DIG == 113 @@ -2122,63 +2316,44 @@ typedef zig_i128 zig_f128; #define zig_make_f128(fp, repr) repr #undef zig_make_special_f128 #define zig_make_special_f128(sign, name, arg, repr) repr -#undef zig_make_special_constant_f128 -#define zig_make_special_constant_f128(sign, name, arg, repr) repr +#undef zig_init_special_f128 +#define zig_init_special_f128(sign, name, arg, repr) repr #endif -#define zig_has_c_longdouble 1 - -#ifdef ZIG_TARGET_ABI_MSVC -#define zig_libc_name_c_longdouble(name) name -#else -#define zig_libc_name_c_longdouble(name) name##l -#endif - -#define zig_make_special_constant_c_longdouble(sign, name, arg, repr) zig_make_special_c_longdouble(sign, name, arg, repr) #ifdef zig_bitSizeOf_c_longdouble +#define zig_has_c_longdouble 1 #ifdef ZIG_TARGET_ABI_MSVC #undef zig_bitSizeOf_c_longdouble #define zig_bitSizeOf_c_longdouble 64 -typedef uint64_t zig_repr_c_longdouble; typedef zig_f64 zig_c_longdouble; -#define zig_make_c_longdouble(fp, repr) fp +typedef zig_repr_f64 zig_repr_c_longdouble; #else typedef long double zig_c_longdouble; -#define zig_make_c_longdouble(fp, repr) fp##l #endif #else /* zig_bitSizeOf_c_longdouble */ -#undef zig_has_c_longdouble #define zig_has_c_longdouble 0 -#define zig_bitSizeOf_c_longdouble 80 -typedef zig_u128 zig_repr_c_longdouble; -#define zig_compiler_rt_abbrev_c_longdouble zig_compiler_rt_abbrev_f80 #define zig_bitSizeOf_repr_c_longdouble 128 -typedef zig_i128 zig_c_longdouble; -#define zig_make_c_longdouble(fp, repr) repr -#undef zig_make_special_c_longdouble -#define zig_make_special_c_longdouble(sign, name, arg, repr) repr -#undef zig_make_special_constant_c_longdouble -#define zig_make_special_constant_c_longdouble(sign, name, arg, repr) repr +typedef zig_f128 zig_c_longdouble; +typedef zig_repr_f128 zig_repr_c_longdouble; #endif /* zig_bitSizeOf_c_longdouble */ #if !zig_has_float_builtins -#define zig_float_from_repr(Type, ReprType) \ - static inline zig_##Type zig_float_from_repr_##Type(ReprType repr) { \ +#define zig_float_from_repr(Type) \ + static inline zig_##Type zig_float_from_repr_##Type(zig_repr_##Type repr) { \ zig_##Type result; \ memcpy(&result, &repr, sizeof(result)); \ return result; \ } -zig_float_from_repr(f16, uint16_t) -zig_float_from_repr(f32, uint32_t) -zig_float_from_repr(f64, uint64_t) -zig_float_from_repr(f80, zig_u128) -zig_float_from_repr(f128, zig_u128) -zig_float_from_repr(c_longdouble, zig_repr_c_longdouble) +zig_float_from_repr(f16) +zig_float_from_repr(f32) +zig_float_from_repr(f64) +zig_float_from_repr(f80) +zig_float_from_repr(f128) #endif #define zig_cast_f16 (zig_f16) @@ -2187,11 +2362,9 @@ zig_float_from_repr(c_longdouble, zig_repr_c_longdouble) #if _MSC_VER && !zig_has_f128 #define zig_cast_f80 -#define zig_cast_c_longdouble #define zig_cast_f128 #else #define zig_cast_f80 (zig_f80) -#define zig_cast_c_longdouble (zig_c_longdouble) #define zig_cast_f128 (zig_f128) #endif @@ -2320,7 +2493,6 @@ zig_float_builtins(f32) zig_float_builtins(f64) zig_float_builtins(f80) zig_float_builtins(f128) -zig_float_builtins(c_longdouble) #if _MSC_VER && (_M_IX86 || _M_X64) @@ -2563,6 +2735,29 @@ zig_msvc_atomics_128op(u128, max) #endif /* _MSC_VER && (_M_IX86 || _M_X64) */ +/* ============================= Vector Support ============================= */ + +#define zig_cmp_vec(operation, operator) \ + static inline void zig_##operation##_vec(bool *result, const void *lhs, const void *rhs, uint32_t len, bool is_signed, uint16_t elem_bits) { \ + uint32_t index = 0; \ + const uint8_t *lhs_ptr = lhs; \ + const uint8_t *rhs_ptr = rhs; \ + uint16_t elem_bytes = zig_big_bytes(elem_bits); \ + \ + while (index < len) { \ + result[index] = zig_cmp_big(lhs_ptr, rhs_ptr, is_signed, elem_bits) operator 0; \ + lhs_ptr += elem_bytes; \ + rhs_ptr += elem_bytes; \ + index += 1; \ + } \ + } +zig_cmp_vec(eq, ==) +zig_cmp_vec(ne, !=) +zig_cmp_vec(lt, < ) +zig_cmp_vec(le, <=) +zig_cmp_vec(gt, > ) +zig_cmp_vec(ge, >=) + /* ======================== Special Case Intrinsics ========================= */ #if (_MSC_VER && _M_X64) || defined(__x86_64__) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index addd3c8332..f4a817cecd 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -112,11 +112,7 @@ const ValueRenderLocation = enum { } }; -const BuiltinInfo = enum { - None, - Range, - Bits, -}; +const BuiltinInfo = enum { none, bits }; const reserved_idents = std.ComptimeStringMap(void, .{ // C language @@ -440,6 +436,10 @@ pub const Function = struct { return f.object.dg.typeToCType(ty, kind); } + fn byteSize(f: *Function, cty: CType) u64 { + return f.object.dg.byteSize(cty); + } + fn renderType(f: *Function, w: anytype, t: Type) !void { return f.object.dg.renderType(w, t); } @@ -1003,8 +1003,9 @@ pub const DeclGen = struct { // return dg.fail("Only quiet nans are supported in global variable initializers", .{}); } - try writer.writeAll("zig_make_special_"); - if (location == .StaticInitializer) try writer.writeAll("constant_"); + try writer.writeAll("zig_"); + try writer.writeAll(if (location == .StaticInitializer) "init" else "make"); + try writer.writeAll("_special_"); try dg.renderTypeForBuiltinFnName(writer, ty); try writer.writeByte('('); if (std.math.signbit(f128_val)) try writer.writeByte('-'); @@ -1565,6 +1566,10 @@ pub const DeclGen = struct { return dg.ctypes.typeToCType(dg.gpa, ty, dg.module, kind); } + fn byteSize(dg: *DeclGen, cty: CType) u64 { + return cty.byteSize(dg.ctypes.set, dg.module.getTarget()); + } + /// Renders a type as a single identifier, generating intermediate typedefs /// if necessary. /// @@ -1861,51 +1866,64 @@ pub const DeclGen = struct { } fn renderTypeForBuiltinFnName(dg: *DeclGen, writer: anytype, ty: Type) !void { - const target = dg.module.getTarget(); - if (ty.isAbiInt()) { - const int_info = ty.intInfo(target); - const c_bits = toCIntBits(int_info.bits) orelse - return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); - try writer.print("{c}{d}", .{ signAbbrev(int_info.signedness), c_bits }); - } else if (ty.isRuntimeFloat()) { - try ty.print(writer, dg.module); - } else if (ty.isPtrAtRuntime()) { - try writer.print("p{d}", .{ty.bitSize(target)}); - } else if (ty.zigTypeTag() == .Bool) { - try writer.print("u8", .{}); - } else return dg.fail("TODO: CBE: implement renderTypeForBuiltinFnName for type {}", .{ - ty.fmt(dg.module), - }); + try dg.renderCTypeForBuiltinFnName(writer, try dg.typeToCType(ty, .complete)); + } + + fn renderCTypeForBuiltinFnName(dg: *DeclGen, writer: anytype, cty: CType) !void { + switch (cty.tag()) { + else => try writer.print("{c}{d}", .{ + if (cty.isBool()) + signAbbrev(.unsigned) + else if (cty.isInteger()) + signAbbrev(cty.signedness() orelse .unsigned) + else if (cty.isFloat()) + @as(u8, 'f') + else if (cty.isPointer()) + @as(u8, 'p') + else + return dg.fail("TODO: CBE: implement renderTypeForBuiltinFnName for type {}", .{ + cty.tag(), + }), + if (cty.isFloat()) cty.floatActiveBits(dg.module.getTarget()) else dg.byteSize(cty) * 8, + }), + .array => try writer.writeAll("big"), + .vector => try writer.writeAll("vec"), + } } fn renderBuiltinInfo(dg: *DeclGen, writer: anytype, ty: Type, info: BuiltinInfo) !void { - const target = dg.module.getTarget(); switch (info) { - .None => {}, - .Range => { - var arena = std.heap.ArenaAllocator.init(dg.gpa); - defer arena.deinit(); - - const ExpectedContents = union { u: Value.Payload.U64, i: Value.Payload.I64 }; - var stack align(@alignOf(ExpectedContents)) = - std.heap.stackFallback(@sizeOf(ExpectedContents), arena.allocator()); - - const int_info = ty.intInfo(target); - if (int_info.signedness == .signed) { - const min_val = try ty.minInt(stack.get(), target); - try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, min_val, .Other)}); + .none => {}, + .bits => { + const cty = try dg.typeToCType(ty, .complete); + if (cty.castTag(.vector)) |pl| { + var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = pl.data.len }; + try writer.print(", {}", .{try dg.fmtIntLiteral( + Type.u32, + Value.initPayload(&len_pl.base), + .FunctionArgument, + )}); } - const max_val = try ty.maxInt(stack.get(), target); - try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, max_val, .Other)}); - }, - .Bits => { - var bits_pl = Value.Payload.U64{ - .base = .{ .tag = .int_u64 }, - .data = ty.bitSize(target), - }; - const bits_val = Value.initPayload(&bits_pl.base); - try writer.print(", {}", .{try dg.fmtIntLiteral(Type.u8, bits_val, .Other)}); + const target = dg.module.getTarget(); + const elem_ty = ty.shallowElemType(); + const elem_info = if (elem_ty.isAbiInt()) + elem_ty.intInfo(target) + else + std.builtin.Type.Int{ + .signedness = .unsigned, + .bits = @intCast(u16, elem_ty.bitSize(target)), + }; + switch (cty.tag()) { + else => {}, + .array, .vector => try writer.print(", {}", .{elem_info.signedness == .signed}), + } + + var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = elem_info.bits }; + try writer.print(", {}", .{try dg.fmtIntLiteral(switch (cty.tag()) { + else => Type.u8, + .array, .vector => Type.u16, + }, Value.initPayload(&bits_pl.base), .FunctionArgument)}); }, } } @@ -2758,35 +2776,35 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, // TODO use a different strategy for add, sub, mul, div // that communicates to the optimizer that wrapping is UB. - .add => try airBinOp(f, inst, "+", "add", .None), - .sub => try airBinOp(f, inst, "-", "sub", .None), - .mul => try airBinOp(f, inst, "*", "mul", .None), + .add => try airBinOp(f, inst, "+", "add", .none), + .sub => try airBinOp(f, inst, "-", "sub", .none), + .mul => try airBinOp(f, inst, "*", "mul", .none), .neg => try airFloatNeg(f, inst), - .div_float => try airBinBuiltinCall(f, inst, "div", .None), + .div_float => try airBinBuiltinCall(f, inst, "div", .none), - .div_trunc, .div_exact => try airBinOp(f, inst, "/", "div_trunc", .None), + .div_trunc, .div_exact => try airBinOp(f, inst, "/", "div_trunc", .none), .rem => blk: { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs_ty = f.air.typeOf(bin_op.lhs); // For binary operations @TypeOf(lhs)==@TypeOf(rhs), // so we only check one. break :blk if (lhs_ty.isInt()) - try airBinOp(f, inst, "%", "rem", .None) + try airBinOp(f, inst, "%", "rem", .none) else try airBinFloatOp(f, inst, "fmod"); }, - .div_floor => try airBinBuiltinCall(f, inst, "div_floor", .None), - .mod => try airBinBuiltinCall(f, inst, "mod", .None), + .div_floor => try airBinBuiltinCall(f, inst, "div_floor", .none), + .mod => try airBinBuiltinCall(f, inst, "mod", .none), - .addwrap => try airBinBuiltinCall(f, inst, "addw", .Bits), - .subwrap => try airBinBuiltinCall(f, inst, "subw", .Bits), - .mulwrap => try airBinBuiltinCall(f, inst, "mulw", .Bits), + .addwrap => try airBinBuiltinCall(f, inst, "addw", .bits), + .subwrap => try airBinBuiltinCall(f, inst, "subw", .bits), + .mulwrap => try airBinBuiltinCall(f, inst, "mulw", .bits), - .add_sat => try airBinBuiltinCall(f, inst, "adds", .Bits), - .sub_sat => try airBinBuiltinCall(f, inst, "subs", .Bits), - .mul_sat => try airBinBuiltinCall(f, inst, "muls", .Bits), - .shl_sat => try airBinBuiltinCall(f, inst, "shls", .Bits), + .add_sat => try airBinBuiltinCall(f, inst, "adds", .bits), + .sub_sat => try airBinBuiltinCall(f, inst, "subs", .bits), + .mul_sat => try airBinBuiltinCall(f, inst, "muls", .bits), + .shl_sat => try airBinBuiltinCall(f, inst, "shls", .bits), .sqrt => try airUnFloatOp(f, inst, "sqrt"), .sin => try airUnFloatOp(f, inst, "sin"), @@ -2805,34 +2823,38 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .mul_add => try airMulAdd(f, inst), - .add_with_overflow => try airOverflow(f, inst, "add", .Bits), - .sub_with_overflow => try airOverflow(f, inst, "sub", .Bits), - .mul_with_overflow => try airOverflow(f, inst, "mul", .Bits), - .shl_with_overflow => try airOverflow(f, inst, "shl", .Bits), + .add_with_overflow => try airOverflow(f, inst, "add", .bits), + .sub_with_overflow => try airOverflow(f, inst, "sub", .bits), + .mul_with_overflow => try airOverflow(f, inst, "mul", .bits), + .shl_with_overflow => try airOverflow(f, inst, "shl", .bits), .min => try airMinMax(f, inst, '<', "fmin"), .max => try airMinMax(f, inst, '>', "fmax"), .slice => try airSlice(f, inst), - .cmp_gt => try airCmpOp(f, inst, ">", "gt"), - .cmp_gte => try airCmpOp(f, inst, ">=", "ge"), - .cmp_lt => try airCmpOp(f, inst, "<", "lt"), - .cmp_lte => try airCmpOp(f, inst, "<=", "le"), + .cmp_gt => try airCmpOp(f, inst, .gt), + .cmp_gte => try airCmpOp(f, inst, .gte), + .cmp_lt => try airCmpOp(f, inst, .lt), + .cmp_lte => try airCmpOp(f, inst, .lte), - .cmp_eq => try airEquality(f, inst, "((", "==", "eq"), - .cmp_neq => try airEquality(f, inst, "!((", "!=", "ne"), + .cmp_eq => try airEquality(f, inst, .eq), + .cmp_neq => try airEquality(f, inst, .neq), - .cmp_vector => return f.fail("TODO: C backend: implement cmp_vector", .{}), + .cmp_vector => blk: { + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.VectorCmp, ty_pl.payload).data; + break :blk try cmpBuiltinCall(f, inst, extra, extra.compareOperator(), .operator, .bits); + }, .cmp_lt_errors_len => try airCmpLtErrorsLen(f, inst), // bool_and and bool_or are non-short-circuit operations - .bool_and, .bit_and => try airBinOp(f, inst, "&", "and", .None), - .bool_or, .bit_or => try airBinOp(f, inst, "|", "or", .None), - .xor => try airBinOp(f, inst, "^", "xor", .None), - .shr, .shr_exact => try airBinBuiltinCall(f, inst, "shr", .None), - .shl, => try airBinBuiltinCall(f, inst, "shlw", .Bits), - .shl_exact => try airBinOp(f, inst, "<<", "shl", .None), + .bool_and, .bit_and => try airBinOp(f, inst, "&", "and", .none), + .bool_or, .bit_or => try airBinOp(f, inst, "|", "or", .none), + .xor => try airBinOp(f, inst, "^", "xor", .none), + .shr, .shr_exact => try airBinBuiltinCall(f, inst, "shr", .none), + .shl, => try airBinBuiltinCall(f, inst, "shlw", .bits), + .shl_exact => try airBinOp(f, inst, "<<", "shl", .none), .not => try airNot (f, inst), .optional_payload => try airOptionalPayload(f, inst), @@ -2877,11 +2899,11 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .memcpy => try airMemcpy(f, inst), .set_union_tag => try airSetUnionTag(f, inst), .get_union_tag => try airGetUnionTag(f, inst), - .clz => try airUnBuiltinCall(f, inst, "clz", .Bits), - .ctz => try airUnBuiltinCall(f, inst, "ctz", .Bits), - .popcount => try airUnBuiltinCall(f, inst, "popcount", .Bits), - .byte_swap => try airUnBuiltinCall(f, inst, "byte_swap", .Bits), - .bit_reverse => try airUnBuiltinCall(f, inst, "bit_reverse", .Bits), + .clz => try airUnBuiltinCall(f, inst, "clz", .bits), + .ctz => try airUnBuiltinCall(f, inst, "ctz", .bits), + .popcount => try airUnBuiltinCall(f, inst, "popcount", .bits), + .byte_swap => try airUnBuiltinCall(f, inst, "byte_swap", .bits), + .bit_reverse => try airUnBuiltinCall(f, inst, "bit_reverse", .bits), .tag_name => try airTagName(f, inst), .error_name => try airErrorName(f, inst), .splat => try airSplat(f, inst), @@ -3349,7 +3371,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { try f.writeCValueDeref(writer, operand); try writer.print(", {})", .{try f.fmtIntLiteral(bit_offset_ty, bit_offset_val)}); if (cant_cast) try writer.writeByte(')'); - try f.object.dg.renderBuiltinInfo(writer, field_ty, .Bits); + try f.object.dg.renderBuiltinInfo(writer, field_ty, .bits); try writer.writeByte(')'); } else { try f.writeCValue(writer, local, .Other); @@ -3744,7 +3766,7 @@ fn airOverflow(f: *Function, inst: Air.Inst.Index, operation: []const u8, info: fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.tag() != .bool) - return try airUnBuiltinCall(f, inst, "not", .Bits); + return try airUnBuiltinCall(f, inst, "not", .bits); const ty_op = f.air.instructions.items(.data)[inst].ty_op; @@ -3803,7 +3825,7 @@ fn airBinOp( return local; } -fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: []const u8) !CValue { +fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: std.math.CompareOperator) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; if (f.liveness.isUnused(inst)) { @@ -3813,10 +3835,11 @@ fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: const operand_ty = f.air.typeOf(bin_op.lhs); const target = f.object.dg.module.getTarget(); - if (operand_ty.isInt() and operand_ty.bitSize(target) > 64) - return try cmpBuiltinCall(f, inst, operator, "cmp"); + const operand_bits = operand_ty.bitSize(target); + if (operand_ty.isInt() and operand_bits > 64) + return cmpBuiltinCall(f, inst, bin_op, operator, .cmp, if (operand_bits > 128) .bits else .none); if (operand_ty.isRuntimeFloat()) - return try cmpBuiltinCall(f, inst, operator, operation); + return cmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); const inst_ty = f.air.typeOfIndex(inst); const lhs = try f.resolveInst(bin_op.lhs); @@ -3829,7 +3852,7 @@ fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: try writer.writeAll(" = "); try f.writeCValue(writer, lhs, .Other); try writer.writeByte(' '); - try writer.writeAll(operator); + try writer.writeAll(compareOperatorC(operator)); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(";\n"); @@ -3840,9 +3863,7 @@ fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: fn airEquality( f: *Function, inst: Air.Inst.Index, - negate_prefix: []const u8, - operator: []const u8, - operation: []const u8, + operator: std.math.CompareOperator, ) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; @@ -3853,10 +3874,11 @@ fn airEquality( const operand_ty = f.air.typeOf(bin_op.lhs); const target = f.object.dg.module.getTarget(); - if (operand_ty.isInt() and operand_ty.bitSize(target) > 64) - return try cmpBuiltinCall(f, inst, operator, "cmp"); + const operand_bits = operand_ty.bitSize(target); + if (operand_ty.isInt() and operand_bits > 64) + return cmpBuiltinCall(f, inst, bin_op, operator, .cmp, if (operand_bits > 128) .bits else .none); if (operand_ty.isRuntimeFloat()) - return try cmpBuiltinCall(f, inst, operator, operation); + return cmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); @@ -3872,7 +3894,12 @@ fn airEquality( // (A && B) || (C && (A == B)) // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload - try writer.writeAll(negate_prefix); + switch (operator) { + .eq => {}, + .neq => try writer.writeByte('!'), + else => unreachable, + } + try writer.writeAll("(("); try f.writeCValue(writer, lhs, .Other); try writer.writeAll(".is_null && "); try f.writeCValue(writer, rhs, .Other); @@ -3891,7 +3918,7 @@ fn airEquality( try f.writeCValue(writer, lhs, .Other); try writer.writeByte(' '); - try writer.writeAll(operator); + try writer.writeAll(compareOperatorC(operator)); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(";\n"); @@ -3972,7 +3999,7 @@ fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: u8, operation: []cons const inst_ty = f.air.typeOfIndex(inst); const target = f.object.dg.module.getTarget(); if (inst_ty.isInt() and inst_ty.bitSize(target) > 64) - return try airBinBuiltinCall(f, inst, operation[1..], .None); + return try airBinBuiltinCall(f, inst, operation[1..], .none); if (inst_ty.isRuntimeFloat()) return try airBinFloatOp(f, inst, operation); @@ -4418,12 +4445,35 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { // Ensure padding bits have the expected value. if (dest_ty.isAbiInt()) { + const dest_cty = try f.typeToCType(dest_ty, .complete); + const dest_info = dest_ty.intInfo(target); + var wrap_ty_pl = Type.Payload.Bits{ .base = .{ .tag = switch (dest_info.signedness) { + .unsigned => .int_unsigned, + .signed => .int_signed, + } }, .data = dest_info.bits }; + try f.writeCValue(writer, local, .Other); + if (dest_cty.castTag(.array)) |pl| { + try writer.print("[{d}]", .{switch (target.cpu.arch.endian()) { + .Little => pl.data.len - 1, + .Big => 0, + }}); + wrap_ty_pl.data -= 1; + wrap_ty_pl.data %= @intCast(u16, f.byteSize(f.indexToCType(pl.data.elem_type)) * 8); + wrap_ty_pl.data += 1; + } + const wrap_ty = Type.initPayload(&wrap_ty_pl.base); try writer.writeAll(" = zig_wrap_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, dest_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, wrap_ty); try writer.writeByte('('); try f.writeCValue(writer, local, .Other); - try f.object.dg.renderBuiltinInfo(writer, dest_ty, .Bits); + if (dest_cty.castTag(.array)) |pl| { + try writer.print("[{d}]", .{switch (target.cpu.arch.endian()) { + .Little => pl.data.len - 1, + .Big => 0, + }}); + } + try f.object.dg.renderBuiltinInfo(writer, wrap_ty, .bits); try writer.writeAll(");\n"); } @@ -5438,7 +5488,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { try f.object.dg.renderValue(writer, bit_offset_ty, bit_offset_val, .FunctionArgument); try writer.writeByte(')'); if (cant_cast) try writer.writeByte(')'); - try f.object.dg.renderBuiltinInfo(writer, field_int_ty, .Bits); + try f.object.dg.renderBuiltinInfo(writer, field_int_ty, .bits); try writer.writeAll(");\n"); if (inst_ty.eql(field_int_ty, f.object.dg.module)) return temp_local; @@ -5871,7 +5921,7 @@ fn airFloatCast(f: *Function, inst: Air.Inst.Index) !CValue { try f.writeCValue(writer, operand, .FunctionArgument); try writer.writeByte(')'); if (inst_ty.isInt() and operand_ty.isRuntimeFloat()) { - try f.object.dg.renderBuiltinInfo(writer, inst_ty, .Bits); + try f.object.dg.renderBuiltinInfo(writer, inst_ty, .bits); try writer.writeByte(')'); } try writer.writeAll(";\n"); @@ -5972,29 +6022,46 @@ fn airBinBuiltinCall( fn cmpBuiltinCall( f: *Function, inst: Air.Inst.Index, - operator: []const u8, - operation: []const u8, + data: anytype, + operator: std.math.CompareOperator, + operation: enum { cmp, operator }, + info: BuiltinInfo, ) !CValue { const inst_ty = f.air.typeOfIndex(inst); - const bin_op = f.air.instructions.items(.data)[inst].bin_op; - const operand_ty = f.air.typeOf(bin_op.lhs); + const operand_ty = f.air.typeOf(data.lhs); - const lhs = try f.resolveInst(bin_op.lhs); - const rhs = try f.resolveInst(bin_op.rhs); - try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); + const lhs = try f.resolveInst(data.lhs); + const rhs = try f.resolveInst(data.rhs); + try reap(f, inst, &.{ data.lhs, data.rhs }); + + const ref_ret = inst_ty.tag() != .bool; const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); - try f.writeCValue(writer, local, .Other); - try writer.writeAll(" = zig_"); - try writer.writeAll(operation); - try writer.writeByte('_'); + if (!ref_ret) { + try f.writeCValue(writer, local, .Other); + try writer.writeAll(" = "); + } + try writer.print("zig_{s}_", .{switch (operation) { + else => @tagName(operation), + .operator => compareOperatorAbbrev(operator), + }}); try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); try writer.writeByte('('); + if (ref_ret) { + try f.writeCValue(writer, local, .FunctionArgument); + try writer.writeAll(", "); + } try f.writeCValue(writer, lhs, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, rhs, .FunctionArgument); - try writer.print(") {s} {};\n", .{ operator, try f.fmtIntLiteral(Type.initTag(.i32), Value.zero) }); + try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); + try writer.writeByte(')'); + if (!ref_ret) try writer.print(" {s} {}", .{ + compareOperatorC(operator), + try f.fmtIntLiteral(Type.initTag(.i32), Value.zero), + }); + try writer.writeAll(";\n"); return local; } @@ -6675,7 +6742,7 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll(", "); try f.object.dg.renderValue(writer, bit_offset_ty, bit_offset_val, .FunctionArgument); - try f.object.dg.renderBuiltinInfo(writer, inst_ty, .Bits); + try f.object.dg.renderBuiltinInfo(writer, inst_ty, .bits); try writer.writeByte(')'); if (!empty) try writer.writeByte(')'); @@ -7094,6 +7161,28 @@ fn compilerRtAbbrev(ty: Type, target: std.Target) []const u8 { } else unreachable; } +fn compareOperatorAbbrev(operator: std.math.CompareOperator) []const u8 { + return switch (operator) { + .lt => "lt", + .lte => "le", + .eq => "eq", + .gte => "ge", + .gt => "gt", + .neq => "ne", + }; +} + +fn compareOperatorC(operator: std.math.CompareOperator) []const u8 { + return switch (operator) { + .lt => "<", + .lte => "<=", + .eq => "==", + .gte => ">=", + .gt => ">", + .neq => "!=", + }; +} + fn StringLiteral(comptime WriterType: type) type { // MSVC has a length limit of 16380 per string literal (before concatenation) const max_char_len = 4; @@ -7239,14 +7328,6 @@ fn formatIntLiteral( .positive = undefined, }; defer allocator.free(wrap.limbs); - if (wrap.addWrap(int, one, data.int_info.signedness, c_bits) or - data.int_info.signedness == .signed and wrap.subWrap(int, one, data.int_info.signedness, c_bits)) - return writer.print("{s}_{s}", .{ - data.cty.getStandardDefineAbbrev() orelse return writer.print("zig_{s}Int_{c}{d}", .{ - if (int.positive) "max" else "min", signAbbrev(data.int_info.signedness), c_bits, - }), - if (int.positive) "MAX" else "MIN", - }); const c_limb_info: struct { cty: CType, @@ -7277,6 +7358,15 @@ fn formatIntLiteral( }, }; if (c_limb_info.count == 1) { + if (wrap.addWrap(int, one, data.int_info.signedness, c_bits) or + data.int_info.signedness == .signed and wrap.subWrap(int, one, data.int_info.signedness, c_bits)) + return writer.print("{s}_{s}", .{ + data.cty.getStandardDefineAbbrev() orelse return writer.print("zig_{s}Int_{c}{d}", .{ + if (int.positive) "max" else "min", signAbbrev(data.int_info.signedness), c_bits, + }), + if (int.positive) "MAX" else "MIN", + }); + if (!int.positive) try writer.writeByte('-'); try data.cty.renderLiteralPrefix(writer, data.kind); @@ -7310,7 +7400,7 @@ fn formatIntLiteral( try writer.writeAll(string); } else { try data.cty.renderLiteralPrefix(writer, data.kind); - wrap.convertToTwosComplement(int, .unsigned, data.int_info.bits); + wrap.convertToTwosComplement(int, data.int_info.signedness, c_bits); std.mem.set(BigIntLimb, wrap.limbs[wrap.len..], 0); wrap.len = wrap.limbs.len; const limbs_per_c_limb = @divExact(wrap.len, c_limb_info.count); @@ -7343,7 +7433,7 @@ fn formatIntLiteral( c_limb_cty = c_limb_info.cty.toSigned(); c_limb_mut.positive = wrap.positive; - c_limb_mut.convertToTwosComplement( + c_limb_mut.truncate( c_limb_mut.toConst(), .signed, data.int_info.bits - limb_i * @bitSizeOf(BigIntLimb), diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig index a1b11df315..85e4cc9840 100644 --- a/src/codegen/c/type.zig +++ b/src/codegen/c/type.zig @@ -496,6 +496,116 @@ pub const CType = extern union { } }; + pub fn isBool(self: CType) bool { + return switch (self.tag()) { + ._Bool, + .bool, + => true, + else => false, + }; + } + + pub fn isInteger(self: CType) bool { + return switch (self.tag()) { + .char, + .@"signed char", + .short, + .int, + .long, + .@"long long", + .@"unsigned char", + .@"unsigned short", + .@"unsigned int", + .@"unsigned long", + .@"unsigned long long", + .size_t, + .ptrdiff_t, + .uint8_t, + .int8_t, + .uint16_t, + .int16_t, + .uint32_t, + .int32_t, + .uint64_t, + .int64_t, + .uintptr_t, + .intptr_t, + .zig_u128, + .zig_i128, + => true, + else => false, + }; + } + + pub fn signedness(self: CType) ?std.builtin.Signedness { + return switch (self.tag()) { + .char => null, // unknown signedness + .@"signed char", + .short, + .int, + .long, + .@"long long", + .ptrdiff_t, + .int8_t, + .int16_t, + .int32_t, + .int64_t, + .intptr_t, + .zig_i128, + => .signed, + .@"unsigned char", + .@"unsigned short", + .@"unsigned int", + .@"unsigned long", + .@"unsigned long long", + .size_t, + .uint8_t, + .uint16_t, + .uint32_t, + .uint64_t, + .uintptr_t, + .zig_u128, + => .unsigned, + else => unreachable, + }; + } + + pub fn isFloat(self: CType) bool { + return switch (self.tag()) { + .float, + .double, + .@"long double", + .zig_f16, + .zig_f32, + .zig_f64, + .zig_f80, + .zig_f128, + .zig_c_longdouble, + => true, + else => false, + }; + } + + pub fn isPointer(self: CType) bool { + return switch (self.tag()) { + .pointer, + .pointer_const, + .pointer_volatile, + .pointer_const_volatile, + => true, + else => false, + }; + } + + pub fn isFunction(self: CType) bool { + return switch (self.tag()) { + .function, + .varargs_function, + => true, + else => false, + }; + } + pub fn toSigned(self: CType) CType { return CType.initTag(switch (self.tag()) { .char, .@"signed char", .@"unsigned char" => .@"signed char", @@ -725,6 +835,20 @@ pub const CType = extern union { } } + pub fn floatActiveBits(self: CType, target: Target) u16 { + return switch (self.tag()) { + .float => target.c_type_bit_size(.float), + .double => target.c_type_bit_size(.double), + .@"long double", .zig_c_longdouble => target.c_type_bit_size(.longdouble), + .zig_f16 => 16, + .zig_f32 => 32, + .zig_f64 => 64, + .zig_f80 => 80, + .zig_f128 => 128, + else => unreachable, + }; + } + pub fn byteSize(self: CType, store: Store.Set, target: Target) u64 { return switch (self.tag()) { .void => 0, diff --git a/src/type.zig b/src/type.zig index 15525f14eb..9e501d893c 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4213,7 +4213,7 @@ pub const Type = extern union { }; } - fn shallowElemType(child_ty: Type) Type { + pub fn shallowElemType(child_ty: Type) Type { return switch (child_ty.zigTypeTag()) { .Array, .Vector => child_ty.childType(), else => child_ty, diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 70ac38d6fa..552080c836 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -34,7 +34,6 @@ test "@bitCast iX -> uX (8, 16, 128)" { test "@bitCast iX -> uX exotic integers" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; @@ -81,7 +80,6 @@ fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signe test "bitcast uX to bytes" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 54263e1daf..9ebeca8541 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1526,7 +1526,6 @@ fn testNanEqNan(comptime F: type) !void { } test "vector comparison" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 50fef7f646..d885a7fabc 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -48,7 +48,6 @@ test "vector wrap operators" { test "vector bin compares with mem.eql" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -403,7 +402,6 @@ test "initialize vector which is a struct field" { test "vector comparison operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO From 93d696e84ef17a32d5c2f1520a295ebcda968e91 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 3 Mar 2023 01:18:23 -0500 Subject: [PATCH 021/294] CBE: implement some big integer and vector unary operations --- lib/zig.h | 422 ++++++++++++++++++++++++++++++++++- src/codegen/c.zig | 51 ++++- test/behavior/bugs/10147.zig | 1 - test/behavior/math.zig | 8 +- test/behavior/popcount.zig | 1 - 5 files changed, 460 insertions(+), 23 deletions(-) diff --git a/lib/zig.h b/lib/zig.h index c39cffee24..e5cb421c6f 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -1919,7 +1919,7 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { /* ========================== Big Integer Support =========================== */ -static inline uint16_t zig_big_bytes(uint16_t bits) { +static inline uint16_t zig_int_bytes(uint16_t bits) { uint16_t bytes = (bits + CHAR_BIT - 1) / CHAR_BIT; uint16_t alignment = 16; while (alignment / 2 >= bytes) alignment /= 2; @@ -1931,7 +1931,7 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign const uint8_t *rhs_bytes = rhs; uint16_t byte_offset = 0; bool do_signed = is_signed; - uint16_t remaining_bytes = zig_big_bytes(bits); + uint16_t remaining_bytes = zig_int_bytes(bits); #if zig_little_endian byte_offset = remaining_bytes; @@ -1965,7 +1965,7 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign remaining_bytes -= 128 / CHAR_BIT; #if zig_big_endian - byte_offset -= 128 / CHAR_BIT; + byte_offset += 128 / CHAR_BIT; #endif } @@ -1994,7 +1994,7 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign remaining_bytes -= 64 / CHAR_BIT; #if zig_big_endian - byte_offset -= 64 / CHAR_BIT; + byte_offset += 64 / CHAR_BIT; #endif } @@ -2023,7 +2023,7 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign remaining_bytes -= 32 / CHAR_BIT; #if zig_big_endian - byte_offset -= 32 / CHAR_BIT; + byte_offset += 32 / CHAR_BIT; #endif } @@ -2052,7 +2052,7 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign remaining_bytes -= 16 / CHAR_BIT; #if zig_big_endian - byte_offset -= 16 / CHAR_BIT; + byte_offset += 16 / CHAR_BIT; #endif } @@ -2081,13 +2081,368 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign remaining_bytes -= 8 / CHAR_BIT; #if zig_big_endian - byte_offset -= 8 / CHAR_BIT; + byte_offset += 8 / CHAR_BIT; #endif } return 0; } +static inline uint16_t zig_clz_big(const void *val, bool is_signed, uint16_t bits) { + const uint8_t *val_bytes = val; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t skip_bits = remaining_bytes * 8 - bits; + uint16_t total_lz = 0; + uint16_t limb_lz; + (void)is_signed; + +#if zig_little_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + { + zig_u128 val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u128(val_limb, 128 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 128 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + { + uint64_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u64(val_limb, 64 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 64 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + { + uint32_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u32(val_limb, 32 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 32 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + { + uint16_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u16(val_limb, 16 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 16 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + { + uint8_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u8(val_limb, 8 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 8 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return total_lz; +} + +static inline uint16_t zig_ctz_big(const void *val, bool is_signed, uint16_t bits) { + const uint8_t *val_bytes = val; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t total_tz = 0; + uint16_t limb_tz; + (void)is_signed; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + { + zig_u128 val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u128(val_limb, 128); + } + + total_tz += limb_tz; + if (limb_tz < 128) return total_tz; + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + { + uint64_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u64(val_limb, 64); + } + + total_tz += limb_tz; + if (limb_tz < 64) return total_tz; + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + { + uint32_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u32(val_limb, 32); + } + + total_tz += limb_tz; + if (limb_tz < 32) return total_tz; + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + { + uint16_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u16(val_limb, 16); + } + + total_tz += limb_tz; + if (limb_tz < 16) return total_tz; + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + { + uint8_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u8(val_limb, 8); + } + + total_tz += limb_tz; + if (limb_tz < 8) return total_tz; + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return total_tz; +} + +static inline uint16_t zig_popcount_big(const void *val, bool is_signed, uint16_t bits) { + const uint8_t *val_bytes = val; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t total_pc = 0; + (void)is_signed; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + { + zig_u128 val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc += zig_popcount_u128(val_limb, 128); + } + + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + { + uint64_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc += zig_popcount_u64(val_limb, 64); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + { + uint32_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc += zig_popcount_u32(val_limb, 32); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + { + uint16_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc = zig_popcount_u16(val_limb, 16); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + { + uint8_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc = zig_popcount_u8(val_limb, 8); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return total_pc; +} + /* ========================= Floating Point Support ========================= */ #if _MSC_VER @@ -2742,7 +3097,7 @@ zig_msvc_atomics_128op(u128, max) uint32_t index = 0; \ const uint8_t *lhs_ptr = lhs; \ const uint8_t *rhs_ptr = rhs; \ - uint16_t elem_bytes = zig_big_bytes(elem_bits); \ + uint16_t elem_bytes = zig_int_bytes(elem_bits); \ \ while (index < len) { \ result[index] = zig_cmp_big(lhs_ptr, rhs_ptr, is_signed, elem_bits) operator 0; \ @@ -2758,6 +3113,57 @@ zig_cmp_vec(le, <=) zig_cmp_vec(gt, > ) zig_cmp_vec(ge, >=) +static inline void zig_clz_vec(void *result, const void *val, uint32_t len, bool is_signed, uint16_t elem_bits) { + uint32_t index = 0; + const uint8_t *val_ptr = val; + uint16_t elem_bytes = zig_int_bytes(elem_bits); + + while (index < len) { + uint16_t lz = zig_clz_big(val_ptr, is_signed, elem_bits); + if (elem_bits <= 128) { + ((uint8_t *)result)[index] = (uint8_t)lz; + } else { + ((uint16_t *)result)[index] = lz; + } + val_ptr += elem_bytes; + index += 1; + } +} + +static inline void zig_ctz_vec(void *result, const void *val, uint32_t len, bool is_signed, uint16_t elem_bits) { + uint32_t index = 0; + const uint8_t *val_ptr = val; + uint16_t elem_bytes = zig_int_bytes(elem_bits); + + while (index < len) { + uint16_t tz = zig_ctz_big(val_ptr, is_signed, elem_bits); + if (elem_bits <= 128) { + ((uint8_t *)result)[index] = (uint8_t)tz; + } else { + ((uint16_t *)result)[index] = tz; + } + val_ptr += elem_bytes; + index += 1; + } +} + +static inline void zig_popcount_vec(void *result, const void *val, uint32_t len, bool is_signed, uint16_t elem_bits) { + uint32_t index = 0; + const uint8_t *val_ptr = val; + uint16_t elem_bytes = zig_int_bytes(elem_bits); + + while (index < len) { + uint16_t pc = zig_popcount_big(val_ptr, is_signed, elem_bits); + if (elem_bits <= 128) { + ((uint8_t *)result)[index] = (uint8_t)pc; + } else { + ((uint16_t *)result)[index] = pc; + } + val_ptr += elem_bytes; + index += 1; + } +} + /* ======================== Special Case Intrinsics ========================= */ #if (_MSC_VER && _M_X64) || defined(__x86_64__) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f4a817cecd..4d3e71e78a 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2844,7 +2844,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .cmp_vector => blk: { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.VectorCmp, ty_pl.payload).data; - break :blk try cmpBuiltinCall(f, inst, extra, extra.compareOperator(), .operator, .bits); + break :blk try airCmpBuiltinCall(f, inst, extra, extra.compareOperator(), .operator, .bits,); }, .cmp_lt_errors_len => try airCmpLtErrorsLen(f, inst), @@ -3837,9 +3837,16 @@ fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: std.math.CompareOperat const target = f.object.dg.module.getTarget(); const operand_bits = operand_ty.bitSize(target); if (operand_ty.isInt() and operand_bits > 64) - return cmpBuiltinCall(f, inst, bin_op, operator, .cmp, if (operand_bits > 128) .bits else .none); + return airCmpBuiltinCall( + f, + inst, + bin_op, + operator, + .cmp, + if (operand_bits > 128) .bits else .none, + ); if (operand_ty.isRuntimeFloat()) - return cmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); + return airCmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); const inst_ty = f.air.typeOfIndex(inst); const lhs = try f.resolveInst(bin_op.lhs); @@ -3876,9 +3883,16 @@ fn airEquality( const target = f.object.dg.module.getTarget(); const operand_bits = operand_ty.bitSize(target); if (operand_ty.isInt() and operand_bits > 64) - return cmpBuiltinCall(f, inst, bin_op, operator, .cmp, if (operand_bits > 128) .bits else .none); + return airCmpBuiltinCall( + f, + inst, + bin_op, + operator, + .cmp, + if (operand_bits > 128) .bits else .none, + ); if (operand_ty.isRuntimeFloat()) - return cmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); + return airCmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); @@ -5969,14 +5983,25 @@ fn airUnBuiltinCall( const inst_ty = f.air.typeOfIndex(inst); const operand_ty = f.air.typeOf(ty_op.operand); + const inst_cty = try f.typeToCType(inst_ty, .complete); + const ref_ret = switch (inst_cty.tag()) { + else => false, + .array, .vector => true, + }; + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); - try f.writeCValue(writer, local, .Other); - try writer.writeAll(" = zig_"); - try writer.writeAll(operation); - try writer.writeByte('_'); + if (!ref_ret) { + try f.writeCValue(writer, local, .Other); + try writer.writeAll(" = "); + } + try writer.print("zig_{s}_", .{operation}); try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); try writer.writeByte('('); + if (ref_ret) { + try f.writeCValue(writer, local, .FunctionArgument); + try writer.writeAll(", "); + } try f.writeCValue(writer, operand, .FunctionArgument); try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); try writer.writeAll(");\n"); @@ -6019,7 +6044,7 @@ fn airBinBuiltinCall( return local; } -fn cmpBuiltinCall( +fn airCmpBuiltinCall( f: *Function, inst: Air.Inst.Index, data: anytype, @@ -6034,7 +6059,11 @@ fn cmpBuiltinCall( const rhs = try f.resolveInst(data.rhs); try reap(f, inst, &.{ data.lhs, data.rhs }); - const ref_ret = inst_ty.tag() != .bool; + const inst_cty = try f.typeToCType(inst_ty, .complete); + const ref_ret = switch (inst_cty.tag()) { + else => false, + .array, .vector => true, + }; const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); diff --git a/test/behavior/bugs/10147.zig b/test/behavior/bugs/10147.zig index 3ca9085805..77c513caa6 100644 --- a/test/behavior/bugs/10147.zig +++ b/test/behavior/bugs/10147.zig @@ -6,7 +6,6 @@ test "test calling @clz on both vector and scalar inputs" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO var x: u32 = 0x1; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 9ebeca8541..d7b8e4764b 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -100,7 +100,6 @@ test "@clz vectors" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testClzVectors(); @@ -163,7 +162,6 @@ test "@ctz vectors" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) { @@ -1562,6 +1560,12 @@ test "signed zeros are represented properly" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64 and + builtin.zig_backend == .stage2_c) + { + return error.SkipZigTest; + } + const S = struct { fn doTheTest() !void { try testOne(f16); diff --git a/test/behavior/popcount.zig b/test/behavior/popcount.zig index b27d5d77d3..9dce5820cd 100644 --- a/test/behavior/popcount.zig +++ b/test/behavior/popcount.zig @@ -67,7 +67,6 @@ fn testPopCountIntegers() !void { } test "@popCount vectors" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO From e96a0fd0a1a05fe8c3b4d87df03d78ae99b7dbcb Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 3 Mar 2023 01:48:13 -0500 Subject: [PATCH 022/294] CBE: "compute" max int alignment the lazy way --- lib/zig.h | 2 +- src/link/C.zig | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/zig.h b/lib/zig.h index e5cb421c6f..5d77c76c8f 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -1921,7 +1921,7 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { static inline uint16_t zig_int_bytes(uint16_t bits) { uint16_t bytes = (bits + CHAR_BIT - 1) / CHAR_BIT; - uint16_t alignment = 16; + uint16_t alignment = ZIG_TARGET_MAX_INT_ALIGNMENT; while (alignment / 2 >= bytes) alignment /= 2; return (bytes + alignment - 1) / alignment * alignment; } diff --git a/src/link/C.zig b/src/link/C.zig index 5663ba71e2..7e3ad2eddd 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -221,14 +221,19 @@ pub fn flush(self: *C, comp: *Compilation, prog_node: *std.Progress.Node) !void return self.flushModule(comp, prog_node); } -fn abiDefine(comp: *Compilation) ?[]const u8 { - return switch (comp.getTarget().abi) { - .msvc => "#define ZIG_TARGET_ABI_MSVC\n", - else => null, - }; +fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { + var defines = std.ArrayList(u8).init(self.base.allocator); + errdefer defines.deinit(); + const writer = defines.writer(); + switch (target.abi) { + .msvc => try writer.writeAll("#define ZIG_TARGET_ABI_MSVC\n"), + else => {}, + } + try writer.print("#define ZIG_TARGET_MAX_INT_ALIGNMENT {d}\n", .{target.maxIntAlignment()}); + return defines; } -pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node) !void { +pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !void { const tracy = trace(@src()); defer tracy.end(); @@ -245,12 +250,13 @@ pub fn flushModule(self: *C, comp: *Compilation, prog_node: *std.Progress.Node) var f: Flush = .{}; defer f.deinit(gpa); - const abi_define = abiDefine(comp); + const abi_defines = try self.abiDefines(module.getTarget()); + defer abi_defines.deinit(); // Covers defines, zig.h, ctypes, asm, lazy fwd. try f.all_buffers.ensureUnusedCapacity(gpa, 5); - if (abi_define) |buf| f.appendBufAssumeCapacity(buf); + f.appendBufAssumeCapacity(abi_defines.items); f.appendBufAssumeCapacity(zig_h); const ctypes_index = f.all_buffers.items.len; From 9e3a5ecd39227aff3b2821d0c0b489eb9713b146 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 4 Mar 2023 15:18:05 -0500 Subject: [PATCH 023/294] CBE: fix behavior test failures on msvc --- lib/zig.h | 4 +++- src/codegen/c.zig | 37 +++++++++++++++++++++++++++++-------- src/codegen/c/type.zig | 7 +++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/zig.h b/lib/zig.h index 5d77c76c8f..6b95ba3358 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -1646,7 +1646,9 @@ static inline zig_u128 zig_wrap_u128(zig_u128 val, uint8_t bits) { } static inline zig_i128 zig_wrap_i128(zig_i128 val, uint8_t bits) { - return zig_make_i128(zig_wrap_i64(zig_hi_i128(val), bits - UINT8_C(64)), zig_lo_i128(val)); + if (bits > UINT8_C(64)) return zig_make_i128(zig_wrap_i64(zig_hi_i128(val), bits - UINT8_C(64)), zig_lo_i128(val)); + int64_t lo = zig_wrap_i64((int64_t)zig_lo_i128(val), bits); + return zig_make_i128(zig_shr_i64(lo, 63), (uint64_t)lo); } static inline zig_u128 zig_shlw_u128(zig_u128 lhs, uint8_t rhs, uint8_t bits) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 4d3e71e78a..b8606b1a17 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -4461,10 +4461,12 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { if (dest_ty.isAbiInt()) { const dest_cty = try f.typeToCType(dest_ty, .complete); const dest_info = dest_ty.intInfo(target); - var wrap_ty_pl = Type.Payload.Bits{ .base = .{ .tag = switch (dest_info.signedness) { + var info_ty_pl = Type.Payload.Bits{ .base = .{ .tag = switch (dest_info.signedness) { .unsigned => .int_unsigned, .signed => .int_signed, } }, .data = dest_info.bits }; + var wrap_cty: ?CType = null; + var need_bitcasts = false; try f.writeCValue(writer, local, .Other); if (dest_cty.castTag(.array)) |pl| { @@ -4472,14 +4474,31 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { .Little => pl.data.len - 1, .Big => 0, }}); - wrap_ty_pl.data -= 1; - wrap_ty_pl.data %= @intCast(u16, f.byteSize(f.indexToCType(pl.data.elem_type)) * 8); - wrap_ty_pl.data += 1; + const elem_cty = f.indexToCType(pl.data.elem_type); + wrap_cty = elem_cty.toSignedness(dest_info.signedness); + need_bitcasts = wrap_cty.?.tag() == .zig_i128; + info_ty_pl.data -= 1; + info_ty_pl.data %= @intCast(u16, f.byteSize(elem_cty) * 8); + info_ty_pl.data += 1; } - const wrap_ty = Type.initPayload(&wrap_ty_pl.base); - try writer.writeAll(" = zig_wrap_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, wrap_ty); + try writer.writeAll(" = "); + if (need_bitcasts) { + try writer.writeAll("zig_bitcast_"); + try f.object.dg.renderCTypeForBuiltinFnName(writer, wrap_cty.?.toUnsigned()); + try writer.writeByte('('); + } + try writer.writeAll("zig_wrap_"); + const info_ty = Type.initPayload(&info_ty_pl.base); + if (wrap_cty) |cty| + try f.object.dg.renderCTypeForBuiltinFnName(writer, cty) + else + try f.object.dg.renderTypeForBuiltinFnName(writer, info_ty); try writer.writeByte('('); + if (need_bitcasts) { + try writer.writeAll("zig_bitcast_"); + try f.object.dg.renderCTypeForBuiltinFnName(writer, wrap_cty.?); + try writer.writeByte('('); + } try f.writeCValue(writer, local, .Other); if (dest_cty.castTag(.array)) |pl| { try writer.print("[{d}]", .{switch (target.cpu.arch.endian()) { @@ -4487,7 +4506,9 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { .Big => 0, }}); } - try f.object.dg.renderBuiltinInfo(writer, wrap_ty, .bits); + if (need_bitcasts) try writer.writeByte(')'); + try f.object.dg.renderBuiltinInfo(writer, info_ty, .bits); + if (need_bitcasts) try writer.writeByte(')'); try writer.writeAll(");\n"); } diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig index 85e4cc9840..313fcc130c 100644 --- a/src/codegen/c/type.zig +++ b/src/codegen/c/type.zig @@ -651,6 +651,13 @@ pub const CType = extern union { }); } + pub fn toSignedness(self: CType, s: std.builtin.Signedness) CType { + return switch (s) { + .unsigned => self.toUnsigned(), + .signed => self.toSigned(), + }; + } + pub fn getStandardDefineAbbrev(self: CType) ?[]const u8 { return switch (self.tag()) { .char => "CHAR", From b2e9c0d0ff1dc6799fe3b5fdbecd53af176f37b7 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 4 Mar 2023 19:02:42 -0500 Subject: [PATCH 024/294] Sema: fix cmp_vector type --- src/Sema.zig | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 8940527bc0..8c6e3cf05c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -574,11 +574,13 @@ pub const Block = struct { }); } - fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator, vector_ty: Air.Inst.Ref) !Air.Inst.Ref { + fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator) !Air.Inst.Ref { return block.addInst(.{ .tag = if (block.float_mode == .Optimized) .cmp_vector_optimized else .cmp_vector, .data = .{ .ty_pl = .{ - .ty = vector_ty, + .ty = try block.sema.addType( + try Type.vector(block.sema.arena, block.sema.typeOf(lhs).vectorLen(), Type.bool), + ), .payload = try block.sema.addExtra(Air.VectorCmp{ .lhs = lhs, .rhs = rhs, @@ -9412,7 +9414,7 @@ fn intCast( const ok = if (is_vector) ok: { const zeros = try Value.Tag.repeated.create(sema.arena, Value.zero); const zero_inst = try sema.addConstant(sema.typeOf(operand), zeros); - const is_in_range = try block.addCmpVector(operand, zero_inst, .eq, try sema.addType(operand_ty)); + const is_in_range = try block.addCmpVector(operand, zero_inst, .eq); const all_in_range = try block.addInst(.{ .tag = .reduce, .data = .{ .reduce = .{ .operand = is_in_range, .operation = .And } }, @@ -9466,7 +9468,7 @@ fn intCast( const dest_range = try sema.addConstant(unsigned_operand_ty, dest_range_val); const ok = if (is_vector) ok: { - const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte, try sema.addType(operand_ty)); + const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte); const all_in_range = try block.addInst(.{ .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ @@ -9483,7 +9485,7 @@ fn intCast( try sema.addSafetyCheck(block, ok, .cast_truncated_data); } else { const ok = if (is_vector) ok: { - const is_in_range = try block.addCmpVector(diff, dest_max, .lte, try sema.addType(operand_ty)); + const is_in_range = try block.addCmpVector(diff, dest_max, .lte); const all_in_range = try block.addInst(.{ .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ @@ -9504,7 +9506,7 @@ fn intCast( const ok = if (is_vector) ok: { const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); const zero_inst = try sema.addConstant(operand_ty, zero_val); - const is_in_range = try block.addCmpVector(operand, zero_inst, .gte, try sema.addType(operand_ty)); + const is_in_range = try block.addCmpVector(operand, zero_inst, .gte); const all_in_range = try block.addInst(.{ .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ @@ -12016,7 +12018,7 @@ fn zirShl( const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val)); - const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty)); + const lt = try block.addCmpVector(rhs, bit_count_inst, .lt); break :ok try block.addInst(.{ .tag = .reduce, .data = .{ .reduce = .{ @@ -12172,7 +12174,7 @@ fn zirShr( const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { const bit_count_inst = try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, bit_count_val)); - const lt = try block.addCmpVector(rhs, bit_count_inst, .lt, try sema.addType(rhs_ty)); + const lt = try block.addCmpVector(rhs, bit_count_inst, .lt); break :ok try block.addInst(.{ .tag = .reduce, .data = .{ .reduce = .{ @@ -12191,7 +12193,7 @@ fn zirShr( const back = try block.addBinOp(.shl, result, rhs); const ok = if (rhs_ty.zigTypeTag() == .Vector) ok: { - const eql = try block.addCmpVector(lhs, back, .eq, try sema.addType(rhs_ty)); + const eql = try block.addCmpVector(lhs, back, .eq); break :ok try block.addInst(.{ .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, .data = .{ .reduce = .{ @@ -13192,7 +13194,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const floored = try block.addUnOp(.floor, result); if (resolved_type.zigTypeTag() == .Vector) { - const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type)); + const eql = try block.addCmpVector(result, floored, .eq); break :ok try block.addInst(.{ .tag = switch (block.float_mode) { .Strict => .reduce, @@ -13216,7 +13218,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (resolved_type.zigTypeTag() == .Vector) { const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); const zero = try sema.addConstant(resolved_type, zero_val); - const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type)); + const eql = try block.addCmpVector(remainder, zero, .eq); break :ok try block.addInst(.{ .tag = .reduce, .data = .{ .reduce = .{ @@ -13514,14 +13516,13 @@ fn addDivIntOverflowSafety( var ok: Air.Inst.Ref = .none; if (resolved_type.zigTypeTag() == .Vector) { - const vector_ty_ref = try sema.addType(resolved_type); if (maybe_lhs_val == null) { const min_int_ref = try sema.addConstant(resolved_type, min_int); - ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); + ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq); } if (maybe_rhs_val == null) { const neg_one_ref = try sema.addConstant(resolved_type, neg_one); - const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); + const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq); if (ok == .none) { ok = rhs_ok; } else { @@ -13573,7 +13574,7 @@ fn addDivByZeroSafety( const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); const zero = try sema.addConstant(resolved_type, zero_val); - const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); + const ok = try block.addCmpVector(casted_rhs, zero, .neq); break :ok try block.addInst(.{ .tag = if (is_int) .reduce else .reduce_optimized, .data = .{ .reduce = .{ @@ -15202,9 +15203,7 @@ fn cmpSelf( }; try sema.requireRuntimeBlock(block, src, runtime_src); if (resolved_type.zigTypeTag() == .Vector) { - const result_ty = try Type.vector(sema.arena, resolved_type.vectorLen(), Type.bool); - const result_ty_ref = try sema.addType(result_ty); - return block.addCmpVector(casted_lhs, casted_rhs, op, result_ty_ref); + return block.addCmpVector(casted_lhs, casted_rhs, op); } const tag = Air.Inst.Tag.fromCmpOp(op, block.float_mode == .Optimized); return block.addBinOp(tag, casted_lhs, casted_rhs); @@ -23035,7 +23034,7 @@ fn panicSentinelMismatch( const ok = if (sentinel_ty.zigTypeTag() == .Vector) ok: { const eql = - try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq, try sema.addType(sentinel_ty)); + try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq); break :ok try parent_block.addInst(.{ .tag = .reduce, .data = .{ .reduce = .{ @@ -29368,8 +29367,7 @@ fn cmpVector( }; try sema.requireRuntimeBlock(block, src, runtime_src); - const result_ty_inst = try sema.addType(result_ty); - return block.addCmpVector(lhs, rhs, op, result_ty_inst); + return block.addCmpVector(lhs, rhs, op); } fn wrapOptional( From c478c7609e4529267d1ce030577777e836ffc10b Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 00:01:15 -0500 Subject: [PATCH 025/294] CBE: implement vector operations Also, bigint add and sub which is all I was actually trying to do. --- lib/zig.h | 660 ++++++++++++++++++++---------- src/codegen/c.zig | 620 +++++++++++++++++----------- src/type.zig | 2 +- src/value.zig | 2 +- test/behavior/bitreverse.zig | 3 - test/behavior/byteswap.zig | 3 - test/behavior/cast.zig | 1 - test/behavior/floatop.zig | 12 - test/behavior/maximum_minimum.zig | 2 - test/behavior/muladd.zig | 5 - test/behavior/vector.zig | 30 +- 11 files changed, 835 insertions(+), 505 deletions(-) diff --git a/lib/zig.h b/lib/zig.h index 6b95ba3358..22a9dbbb9e 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -612,12 +612,6 @@ static inline bool zig_addo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8 #endif } -static inline void zig_vaddo_u32(uint8_t *ov, uint32_t *res, int n, - const uint32_t *lhs, const uint32_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u32(&res[i], lhs[i], rhs[i], bits); -} - zig_extern int32_t __addosi4(int32_t lhs, int32_t rhs, int *overflow); static inline bool zig_addo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) @@ -632,12 +626,6 @@ static inline bool zig_addo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t return overflow || full_res < zig_minInt_i(32, bits) || full_res > zig_maxInt_i(32, bits); } -static inline void zig_vaddo_i32(uint8_t *ov, int32_t *res, int n, - const int32_t *lhs, const int32_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i32(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_addo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) uint64_t full_res; @@ -650,12 +638,6 @@ static inline bool zig_addo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8 #endif } -static inline void zig_vaddo_u64(uint8_t *ov, uint64_t *res, int n, - const uint64_t *lhs, const uint64_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u64(&res[i], lhs[i], rhs[i], bits); -} - zig_extern int64_t __addodi4(int64_t lhs, int64_t rhs, int *overflow); static inline bool zig_addo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) @@ -670,12 +652,6 @@ static inline bool zig_addo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t return overflow || full_res < zig_minInt_i(64, bits) || full_res > zig_maxInt_i(64, bits); } -static inline void zig_vaddo_i64(uint8_t *ov, int64_t *res, int n, - const int64_t *lhs, const int64_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i64(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_addo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) uint8_t full_res; @@ -690,12 +666,6 @@ static inline bool zig_addo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t b #endif } -static inline void zig_vaddo_u8(uint8_t *ov, uint8_t *res, int n, - const uint8_t *lhs, const uint8_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u8(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_addo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) int8_t full_res; @@ -710,12 +680,6 @@ static inline bool zig_addo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits #endif } -static inline void zig_vaddo_i8(uint8_t *ov, int8_t *res, int n, - const int8_t *lhs, const int8_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i8(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_addo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) uint16_t full_res; @@ -730,12 +694,6 @@ static inline bool zig_addo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8 #endif } -static inline void zig_vaddo_u16(uint8_t *ov, uint16_t *res, int n, - const uint16_t *lhs, const uint16_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u16(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_addo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) int16_t full_res; @@ -750,12 +708,6 @@ static inline bool zig_addo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t #endif } -static inline void zig_vaddo_i16(uint8_t *ov, int16_t *res, int n, - const int16_t *lhs, const int16_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i16(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_subo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) uint32_t full_res; @@ -768,12 +720,6 @@ static inline bool zig_subo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8 #endif } -static inline void zig_vsubo_u32(uint8_t *ov, uint32_t *res, int n, - const uint32_t *lhs, const uint32_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u32(&res[i], lhs[i], rhs[i], bits); -} - zig_extern int32_t __subosi4(int32_t lhs, int32_t rhs, int *overflow); static inline bool zig_subo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) @@ -788,12 +734,6 @@ static inline bool zig_subo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t return overflow || full_res < zig_minInt_i(32, bits) || full_res > zig_maxInt_i(32, bits); } -static inline void zig_vsubo_i32(uint8_t *ov, int32_t *res, int n, - const int32_t *lhs, const int32_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i32(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_subo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) uint64_t full_res; @@ -806,12 +746,6 @@ static inline bool zig_subo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8 #endif } -static inline void zig_vsubo_u64(uint8_t *ov, uint64_t *res, int n, - const uint64_t *lhs, const uint64_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u64(&res[i], lhs[i], rhs[i], bits); -} - zig_extern int64_t __subodi4(int64_t lhs, int64_t rhs, int *overflow); static inline bool zig_subo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) @@ -826,12 +760,6 @@ static inline bool zig_subo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t return overflow || full_res < zig_minInt_i(64, bits) || full_res > zig_maxInt_i(64, bits); } -static inline void zig_vsubo_i64(uint8_t *ov, int64_t *res, int n, - const int64_t *lhs, const int64_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i64(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_subo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) uint8_t full_res; @@ -846,12 +774,6 @@ static inline bool zig_subo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t b #endif } -static inline void zig_vsubo_u8(uint8_t *ov, uint8_t *res, int n, - const uint8_t *lhs, const uint8_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u8(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_subo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) int8_t full_res; @@ -866,13 +788,6 @@ static inline bool zig_subo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits #endif } -static inline void zig_vsubo_i8(uint8_t *ov, int8_t *res, int n, - const int8_t *lhs, const int8_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i8(&res[i], lhs[i], rhs[i], bits); -} - - static inline bool zig_subo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) uint16_t full_res; @@ -887,13 +802,6 @@ static inline bool zig_subo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8 #endif } -static inline void zig_vsubo_u16(uint8_t *ov, uint16_t *res, int n, - const uint16_t *lhs, const uint16_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u16(&res[i], lhs[i], rhs[i], bits); -} - - static inline bool zig_subo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) int16_t full_res; @@ -908,12 +816,6 @@ static inline bool zig_subo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t #endif } -static inline void zig_vsubo_i16(uint8_t *ov, int16_t *res, int n, - const int16_t *lhs, const int16_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i16(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_mulo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) uint32_t full_res; @@ -926,12 +828,6 @@ static inline bool zig_mulo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8 #endif } -static inline void zig_vmulo_u32(uint8_t *ov, uint32_t *res, int n, - const uint32_t *lhs, const uint32_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u32(&res[i], lhs[i], rhs[i], bits); -} - zig_extern int32_t __mulosi4(int32_t lhs, int32_t rhs, int *overflow); static inline bool zig_mulo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) @@ -946,12 +842,6 @@ static inline bool zig_mulo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t return overflow || full_res < zig_minInt_i(32, bits) || full_res > zig_maxInt_i(32, bits); } -static inline void zig_vmulo_i32(uint8_t *ov, int32_t *res, int n, - const int32_t *lhs, const int32_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i32(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_mulo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) uint64_t full_res; @@ -964,12 +854,6 @@ static inline bool zig_mulo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8 #endif } -static inline void zig_vmulo_u64(uint8_t *ov, uint64_t *res, int n, - const uint64_t *lhs, const uint64_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u64(&res[i], lhs[i], rhs[i], bits); -} - zig_extern int64_t __mulodi4(int64_t lhs, int64_t rhs, int *overflow); static inline bool zig_mulo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) @@ -984,12 +868,6 @@ static inline bool zig_mulo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t return overflow || full_res < zig_minInt_i(64, bits) || full_res > zig_maxInt_i(64, bits); } -static inline void zig_vmulo_i64(uint8_t *ov, int64_t *res, int n, - const int64_t *lhs, const int64_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i64(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_mulo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) uint8_t full_res; @@ -1004,12 +882,6 @@ static inline bool zig_mulo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t b #endif } -static inline void zig_vmulo_u8(uint8_t *ov, uint8_t *res, int n, - const uint8_t *lhs, const uint8_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u8(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_mulo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) int8_t full_res; @@ -1024,12 +896,6 @@ static inline bool zig_mulo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits #endif } -static inline void zig_vmulo_i8(uint8_t *ov, int8_t *res, int n, - const int8_t *lhs, const int8_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i8(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_mulo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) uint16_t full_res; @@ -1044,12 +910,6 @@ static inline bool zig_mulo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8 #endif } -static inline void zig_vmulo_u16(uint8_t *ov, uint16_t *res, int n, - const uint16_t *lhs, const uint16_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u16(&res[i], lhs[i], rhs[i], bits); -} - static inline bool zig_mulo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) int16_t full_res; @@ -1064,12 +924,6 @@ static inline bool zig_mulo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t #endif } -static inline void zig_vmulo_i16(uint8_t *ov, int16_t *res, int n, - const int16_t *lhs, const int16_t *rhs, uint8_t bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i16(&res[i], lhs[i], rhs[i], bits); -} - #define zig_int_builtins(w) \ static inline bool zig_shlo_u##w(uint##w##_t *res, uint##w##_t lhs, uint8_t rhs, uint8_t bits) { \ *res = zig_shlw_u##w(lhs, rhs, bits); \ @@ -2090,6 +1944,446 @@ static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_sign return 0; } +static inline bool zig_addo_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + uint8_t *res_bytes = res; + const uint8_t *lhs_bytes = lhs; + const uint8_t *rhs_bytes = rhs; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t top_bits = remaining_bytes * 8 - bits; + bool overflow = false; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { + uint16_t limb_bits = 128 - (remaining_bytes == 128 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + if (remaining_bytes == 128 / CHAR_BIT && is_signed) { + zig_i128 res_limb; + zig_i128 tmp_limb; + zig_i128 lhs_limb; + zig_i128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i128(&res_limb, tmp_limb, zig_make_i128(INT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + zig_u128 res_limb; + zig_u128 tmp_limb; + zig_u128 lhs_limb; + zig_u128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u128(&res_limb, tmp_limb, zig_make_u128(UINT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { + uint16_t limb_bits = 64 - (remaining_bytes == 64 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + if (remaining_bytes == 64 / CHAR_BIT && is_signed) { + int64_t res_limb; + int64_t tmp_limb; + int64_t lhs_limb; + int64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i64(&res_limb, tmp_limb, overflow ? INT64_C(1) : INT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint64_t res_limb; + uint64_t tmp_limb; + uint64_t lhs_limb; + uint64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u64(&res_limb, tmp_limb, overflow ? UINT64_C(1) : UINT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { + uint16_t limb_bits = 32 - (remaining_bytes == 32 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + if (remaining_bytes == 32 / CHAR_BIT && is_signed) { + int32_t res_limb; + int32_t tmp_limb; + int32_t lhs_limb; + int32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i32(&res_limb, tmp_limb, overflow ? INT32_C(1) : INT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint32_t res_limb; + uint32_t tmp_limb; + uint32_t lhs_limb; + uint32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u32(&res_limb, tmp_limb, overflow ? UINT32_C(1) : UINT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { + uint16_t limb_bits = 16 - (remaining_bytes == 16 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + if (remaining_bytes == 16 / CHAR_BIT && is_signed) { + int16_t res_limb; + int16_t tmp_limb; + int16_t lhs_limb; + int16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i16(&res_limb, tmp_limb, overflow ? INT16_C(1) : INT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint16_t res_limb; + uint16_t tmp_limb; + uint16_t lhs_limb; + uint16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u16(&res_limb, tmp_limb, overflow ? UINT16_C(1) : UINT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { + uint16_t limb_bits = 8 - (remaining_bytes == 8 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + if (remaining_bytes == 8 / CHAR_BIT && is_signed) { + int8_t res_limb; + int8_t tmp_limb; + int8_t lhs_limb; + int8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i8(&res_limb, tmp_limb, overflow ? INT8_C(1) : INT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint8_t res_limb; + uint8_t tmp_limb; + uint8_t lhs_limb; + uint8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u8(&res_limb, tmp_limb, overflow ? UINT8_C(1) : UINT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return overflow; +} + +static inline bool zig_subo_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + uint8_t *res_bytes = res; + const uint8_t *lhs_bytes = lhs; + const uint8_t *rhs_bytes = rhs; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t top_bits = remaining_bytes * 8 - bits; + bool overflow = false; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { + uint16_t limb_bits = 128 - (remaining_bytes == 128 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + if (remaining_bytes == 128 / CHAR_BIT && is_signed) { + zig_i128 res_limb; + zig_i128 tmp_limb; + zig_i128 lhs_limb; + zig_i128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i128(&res_limb, tmp_limb, zig_make_i128(INT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + zig_u128 res_limb; + zig_u128 tmp_limb; + zig_u128 lhs_limb; + zig_u128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u128(&res_limb, tmp_limb, zig_make_u128(UINT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { + uint16_t limb_bits = 64 - (remaining_bytes == 64 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + if (remaining_bytes == 64 / CHAR_BIT && is_signed) { + int64_t res_limb; + int64_t tmp_limb; + int64_t lhs_limb; + int64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i64(&res_limb, tmp_limb, overflow ? INT64_C(1) : INT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint64_t res_limb; + uint64_t tmp_limb; + uint64_t lhs_limb; + uint64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u64(&res_limb, tmp_limb, overflow ? UINT64_C(1) : UINT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { + uint16_t limb_bits = 32 - (remaining_bytes == 32 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + if (remaining_bytes == 32 / CHAR_BIT && is_signed) { + int32_t res_limb; + int32_t tmp_limb; + int32_t lhs_limb; + int32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i32(&res_limb, tmp_limb, overflow ? INT32_C(1) : INT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint32_t res_limb; + uint32_t tmp_limb; + uint32_t lhs_limb; + uint32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u32(&res_limb, tmp_limb, overflow ? UINT32_C(1) : UINT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { + uint16_t limb_bits = 16 - (remaining_bytes == 16 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + if (remaining_bytes == 16 / CHAR_BIT && is_signed) { + int16_t res_limb; + int16_t tmp_limb; + int16_t lhs_limb; + int16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i16(&res_limb, tmp_limb, overflow ? INT16_C(1) : INT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint16_t res_limb; + uint16_t tmp_limb; + uint16_t lhs_limb; + uint16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u16(&res_limb, tmp_limb, overflow ? UINT16_C(1) : UINT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { + uint16_t limb_bits = 8 - (remaining_bytes == 8 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + if (remaining_bytes == 8 / CHAR_BIT && is_signed) { + int8_t res_limb; + int8_t tmp_limb; + int8_t lhs_limb; + int8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i8(&res_limb, tmp_limb, overflow ? INT8_C(1) : INT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint8_t res_limb; + uint8_t tmp_limb; + uint8_t lhs_limb; + uint8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u8(&res_limb, tmp_limb, overflow ? UINT8_C(1) : UINT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return overflow; +} + +static inline void zig_addw_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + (void)zig_addo_big(res, lhs, rhs, is_signed, bits); +} + +static inline void zig_subw_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + (void)zig_subo_big(res, lhs, rhs, is_signed, bits); +} + static inline uint16_t zig_clz_big(const void *val, bool is_signed, uint16_t bits) { const uint8_t *val_bytes = val; uint16_t byte_offset = 0; @@ -3092,80 +3386,6 @@ zig_msvc_atomics_128op(u128, max) #endif /* _MSC_VER && (_M_IX86 || _M_X64) */ -/* ============================= Vector Support ============================= */ - -#define zig_cmp_vec(operation, operator) \ - static inline void zig_##operation##_vec(bool *result, const void *lhs, const void *rhs, uint32_t len, bool is_signed, uint16_t elem_bits) { \ - uint32_t index = 0; \ - const uint8_t *lhs_ptr = lhs; \ - const uint8_t *rhs_ptr = rhs; \ - uint16_t elem_bytes = zig_int_bytes(elem_bits); \ - \ - while (index < len) { \ - result[index] = zig_cmp_big(lhs_ptr, rhs_ptr, is_signed, elem_bits) operator 0; \ - lhs_ptr += elem_bytes; \ - rhs_ptr += elem_bytes; \ - index += 1; \ - } \ - } -zig_cmp_vec(eq, ==) -zig_cmp_vec(ne, !=) -zig_cmp_vec(lt, < ) -zig_cmp_vec(le, <=) -zig_cmp_vec(gt, > ) -zig_cmp_vec(ge, >=) - -static inline void zig_clz_vec(void *result, const void *val, uint32_t len, bool is_signed, uint16_t elem_bits) { - uint32_t index = 0; - const uint8_t *val_ptr = val; - uint16_t elem_bytes = zig_int_bytes(elem_bits); - - while (index < len) { - uint16_t lz = zig_clz_big(val_ptr, is_signed, elem_bits); - if (elem_bits <= 128) { - ((uint8_t *)result)[index] = (uint8_t)lz; - } else { - ((uint16_t *)result)[index] = lz; - } - val_ptr += elem_bytes; - index += 1; - } -} - -static inline void zig_ctz_vec(void *result, const void *val, uint32_t len, bool is_signed, uint16_t elem_bits) { - uint32_t index = 0; - const uint8_t *val_ptr = val; - uint16_t elem_bytes = zig_int_bytes(elem_bits); - - while (index < len) { - uint16_t tz = zig_ctz_big(val_ptr, is_signed, elem_bits); - if (elem_bits <= 128) { - ((uint8_t *)result)[index] = (uint8_t)tz; - } else { - ((uint16_t *)result)[index] = tz; - } - val_ptr += elem_bytes; - index += 1; - } -} - -static inline void zig_popcount_vec(void *result, const void *val, uint32_t len, bool is_signed, uint16_t elem_bits) { - uint32_t index = 0; - const uint8_t *val_ptr = val; - uint16_t elem_bytes = zig_int_bytes(elem_bits); - - while (index < len) { - uint16_t pc = zig_popcount_big(val_ptr, is_signed, elem_bits); - if (elem_bits <= 128) { - ((uint8_t *)result)[index] = (uint8_t)pc; - } else { - ((uint16_t *)result)[index] = pc; - } - val_ptr += elem_bytes; - index += 1; - } -} - /* ======================== Special Case Intrinsics ========================= */ #if (_MSC_VER && _M_X64) || defined(__x86_64__) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index b8606b1a17..5e92a6f76c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -444,8 +444,8 @@ pub const Function = struct { return f.object.dg.renderType(w, t); } - fn renderIntCast(f: *Function, w: anytype, dest_ty: Type, src: CValue, src_ty: Type, location: ValueRenderLocation) !void { - return f.object.dg.renderIntCast(w, dest_ty, .{ .c_value = .{ .f = f, .value = src } }, src_ty, location); + fn renderIntCast(f: *Function, w: anytype, dest_ty: Type, src: CValue, v: Vectorizer, src_ty: Type, location: ValueRenderLocation) !void { + return f.object.dg.renderIntCast(w, dest_ty, .{ .c_value = .{ .f = f, .value = src, .v = v } }, src_ty, location); } fn fmtIntLiteral(f: *Function, ty: Type, val: Value) !std.fmt.Formatter(formatIntLiteral) { @@ -1593,6 +1593,7 @@ pub const DeclGen = struct { c_value: struct { f: *Function, value: CValue, + v: Vectorizer, }, value: struct { value: Value, @@ -1602,6 +1603,7 @@ pub const DeclGen = struct { switch (self.*) { .c_value => |v| { try v.f.writeCValue(w, v.value, location); + try v.v.elem(v.f, w); }, .value => |v| { try dg.renderValue(w, value_ty, v.value, location); @@ -1887,7 +1889,6 @@ pub const DeclGen = struct { if (cty.isFloat()) cty.floatActiveBits(dg.module.getTarget()) else dg.byteSize(cty) * 8, }), .array => try writer.writeAll("big"), - .vector => try writer.writeAll("vec"), } } @@ -1895,34 +1896,19 @@ pub const DeclGen = struct { switch (info) { .none => {}, .bits => { - const cty = try dg.typeToCType(ty, .complete); - if (cty.castTag(.vector)) |pl| { - var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = pl.data.len }; - try writer.print(", {}", .{try dg.fmtIntLiteral( - Type.u32, - Value.initPayload(&len_pl.base), - .FunctionArgument, - )}); - } - const target = dg.module.getTarget(); - const elem_ty = ty.shallowElemType(); - const elem_info = if (elem_ty.isAbiInt()) - elem_ty.intInfo(target) - else - std.builtin.Type.Int{ - .signedness = .unsigned, - .bits = @intCast(u16, elem_ty.bitSize(target)), - }; - switch (cty.tag()) { - else => {}, - .array, .vector => try writer.print(", {}", .{elem_info.signedness == .signed}), - } + const int_info = if (ty.isAbiInt()) ty.intInfo(target) else std.builtin.Type.Int{ + .signedness = .unsigned, + .bits = @intCast(u16, ty.bitSize(target)), + }; - var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = elem_info.bits }; + const cty = try dg.typeToCType(ty, .complete); + if (cty.tag() == .array) try writer.print(", {}", .{int_info.signedness == .signed}); + + var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = int_info.bits }; try writer.print(", {}", .{try dg.fmtIntLiteral(switch (cty.tag()) { else => Type.u8, - .array, .vector => Type.u16, + .array => Type.u16, }, Value.initPayload(&bits_pl.base), .FunctionArgument)}); }, } @@ -2786,10 +2772,10 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .div_trunc, .div_exact => try airBinOp(f, inst, "/", "div_trunc", .none), .rem => blk: { const bin_op = f.air.instructions.items(.data)[inst].bin_op; - const lhs_ty = f.air.typeOf(bin_op.lhs); + const lhs_scalar_ty = f.air.typeOf(bin_op.lhs).scalarType(); // For binary operations @TypeOf(lhs)==@TypeOf(rhs), // so we only check one. - break :blk if (lhs_ty.isInt()) + break :blk if (lhs_scalar_ty.isInt()) try airBinOp(f, inst, "%", "rem", .none) else try airBinFloatOp(f, inst, "fmod"); @@ -2833,10 +2819,10 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .slice => try airSlice(f, inst), - .cmp_gt => try airCmpOp(f, inst, .gt), - .cmp_gte => try airCmpOp(f, inst, .gte), - .cmp_lt => try airCmpOp(f, inst, .lt), - .cmp_lte => try airCmpOp(f, inst, .lte), + .cmp_gt => try airCmpOp(f, inst, f.air.instructions.items(.data)[inst].bin_op, .gt), + .cmp_gte => try airCmpOp(f, inst, f.air.instructions.items(.data)[inst].bin_op, .gte), + .cmp_lt => try airCmpOp(f, inst, f.air.instructions.items(.data)[inst].bin_op, .lt), + .cmp_lte => try airCmpOp(f, inst, f.air.instructions.items(.data)[inst].bin_op, .lte), .cmp_eq => try airEquality(f, inst, .eq), .cmp_neq => try airEquality(f, inst, .neq), @@ -2844,7 +2830,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .cmp_vector => blk: { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.VectorCmp, ty_pl.payload).data; - break :blk try airCmpBuiltinCall(f, inst, extra, extra.compareOperator(), .operator, .bits,); + break :blk try airCmpOp(f, inst, extra, extra.compareOperator()); }, .cmp_lt_errors_len => try airCmpLtErrorsLen(f, inst), @@ -3294,7 +3280,10 @@ fn airArg(f: *Function, inst: Air.Inst.Index) !CValue { fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { const ty_op = f.air.instructions.items(.data)[inst].ty_op; - const ptr_info = f.air.typeOf(ty_op.operand).ptrInfo().data; + + const ptr_ty = f.air.typeOf(ty_op.operand); + const ptr_scalar_ty = ptr_ty.scalarType(); + const ptr_info = ptr_scalar_ty.ptrInfo().data; const src_ty = ptr_info.pointee_type; if (!src_ty.hasRuntimeBitsIgnoreComptime() or @@ -3312,16 +3301,19 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { const is_aligned = ptr_info.@"align" == 0 or ptr_info.@"align" >= src_ty.abiAlignment(target); const is_array = lowersToArray(src_ty, target); const need_memcpy = !is_aligned or is_array; - const writer = f.object.writer(); + const writer = f.object.writer(); const local = try f.allocLocal(inst, src_ty); + const v = try Vectorizer.start(f, inst, writer, ptr_ty); if (need_memcpy) { try writer.writeAll("memcpy("); if (!is_array) try writer.writeByte('&'); - try f.writeCValue(writer, local, .FunctionArgument); + try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(", (const char *)"); try f.writeCValue(writer, operand, .Other); + try v.elem(f, writer); try writer.writeAll(", sizeof("); try f.renderType(writer, src_ty); try writer.writeAll("))"); @@ -3351,6 +3343,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { const field_ty = Type.initPayload(&field_pl.base); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = ("); try f.renderType(writer, src_ty); try writer.writeAll(")zig_wrap_"); @@ -3369,16 +3362,21 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeByte('('); try f.writeCValueDeref(writer, operand); + try v.elem(f, writer); try writer.print(", {})", .{try f.fmtIntLiteral(bit_offset_ty, bit_offset_val)}); if (cant_cast) try writer.writeByte(')'); try f.object.dg.renderBuiltinInfo(writer, field_ty, .bits); try writer.writeByte(')'); } else { try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); try f.writeCValueDeref(writer, operand); + try v.elem(f, writer); } try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return local; } @@ -3444,15 +3442,22 @@ fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue { const operand = try f.resolveInst(ty_op.operand); try reap(f, inst, &.{ty_op.operand}); - const writer = f.object.writer(); - const inst_ty = f.air.typeOfIndex(inst); - const local = try f.allocLocal(inst, inst_ty); - const operand_ty = f.air.typeOf(ty_op.operand); + const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + const operand_ty = f.air.typeOf(ty_op.operand); + const scalar_ty = operand_ty.scalarType(); + + const writer = f.object.writer(); + const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); - try f.renderIntCast(writer, inst_ty, operand, operand_ty, .Other); + try f.renderIntCast(writer, inst_scalar_ty, operand, v, scalar_ty, .Other); try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return local; } @@ -3578,7 +3583,10 @@ fn storeUndefined(f: *Function, lhs_child_ty: Type, dest_ptr: CValue) !CValue { fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { // *a = b; const bin_op = f.air.instructions.items(.data)[inst].bin_op; - const ptr_info = f.air.typeOf(bin_op.lhs).ptrInfo().data; + + const ptr_ty = f.air.typeOf(bin_op.lhs); + const ptr_scalar_ty = ptr_ty.scalarType(); + const ptr_info = ptr_scalar_ty.ptrInfo().data; if (!ptr_info.pointee_type.hasRuntimeBitsIgnoreComptime()) { try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); return .none; @@ -3601,11 +3609,13 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { ptr_info.@"align" >= ptr_info.pointee_type.abiAlignment(target); const is_array = lowersToArray(ptr_info.pointee_type, target); const need_memcpy = !is_aligned or is_array; - const writer = f.object.writer(); const src_val = try f.resolveInst(bin_op.rhs); try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); + const writer = f.object.writer(); + const v = try Vectorizer.start(f, inst, writer, ptr_ty); + if (need_memcpy) { // For this memcpy to safely work we need the rhs to have the same // underlying type as the lhs (i.e. they must both be arrays of the same underlying type). @@ -3626,9 +3636,11 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll("memcpy((char *)"); try f.writeCValue(writer, ptr_val, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); if (!is_array) try writer.writeByte('&'); try f.writeCValue(writer, array_src, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", sizeof("); try f.renderType(writer, src_ty); try writer.writeAll("))"); @@ -3672,12 +3684,14 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { const mask_val = Value.initPayload(&mask_pl.base); try f.writeCValueDeref(writer, ptr_val); + try v.elem(f, writer); try writer.writeAll(" = zig_or_"); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeAll("(zig_and_"); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeByte('('); try f.writeCValueDeref(writer, ptr_val); + try v.elem(f, writer); try writer.print(", {x}), zig_shl_", .{try f.fmtIntLiteral(host_ty, mask_val)}); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeByte('('); @@ -3699,14 +3713,19 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeByte(')'); } try f.writeCValue(writer, src_val, .Other); + try v.elem(f, writer); if (cant_cast) try writer.writeByte(')'); try writer.print(", {}))", .{try f.fmtIntLiteral(bit_offset_ty, bit_offset_val)}); } else { try f.writeCValueDeref(writer, ptr_val); + try v.elem(f, writer); try writer.writeAll(" = "); try f.writeCValue(writer, src_val, .Other); + try v.elem(f, writer); } try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return .none; } @@ -3724,51 +3743,39 @@ fn airOverflow(f: *Function, inst: Air.Inst.Index, operation: []const u8, info: try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); const inst_ty = f.air.typeOfIndex(inst); - const vector_ty = f.air.typeOf(bin_op.lhs); - const scalar_ty = vector_ty.scalarType(); + const operand_ty = f.air.typeOf(bin_op.lhs); + const scalar_ty = operand_ty.scalarType(); + const w = f.object.writer(); - const local = try f.allocLocal(inst, inst_ty); - - switch (vector_ty.zigTypeTag()) { - .Vector => { - try w.writeAll("zig_v"); - try w.writeAll(operation); - try w.writeAll("o_"); - try f.object.dg.renderTypeForBuiltinFnName(w, scalar_ty); - try w.writeAll("("); - try f.writeCValueMember(w, local, .{ .field = 1 }); - try w.writeAll(", "); - try f.writeCValueMember(w, local, .{ .field = 0 }); - try w.print(", {d}, ", .{vector_ty.vectorLen()}); - }, - else => { - try f.writeCValueMember(w, local, .{ .field = 1 }); - try w.writeAll(" = zig_"); - try w.writeAll(operation); - try w.writeAll("o_"); - try f.object.dg.renderTypeForBuiltinFnName(w, scalar_ty); - try w.writeAll("(&"); - try f.writeCValueMember(w, local, .{ .field = 0 }); - try w.writeAll(", "); - }, - } - + const v = try Vectorizer.start(f, inst, w, operand_ty); + try f.writeCValueMember(w, local, .{ .field = 1 }); + try v.elem(f, w); + try w.writeAll(" = zig_"); + try w.writeAll(operation); + try w.writeAll("o_"); + try f.object.dg.renderTypeForBuiltinFnName(w, scalar_ty); + try w.writeAll("(&"); + try f.writeCValueMember(w, local, .{ .field = 0 }); + try v.elem(f, w); + try w.writeAll(", "); try f.writeCValue(w, lhs, .FunctionArgument); + try v.elem(f, w); try w.writeAll(", "); try f.writeCValue(w, rhs, .FunctionArgument); + try v.elem(f, w); try f.object.dg.renderBuiltinInfo(w, scalar_ty, info); try w.writeAll(");\n"); + try v.end(f, inst, w); return local; } fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { - const inst_ty = f.air.typeOfIndex(inst); - if (inst_ty.tag() != .bool) - return try airUnBuiltinCall(f, inst, "not", .bits); - const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand_ty = f.air.typeOf(ty_op.operand); + const scalar_ty = operand_ty.scalarType(); + if (scalar_ty.tag() != .bool) return try airUnBuiltinCall(f, inst, "not", .bits); if (f.liveness.isUnused(inst)) { try reap(f, inst, &.{ty_op.operand}); @@ -3778,14 +3785,20 @@ fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { const op = try f.resolveInst(ty_op.operand); try reap(f, inst, &.{ty_op.operand}); + const inst_ty = f.air.typeOfIndex(inst); + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); - + const v = try Vectorizer.start(f, inst, writer, operand_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); try writer.writeByte('!'); try f.writeCValue(writer, op, .Other); + try v.elem(f, writer); try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return local; } @@ -3798,71 +3811,89 @@ fn airBinOp( ) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const operand_ty = f.air.typeOf(bin_op.lhs); + const scalar_ty = operand_ty.scalarType(); const target = f.object.dg.module.getTarget(); - if ((operand_ty.isInt() and operand_ty.bitSize(target) > 64) or operand_ty.isRuntimeFloat()) + if ((scalar_ty.isInt() and scalar_ty.bitSize(target) > 64) or scalar_ty.isRuntimeFloat()) return try airBinBuiltinCall(f, inst, operation, info); - const lhs = try f.resolveInst(bin_op.lhs); - const rhs = try f.resolveInst(bin_op.rhs); - - try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); - - if (f.liveness.isUnused(inst)) return .none; - - const inst_ty = f.air.typeOfIndex(inst); - - const writer = f.object.writer(); - const local = try f.allocLocal(inst, inst_ty); - try f.writeCValue(writer, local, .Other); - try writer.writeAll(" = "); - try f.writeCValue(writer, lhs, .Other); - try writer.writeByte(' '); - try writer.writeAll(operator); - try writer.writeByte(' '); - try f.writeCValue(writer, rhs, .Other); - try writer.writeAll(";\n"); - - return local; -} - -fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: std.math.CompareOperator) !CValue { - const bin_op = f.air.instructions.items(.data)[inst].bin_op; - if (f.liveness.isUnused(inst)) { try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); return .none; } - const operand_ty = f.air.typeOf(bin_op.lhs); - const target = f.object.dg.module.getTarget(); - const operand_bits = operand_ty.bitSize(target); - if (operand_ty.isInt() and operand_bits > 64) - return airCmpBuiltinCall( - f, - inst, - bin_op, - operator, - .cmp, - if (operand_bits > 128) .bits else .none, - ); - if (operand_ty.isRuntimeFloat()) - return airCmpBuiltinCall(f, inst, bin_op, operator, .operator, .none); - - const inst_ty = f.air.typeOfIndex(inst); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); + const inst_ty = f.air.typeOfIndex(inst); + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); + try writer.writeByte(' '); + try writer.writeAll(operator); + try writer.writeByte(' '); + try f.writeCValue(writer, rhs, .Other); + try v.elem(f, writer); + try writer.writeAll(";\n"); + try v.end(f, inst, writer); + + return local; +} + +fn airCmpOp( + f: *Function, + inst: Air.Inst.Index, + data: anytype, + operator: std.math.CompareOperator, +) !CValue { + if (f.liveness.isUnused(inst)) { + try reap(f, inst, &.{ data.lhs, data.rhs }); + return .none; + } + + const operand_ty = f.air.typeOf(data.lhs); + const scalar_ty = operand_ty.scalarType(); + + const target = f.object.dg.module.getTarget(); + const scalar_bits = scalar_ty.bitSize(target); + if (scalar_ty.isInt() and scalar_bits > 64) + return airCmpBuiltinCall( + f, + inst, + data, + operator, + .cmp, + if (scalar_bits > 128) .bits else .none, + ); + if (scalar_ty.isRuntimeFloat()) + return airCmpBuiltinCall(f, inst, data, operator, .operator, .none); + + const inst_ty = f.air.typeOfIndex(inst); + const lhs = try f.resolveInst(data.lhs); + const rhs = try f.resolveInst(data.rhs); + try reap(f, inst, &.{ data.lhs, data.rhs }); + + const writer = f.object.writer(); + const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); + try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); + try writer.writeAll(" = "); + try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); try writer.writeByte(' '); try writer.writeAll(compareOperatorC(operator)); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); + try v.elem(f, writer); try writer.writeAll(";\n"); + try v.end(f, inst, writer); return local; } @@ -3974,11 +4005,14 @@ fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: u8) !CValue { try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); const inst_ty = f.air.typeOfIndex(inst); - const elem_ty = inst_ty.elemType2(); + const inst_scalar_ty = inst_ty.scalarType(); + const elem_ty = inst_scalar_ty.elemType2(); const local = try f.allocLocal(inst, inst_ty); const writer = f.object.writer(); + const v = try Vectorizer.start(f, inst, writer, inst_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); if (elem_ty.hasRuntimeBitsIgnoreComptime()) { @@ -3986,19 +4020,26 @@ fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: u8) !CValue { // results in a NULL pointer, or if LHS is NULL. The operation is only UB // if the result is NULL and then dereferenced. try writer.writeByte('('); - try f.renderType(writer, inst_ty); + try f.renderType(writer, inst_scalar_ty); try writer.writeAll(")(((uintptr_t)"); try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); try writer.writeAll(") "); try writer.writeByte(operator); try writer.writeAll(" ("); try f.writeCValue(writer, rhs, .Other); + try v.elem(f, writer); try writer.writeAll("*sizeof("); try f.renderType(writer, elem_ty); try writer.writeAll(")))"); - } else try f.writeCValue(writer, lhs, .Initializer); + } else { + try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); + } try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return local; } @@ -4011,10 +4052,12 @@ fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: u8, operation: []cons } const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + const target = f.object.dg.module.getTarget(); - if (inst_ty.isInt() and inst_ty.bitSize(target) > 64) + if (inst_scalar_ty.isInt() and inst_scalar_ty.bitSize(target) > 64) return try airBinBuiltinCall(f, inst, operation[1..], .none); - if (inst_ty.isRuntimeFloat()) + if (inst_scalar_ty.isRuntimeFloat()) return try airBinFloatOp(f, inst, operation); const lhs = try f.resolveInst(bin_op.lhs); @@ -4023,19 +4066,26 @@ fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: u8, operation: []cons const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, inst_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); // (lhs <> rhs) ? lhs : rhs try writer.writeAll(" = ("); try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); try writer.writeByte(' '); try writer.writeByte(operator); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); + try v.elem(f, writer); try writer.writeAll(") ? "); try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); try writer.writeAll(" : "); try f.writeCValue(writer, rhs, .Other); + try v.elem(f, writer); try writer.writeAll(";\n"); + try v.end(f, inst, writer); return local; } @@ -6002,30 +6052,35 @@ fn airUnBuiltinCall( const operand = try f.resolveInst(ty_op.operand); try reap(f, inst, &.{ty_op.operand}); const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); const operand_ty = f.air.typeOf(ty_op.operand); + const scalar_ty = operand_ty.scalarType(); - const inst_cty = try f.typeToCType(inst_ty, .complete); - const ref_ret = switch (inst_cty.tag()) { - else => false, - .array, .vector => true, - }; + const inst_scalar_cty = try f.typeToCType(inst_scalar_ty, .complete); + const ref_ret = inst_scalar_cty.tag() == .array; const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); if (!ref_ret) { try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); } try writer.print("zig_{s}_", .{operation}); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); if (ref_ret) { try f.writeCValue(writer, local, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); } try f.writeCValue(writer, operand, .FunctionArgument); - try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); + try v.elem(f, writer); + try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info); try writer.writeAll(");\n"); + try v.end(f, inst, writer); + return local; } @@ -6047,21 +6102,38 @@ fn airBinBuiltinCall( try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); const operand_ty = f.air.typeOf(bin_op.lhs); + const scalar_ty = operand_ty.scalarType(); + + const inst_scalar_cty = try f.typeToCType(inst_scalar_ty, .complete); + const ref_ret = inst_scalar_cty.tag() == .array; const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); - try f.writeCValue(writer, local, .Other); - try writer.writeAll(" = zig_"); - try writer.writeAll(operation); - try writer.writeByte('_'); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); + if (!ref_ret) { + try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); + try writer.writeAll(" = "); + } + try writer.print("zig_{s}_", .{operation}); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); + if (ref_ret) { + try f.writeCValue(writer, local, .FunctionArgument); + try v.elem(f, writer); + try writer.writeAll(", "); + } try f.writeCValue(writer, lhs, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); try f.writeCValue(writer, rhs, .FunctionArgument); - try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); + try v.elem(f, writer); + try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info); try writer.writeAll(");\n"); + try v.end(f, inst, writer); + return local; } @@ -6073,45 +6145,56 @@ fn airCmpBuiltinCall( operation: enum { cmp, operator }, info: BuiltinInfo, ) !CValue { - const inst_ty = f.air.typeOfIndex(inst); - const operand_ty = f.air.typeOf(data.lhs); + if (f.liveness.isUnused(inst)) { + try reap(f, inst, &.{ data.lhs, data.rhs }); + return .none; + } const lhs = try f.resolveInst(data.lhs); const rhs = try f.resolveInst(data.rhs); try reap(f, inst, &.{ data.lhs, data.rhs }); - const inst_cty = try f.typeToCType(inst_ty, .complete); - const ref_ret = switch (inst_cty.tag()) { - else => false, - .array, .vector => true, - }; + const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + const operand_ty = f.air.typeOf(data.lhs); + const scalar_ty = operand_ty.scalarType(); + + const inst_scalar_cty = try f.typeToCType(inst_scalar_ty, .complete); + const ref_ret = inst_scalar_cty.tag() == .array; const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); if (!ref_ret) { try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); } try writer.print("zig_{s}_", .{switch (operation) { else => @tagName(operation), .operator => compareOperatorAbbrev(operator), }}); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); if (ref_ret) { try f.writeCValue(writer, local, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); } try f.writeCValue(writer, lhs, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); try f.writeCValue(writer, rhs, .FunctionArgument); - try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); + try v.elem(f, writer); + try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info); try writer.writeByte(')'); if (!ref_ret) try writer.print(" {s} {}", .{ compareOperatorC(operator), try f.fmtIntLiteral(Type.initTag(.i32), Value.zero), }); try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return local; } @@ -6498,65 +6581,35 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { const operand = try f.resolveInst(reduce.operand); try reap(f, inst, &.{reduce.operand}); const operand_ty = f.air.typeOf(reduce.operand); - const vector_len = operand_ty.vectorLen(); const writer = f.object.writer(); - const Op = union(enum) { - call_fn: []const u8, + const op: union(enum) { + float_op: []const u8, + builtin: []const u8, infix: []const u8, ternary: []const u8, - }; - var fn_name_buf: [64]u8 = undefined; - const op: Op = switch (reduce.operation) { + } = switch (reduce.operation) { .And => .{ .infix = " &= " }, .Or => .{ .infix = " |= " }, .Xor => .{ .infix = " ^= " }, .Min => switch (scalar_ty.zigTypeTag()) { - .Int => Op{ .ternary = " < " }, - .Float => op: { - const float_bits = scalar_ty.floatBits(target); - break :op Op{ - .call_fn = std.fmt.bufPrintZ(&fn_name_buf, "{s}fmin{s}", .{ - libcFloatPrefix(float_bits), libcFloatSuffix(float_bits), - }) catch unreachable, - }; - }, + .Int => .{ .ternary = " < " }, + .Float => .{ .float_op = "fmin" }, else => unreachable, }, .Max => switch (scalar_ty.zigTypeTag()) { - .Int => Op{ .ternary = " > " }, - .Float => op: { - const float_bits = scalar_ty.floatBits(target); - break :op Op{ - .call_fn = std.fmt.bufPrintZ(&fn_name_buf, "{s}fmax{s}", .{ - libcFloatPrefix(float_bits), libcFloatSuffix(float_bits), - }) catch unreachable, - }; - }, + .Int => .{ .ternary = " > " }, + .Float => .{ .float_op = "fmax" }, else => unreachable, }, .Add => switch (scalar_ty.zigTypeTag()) { - .Int => Op{ .infix = " += " }, - .Float => op: { - const float_bits = scalar_ty.floatBits(target); - break :op Op{ - .call_fn = std.fmt.bufPrintZ(&fn_name_buf, "__add{s}f3", .{ - compilerRtFloatAbbrev(float_bits), - }) catch unreachable, - }; - }, + .Int => .{ .infix = " += " }, + .Float => .{ .builtin = "add" }, else => unreachable, }, .Mul => switch (scalar_ty.zigTypeTag()) { - .Int => Op{ .infix = " *= " }, - .Float => op: { - const float_bits = scalar_ty.floatBits(target); - break :op Op{ - .call_fn = std.fmt.bufPrintZ(&fn_name_buf, "__mul{s}f3", .{ - compilerRtFloatAbbrev(float_bits), - }) catch unreachable, - }; - }, + .Int => .{ .infix = " *= " }, + .Float => .{ .builtin = "mul" }, else => unreachable, }, }; @@ -6572,75 +6625,94 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { // } // break :reduce accum; // } - const it = try f.allocLocal(inst, Type.usize); - try f.writeCValue(writer, it, .Other); - try writer.writeAll(" = 0;\n"); const accum = try f.allocLocal(inst, scalar_ty); try f.writeCValue(writer, accum, .Other); try writer.writeAll(" = "); - const init_val = switch (reduce.operation) { - .And, .Or, .Xor, .Add => "0", + var arena = std.heap.ArenaAllocator.init(f.object.dg.gpa); + defer arena.deinit(); + + const ExpectedContents = union { + u: Value.Payload.U64, + i: Value.Payload.I64, + f16: Value.Payload.Float_16, + f32: Value.Payload.Float_32, + f64: Value.Payload.Float_64, + f80: Value.Payload.Float_80, + f128: Value.Payload.Float_128, + }; + var stack align(@alignOf(ExpectedContents)) = + std.heap.stackFallback(@sizeOf(ExpectedContents), arena.allocator()); + + try f.object.dg.renderValue(writer, scalar_ty, switch (reduce.operation) { + .Or, .Xor, .Add => Value.zero, + .And => switch (scalar_ty.zigTypeTag()) { + .Bool => Value.one, + else => switch (scalar_ty.intInfo(target).signedness) { + .unsigned => try scalar_ty.maxInt(stack.get(), target), + .signed => Value.negative_one, + }, + }, .Min => switch (scalar_ty.zigTypeTag()) { - .Int => "TODO_intmax", - .Float => "TODO_nan", + .Bool => Value.one, + .Int => try scalar_ty.maxInt(stack.get(), target), + .Float => try Value.floatToValue(std.math.nan(f128), stack.get(), scalar_ty, target), else => unreachable, }, .Max => switch (scalar_ty.zigTypeTag()) { - .Int => "TODO_intmin", - .Float => "TODO_nan", + .Bool => Value.zero, + .Int => try scalar_ty.minInt(stack.get(), target), + .Float => try Value.floatToValue(std.math.nan(f128), stack.get(), scalar_ty, target), else => unreachable, }, - .Mul => "1", - }; - try writer.writeAll(init_val); - try writer.writeAll(";"); - try f.object.indent_writer.insertNewline(); - try writer.writeAll("for (;"); - try f.writeCValue(writer, it, .Other); - try writer.print("<{d};++", .{vector_len}); - try f.writeCValue(writer, it, .Other); - try writer.writeAll(") "); - try f.writeCValue(writer, accum, .Other); + .Mul => Value.one, + }, .Initializer); + try writer.writeAll(";\n"); + const v = try Vectorizer.start(f, inst, writer, operand_ty); + try f.writeCValue(writer, accum, .Other); switch (op) { - .call_fn => |fn_name| { - try writer.print(" = {s}(", .{fn_name}); + .float_op => |operation| { + try writer.writeAll(" = zig_libc_name_"); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); + try writer.print("({s})(", .{operation}); try f.writeCValue(writer, accum, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, operand, .Other); - try writer.writeAll("["); - try f.writeCValue(writer, it, .Other); - try writer.writeAll("])"); + try v.elem(f, writer); + try writer.writeByte(')'); + }, + .builtin => |operation| { + try writer.print(" = zig_{s}_", .{operation}); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); + try writer.writeByte('('); + try f.writeCValue(writer, accum, .FunctionArgument); + try writer.writeAll(", "); + try f.writeCValue(writer, operand, .Other); + try v.elem(f, writer); + try writer.writeByte(')'); }, .infix => |ass| { try writer.writeAll(ass); try f.writeCValue(writer, operand, .Other); - try writer.writeAll("["); - try f.writeCValue(writer, it, .Other); - try writer.writeAll("]"); + try v.elem(f, writer); }, .ternary => |cmp| { try writer.writeAll(" = "); try f.writeCValue(writer, accum, .Other); try writer.writeAll(cmp); try f.writeCValue(writer, operand, .Other); - try writer.writeAll("["); - try f.writeCValue(writer, it, .Other); - try writer.writeAll("] ? "); + try v.elem(f, writer); + try writer.writeAll(" ? "); try f.writeCValue(writer, accum, .Other); try writer.writeAll(" : "); try f.writeCValue(writer, operand, .Other); - try writer.writeAll("["); - try f.writeCValue(writer, it, .Other); - try writer.writeAll("]"); + try v.elem(f, writer); }, } - try writer.writeAll(";\n"); - - try freeLocal(f, inst, it.new_local, 0); + try v.end(f, inst, writer); return accum; } @@ -6774,7 +6846,7 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeByte('('); if (inst_ty.isAbiInt() and (field_ty.isAbiInt() or field_ty.isPtrAtRuntime())) { - try f.renderIntCast(writer, inst_ty, element, field_ty, .FunctionArgument); + try f.renderIntCast(writer, inst_ty, element, .{}, field_ty, .FunctionArgument); } else { try writer.writeByte('('); try f.renderType(writer, inst_ty); @@ -6916,7 +6988,6 @@ fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue { } fn airFloatNeg(f: *Function, inst: Air.Inst.Index) !CValue { - const inst_ty = f.air.typeOfIndex(inst); const un_op = f.air.instructions.items(.data)[inst].un_op; if (f.liveness.isUnused(inst)) { try reap(f, inst, &.{un_op}); @@ -6925,16 +6996,23 @@ fn airFloatNeg(f: *Function, inst: Air.Inst.Index) !CValue { const operand = try f.resolveInst(un_op); try reap(f, inst, &.{un_op}); + const operand_ty = f.air.typeOf(un_op); + const scalar_ty = operand_ty.scalarType(); const writer = f.object.writer(); - const local = try f.allocLocal(inst, inst_ty); + const local = try f.allocLocal(inst, operand_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = zig_neg_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); try f.writeCValue(writer, operand, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(");\n"); + try v.end(f, inst, writer); + return local; } @@ -6944,19 +7022,28 @@ fn airUnFloatOp(f: *Function, inst: Air.Inst.Index, operation: []const u8) !CVal try reap(f, inst, &.{un_op}); return .none; } + const operand = try f.resolveInst(un_op); try reap(f, inst, &.{un_op}); - const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, inst_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = zig_libc_name_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, inst_scalar_ty); try writer.writeByte('('); try writer.writeAll(operation); try writer.writeAll(")("); try f.writeCValue(writer, operand, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(");\n"); + try v.end(f, inst, writer); + return local; } @@ -6966,23 +7053,32 @@ fn airBinFloatOp(f: *Function, inst: Air.Inst.Index, operation: []const u8) !CVa try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); return .none; } + const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); - const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, inst_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = zig_libc_name_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, inst_scalar_ty); try writer.writeByte('('); try writer.writeAll(operation); try writer.writeAll(")("); try f.writeCValue(writer, lhs, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); try f.writeCValue(writer, rhs, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(");\n"); + try v.end(f, inst, writer); + return local; } @@ -6993,23 +7089,34 @@ fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue { try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs, pl_op.operand }); return .none; } - const inst_ty = f.air.typeOfIndex(inst); + const mulend1 = try f.resolveInst(bin_op.lhs); const mulend2 = try f.resolveInst(bin_op.rhs); const addend = try f.resolveInst(pl_op.operand); try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs, pl_op.operand }); + + const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, inst_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = zig_libc_name_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, inst_scalar_ty); try writer.writeAll("(fma)("); try f.writeCValue(writer, mulend1, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); try f.writeCValue(writer, mulend2, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(", "); try f.writeCValue(writer, addend, .FunctionArgument); + try v.elem(f, writer); try writer.writeAll(");\n"); + try v.end(f, inst, writer); + return local; } @@ -7510,6 +7617,47 @@ fn formatIntLiteral( try data.cty.renderLiteralSuffix(writer); } +const Vectorizer = struct { + index: CValue = .none, + + pub fn start(f: *Function, inst: Air.Inst.Index, writer: anytype, ty: Type) !Vectorizer { + return if (ty.zigTypeTag() == .Vector) index: { + var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = ty.vectorLen() }; + + const local = try f.allocLocal(inst, Type.usize); + + try writer.writeAll("for ("); + try f.writeCValue(writer, local, .Other); + try writer.print(" = {d}; ", .{try f.fmtIntLiteral(Type.usize, Value.zero)}); + try f.writeCValue(writer, local, .Other); + try writer.print(" < {d}; ", .{ + try f.fmtIntLiteral(Type.usize, Value.initPayload(&len_pl.base)), + }); + try f.writeCValue(writer, local, .Other); + try writer.print(" += {d}) {{\n", .{try f.fmtIntLiteral(Type.usize, Value.one)}); + f.object.indent_writer.pushIndent(); + + break :index .{ .index = local }; + } else .{}; + } + + pub fn elem(self: Vectorizer, f: *Function, writer: anytype) !void { + if (self.index != .none) { + try writer.writeByte('['); + try f.writeCValue(writer, self.index, .Other); + try writer.writeByte(']'); + } + } + + pub fn end(self: Vectorizer, f: *Function, inst: Air.Inst.Index, writer: anytype) !void { + if (self.index != .none) { + f.object.indent_writer.popIndent(); + try writer.writeAll("}\n"); + try freeLocal(f, inst, self.index.new_local, 0); + } + } +}; + fn isByRef(ty: Type) bool { _ = ty; return false; diff --git a/src/type.zig b/src/type.zig index 9e501d893c..15525f14eb 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4213,7 +4213,7 @@ pub const Type = extern union { }; } - pub fn shallowElemType(child_ty: Type) Type { + fn shallowElemType(child_ty: Type) Type { return switch (child_ty.zigTypeTag()) { .Array, .Vector => child_ty.childType(), else => child_ty, diff --git a/src/value.zig b/src/value.zig index 4a5683df36..00bf59ca38 100644 --- a/src/value.zig +++ b/src/value.zig @@ -3319,7 +3319,7 @@ pub const Value = extern union { } } - fn floatToValue(float: f128, arena: Allocator, dest_ty: Type, target: Target) !Value { + pub fn floatToValue(float: f128, arena: Allocator, dest_ty: Type, target: Target) !Value { switch (dest_ty.floatBits(target)) { 16 => return Value.Tag.float_16.create(arena, @floatCast(f16, float)), 32 => return Value.Tag.float_32.create(arena, @floatCast(f32, float)), diff --git a/test/behavior/bitreverse.zig b/test/behavior/bitreverse.zig index aa830144d1..80167b9a17 100644 --- a/test/behavior/bitreverse.zig +++ b/test/behavior/bitreverse.zig @@ -96,7 +96,6 @@ fn vector8() !void { test "bitReverse vectors u8" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -115,7 +114,6 @@ fn vector16() !void { test "bitReverse vectors u16" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -134,7 +132,6 @@ fn vector24() !void { test "bitReverse vectors u24" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; diff --git a/test/behavior/byteswap.zig b/test/behavior/byteswap.zig index fc385e0443..d173c13275 100644 --- a/test/behavior/byteswap.zig +++ b/test/behavior/byteswap.zig @@ -62,7 +62,6 @@ fn vector8() !void { test "@byteSwap vectors u8" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -81,7 +80,6 @@ fn vector16() !void { test "@byteSwap vectors u16" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -100,7 +98,6 @@ fn vector24() !void { test "@byteSwap vectors u24" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 927caa965b..f179cbe525 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -598,7 +598,6 @@ test "cast *[1][*]const u8 to [*]const ?[*]const u8" { test "vector casts" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 7befa41380..f05901f7d9 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -141,7 +141,6 @@ fn testSqrt() !void { test "@sqrt with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -234,7 +233,6 @@ fn testSin() !void { test "@sin with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -275,7 +273,6 @@ fn testCos() !void { test "@cos with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -315,7 +312,6 @@ fn testExp() !void { test "@exp with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -355,7 +351,6 @@ fn testExp2() !void { test "@exp2" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -409,7 +404,6 @@ test "@log with @vectors" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO { @@ -447,7 +441,6 @@ test "@log2 with vectors" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO // https://github.com/ziglang/zig/issues/13681 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64 and @@ -491,7 +484,6 @@ test "@log10 with vectors" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO comptime try testLog10WithVectors(); try testLog10WithVectors(); @@ -537,7 +529,6 @@ fn testFabs() !void { test "@fabs with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -660,7 +651,6 @@ fn testFloor() !void { test "@floor with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -754,7 +744,6 @@ fn testCeil() !void { test "@ceil with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -848,7 +837,6 @@ fn testTrunc() !void { test "@trunc with vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig index 133a543d42..34a7d0976a 100644 --- a/test/behavior/maximum_minimum.zig +++ b/test/behavior/maximum_minimum.zig @@ -25,7 +25,6 @@ test "@max" { test "@max on vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -75,7 +74,6 @@ test "@min for vectors" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig index a2d9e6d16d..218edc5a2d 100644 --- a/test/behavior/muladd.zig +++ b/test/behavior/muladd.zig @@ -100,7 +100,6 @@ fn vector16() !void { } test "vector f16" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -124,7 +123,6 @@ fn vector32() !void { } test "vector f32" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -148,7 +146,6 @@ fn vector64() !void { } test "vector f64" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -171,7 +168,6 @@ fn vector80() !void { } test "vector f80" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -195,7 +191,6 @@ fn vector128() !void { } test "vector f128" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index d885a7fabc..e74bcdad86 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -25,7 +25,6 @@ test "implicit cast vector to array - bool" { test "vector wrap operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -116,7 +115,6 @@ test "vector float operators" { test "vector bit operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -442,7 +440,6 @@ test "vector comparison operators" { test "vector division operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -525,7 +522,6 @@ test "vector division operators" { test "vector bitwise not operator" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -557,7 +553,6 @@ test "vector bitwise not operator" { test "vector shift operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -651,7 +646,6 @@ test "vector shift operators" { test "vector reduce operation" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -707,7 +701,7 @@ test "vector reduce operation" { // LLVM 11 ERROR: Cannot select type // https://github.com/ziglang/zig/issues/7138 - if (builtin.target.cpu.arch != .aarch64) { + if (builtin.zig_backend != .stage2_llvm or builtin.target.cpu.arch != .aarch64) { try testReduce(.Min, [4]i64{ 1234567, -386, 0, 3 }, @as(i64, -386)); try testReduce(.Min, [4]u64{ 99, 9999, 9, 99999 }, @as(u64, 9)); } @@ -725,7 +719,7 @@ test "vector reduce operation" { // LLVM 11 ERROR: Cannot select type // https://github.com/ziglang/zig/issues/7138 - if (builtin.target.cpu.arch != .aarch64) { + if (builtin.zig_backend != .stage2_llvm or builtin.target.cpu.arch != .aarch64) { try testReduce(.Max, [4]i64{ 1234567, -386, 0, 3 }, @as(i64, 1234567)); try testReduce(.Max, [4]u64{ 99, 9999, 9, 99999 }, @as(u64, 99999)); } @@ -773,14 +767,14 @@ test "vector reduce operation" { // LLVM 11 ERROR: Cannot select type // https://github.com/ziglang/zig/issues/7138 - if (false) { - try testReduce(.Min, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); - try testReduce(.Min, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, f32_nan); - try testReduce(.Min, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, f64_nan); + if (builtin.zig_backend != .stage2_llvm) { + try testReduce(.Min, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, @as(f16, -1.9)); + try testReduce(.Min, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, @as(f32, -1.9)); + try testReduce(.Min, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, @as(f64, -1.9)); - try testReduce(.Max, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); - try testReduce(.Max, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, f32_nan); - try testReduce(.Max, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, f64_nan); + try testReduce(.Max, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, @as(f16, 100.0)); + try testReduce(.Max, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, @as(f32, 100.0)); + try testReduce(.Max, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, @as(f64, 100.0)); } try testReduce(.Mul, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); @@ -831,7 +825,6 @@ test "mask parameter of @shuffle is comptime scope" { test "saturating add" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -863,7 +856,6 @@ test "saturating add" { test "saturating subtraction" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -886,7 +878,6 @@ test "saturating subtraction" { test "saturating multiplication" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -913,7 +904,6 @@ test "saturating multiplication" { test "saturating shift-left" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -1047,7 +1037,6 @@ test "@mulWithOverflow" { } test "@shlWithOverflow" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -1202,7 +1191,6 @@ test "zero multiplicand" { test "@intCast to u0" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO From 8f6da78fb1bfc9d5e8b3d5affd33cf6a62f5e8c7 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 00:30:55 -0500 Subject: [PATCH 026/294] CBE: implement vector element pointers --- src/codegen/c.zig | 10 ++-------- src/codegen/c/type.zig | 2 +- test/behavior/vector.zig | 3 --- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5e92a6f76c..60f93311a4 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -17,12 +17,6 @@ const LazySrcLoc = Module.LazySrcLoc; const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); -const target_util = @import("../target.zig"); -const libcFloatPrefix = target_util.libcFloatPrefix; -const libcFloatSuffix = target_util.libcFloatSuffix; -const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev; -const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev; - const BigIntLimb = std.math.big.Limb; const BigInt = std.math.big.int; @@ -3317,7 +3311,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll(", sizeof("); try f.renderType(writer, src_ty); try writer.writeAll("))"); - } else if (ptr_info.host_size != 0) { + } else if (ptr_info.host_size > 0 and ptr_info.vector_index == .none) { var host_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = ptr_info.host_size * 8, @@ -3647,7 +3641,7 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { if (src_val == .constant) { try freeLocal(f, inst, array_src.new_local, 0); } - } else if (ptr_info.host_size != 0) { + } else if (ptr_info.host_size > 0 and ptr_info.vector_index == .none) { const host_bits = ptr_info.host_size * 8; var host_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = host_bits }; const host_ty = Type.initPayload(&host_pl.base); diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig index 313fcc130c..038f53f186 100644 --- a/src/codegen/c/type.zig +++ b/src/codegen/c/type.zig @@ -1465,7 +1465,7 @@ pub const CType = extern union { .base = .{ .tag = .int_unsigned }, .data = info.host_size * 8, }; - const pointee_ty = if (info.host_size > 0) + const pointee_ty = if (info.host_size > 0 and info.vector_index == .none) Type.initPayload(&host_int_pl.base) else info.pointee_type; diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index e74bcdad86..42befa9c0f 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -1118,7 +1118,6 @@ test "byte vector initialized in inline function" { } test "byte vector initialized in inline function" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO @@ -1233,7 +1232,6 @@ test "load packed vector element" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO var x: @Vector(2, u15) = .{ 1, 4 }; try expect((&x[0]).* == 1); @@ -1246,7 +1244,6 @@ test "store packed vector element" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO var v = @Vector(4, u1){ 1, 1, 1, 1 }; try expectEqual(@Vector(4, u1){ 1, 1, 1, 1 }, v); From ba69ee488baec677d6e206eb0670240b1c2167a6 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 00:44:27 -0500 Subject: [PATCH 027/294] CBE: implement vector truncate --- src/codegen/c.zig | 34 ++++++++++++++++++++++------------ test/behavior/truncate.zig | 1 - 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 60f93311a4..3fea7c2ef2 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3465,34 +3465,40 @@ fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { const operand = try f.resolveInst(ty_op.operand); try reap(f, inst, &.{ty_op.operand}); const inst_ty = f.air.typeOfIndex(inst); - const writer = f.object.writer(); - const local = try f.allocLocal(inst, inst_ty); + const inst_scalar_ty = inst_ty.scalarType(); const target = f.object.dg.module.getTarget(); - const dest_int_info = inst_ty.intInfo(target); + const dest_int_info = inst_scalar_ty.intInfo(target); const dest_bits = dest_int_info.bits; const dest_c_bits = toCIntBits(dest_int_info.bits) orelse return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); const operand_ty = f.air.typeOf(ty_op.operand); - const operand_int_info = operand_ty.intInfo(target); + const scalar_ty = operand_ty.scalarType(); + const scalar_int_info = scalar_ty.intInfo(target); + + const writer = f.object.writer(); + const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, operand_ty); try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); try writer.writeAll(" = "); if (dest_c_bits < 64) { try writer.writeByte('('); - try f.renderType(writer, inst_ty); + try f.renderType(writer, inst_scalar_ty); try writer.writeByte(')'); } - const needs_lo = operand_int_info.bits > 64 and dest_bits <= 64; + const needs_lo = scalar_int_info.bits > 64 and dest_bits <= 64; if (needs_lo) { try writer.writeAll("zig_lo_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); } if (dest_bits >= 8 and std.math.isPowerOfTwo(dest_bits)) { try f.writeCValue(writer, operand, .Other); + try v.elem(f, writer); } else switch (dest_int_info.signedness) { .unsigned => { var arena = std.heap.ArenaAllocator.init(f.object.dg.gpa); @@ -3502,15 +3508,16 @@ fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), arena.allocator()); - const mask_val = try inst_ty.maxInt(stack.get(), target); + const mask_val = try inst_scalar_ty.maxInt(stack.get(), target); try writer.writeAll("zig_and_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); try f.writeCValue(writer, operand, .FunctionArgument); - try writer.print(", {x})", .{try f.fmtIntLiteral(operand_ty, mask_val)}); + try v.elem(f, writer); + try writer.print(", {x})", .{try f.fmtIntLiteral(scalar_ty, mask_val)}); }, .signed => { - const c_bits = toCIntBits(operand_int_info.bits) orelse + const c_bits = toCIntBits(scalar_int_info.bits) orelse return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); var shift_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, @@ -3519,7 +3526,7 @@ fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { const shift_val = Value.initPayload(&shift_pl.base); try writer.writeAll("zig_shr_"); - try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); + try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); if (c_bits == 128) { try writer.print("(zig_bitcast_i{d}(", .{c_bits}); } else { @@ -3532,6 +3539,7 @@ fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { try writer.print("(uint{d}_t)", .{c_bits}); } try f.writeCValue(writer, operand, .FunctionArgument); + try v.elem(f, writer); if (c_bits == 128) try writer.writeByte(')'); try writer.print(", {})", .{try f.fmtIntLiteral(Type.u8, shift_val)}); if (c_bits == 128) try writer.writeByte(')'); @@ -3541,6 +3549,8 @@ fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { if (needs_lo) try writer.writeByte(')'); try writer.writeAll(";\n"); + try v.end(f, inst, writer); + return local; } diff --git a/test/behavior/truncate.zig b/test/behavior/truncate.zig index c81abebe68..e70d33eea2 100644 --- a/test/behavior/truncate.zig +++ b/test/behavior/truncate.zig @@ -60,7 +60,6 @@ test "truncate on comptime integer" { } test "truncate on vectors" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; From aac47079026d0daf4d5acac08b7d0ad1150002d0 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 01:23:21 -0500 Subject: [PATCH 028/294] CBE: implement splat --- src/codegen/c.zig | 33 ++++++++++++++++++++++++++++----- test/behavior/vector.zig | 1 - 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3fea7c2ef2..f5309918bf 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -438,6 +438,10 @@ pub const Function = struct { return f.object.dg.renderType(w, t); } + fn renderCType(f: *Function, w: anytype, t: CType.Index) !void { + return f.object.dg.renderCType(w, t); + } + fn renderIntCast(f: *Function, w: anytype, dest_ty: Type, src: CValue, v: Vectorizer, src_ty: Type, location: ValueRenderLocation) !void { return f.object.dg.renderIntCast(w, dest_ty, .{ .c_value = .{ .f = f, .value = src, .v = v } }, src_ty, location); } @@ -1576,9 +1580,12 @@ pub const DeclGen = struct { /// | `renderType` | "uint8_t *" | "uint8_t *[10]" | /// fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void { + try dg.renderCType(w, try dg.typeToIndex(t, .complete)); + } + + fn renderCType(dg: *DeclGen, w: anytype, idx: CType.Index) error{ OutOfMemory, AnalysisFail }!void { const store = &dg.ctypes.set; const module = dg.module; - const idx = try dg.typeToIndex(t, .complete); _ = try renderTypePrefix(dg.decl_index, store.*, module, w, idx, .suffix, .{}); try renderTypeSuffix(dg.decl_index, store.*, module, w, idx, .suffix, .{}); } @@ -6543,21 +6550,37 @@ fn airErrorName(f: *Function, inst: Air.Inst.Index) !CValue { fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue { const ty_op = f.air.instructions.items(.data)[inst].ty_op; + if (f.liveness.isUnused(inst)) { try reap(f, inst, &.{ty_op.operand}); return .none; } - const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(ty_op.operand); try reap(f, inst, &.{ty_op.operand}); + + const inst_ty = f.air.typeOfIndex(inst); + const inst_scalar_ty = inst_ty.scalarType(); + const inst_scalar_cty = try f.typeToIndex(inst_scalar_ty, .complete); + const need_memcpy = f.indexToCType(inst_scalar_cty).tag() == .array; + const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, inst_ty); + if (need_memcpy) try writer.writeAll("memcpy(&"); try f.writeCValue(writer, local, .Other); - try writer.writeAll(" = "); + try v.elem(f, writer); + try writer.writeAll(if (need_memcpy) ", &" else " = "); + try f.writeCValue(writer, operand, .Other); + if (need_memcpy) { + try writer.writeAll(", sizeof("); + try f.renderCType(writer, inst_scalar_cty); + try writer.writeAll("))"); + } + try writer.writeAll(";\n"); + try v.end(f, inst, writer); - _ = operand; - return f.fail("TODO: C backend: implement airSplat", .{}); + return local; } fn airSelect(f: *Function, inst: Air.Inst.Index) !CValue { diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 42befa9c0f..5d569bd815 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -234,7 +234,6 @@ test "vector casts of sizes not divisible by 8" { } test "vector @splat" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO From 0b0298aff27a31a7f45828d96d95adfdde61a085 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 02:06:53 -0500 Subject: [PATCH 029/294] CBE: implement select and shuffle --- src/codegen/c.zig | 79 +++++++++++++++++++++++++++++++++++++-- test/behavior/select.zig | 2 - test/behavior/shuffle.zig | 2 - test/behavior/vector.zig | 2 - 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f5309918bf..5e64823a0d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -6584,15 +6584,86 @@ fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue { } fn airSelect(f: *Function, inst: Air.Inst.Index) !CValue { - if (f.liveness.isUnused(inst)) return .none; + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.Bin, pl_op.payload).data; - return f.fail("TODO: C backend: implement airSelect", .{}); + if (f.liveness.isUnused(inst)) { + try reap(f, inst, &.{ pl_op.operand, extra.lhs, extra.rhs }); + return .none; + } + + const pred = try f.resolveInst(pl_op.operand); + const lhs = try f.resolveInst(extra.lhs); + const rhs = try f.resolveInst(extra.rhs); + try reap(f, inst, &.{ pl_op.operand, extra.lhs, extra.rhs }); + + const inst_ty = f.air.typeOfIndex(inst); + + const writer = f.object.writer(); + const local = try f.allocLocal(inst, inst_ty); + const v = try Vectorizer.start(f, inst, writer, inst_ty); + try f.writeCValue(writer, local, .Other); + try v.elem(f, writer); + try writer.writeAll(" = "); + try f.writeCValue(writer, pred, .Other); + try v.elem(f, writer); + try writer.writeAll(" ? "); + try f.writeCValue(writer, lhs, .Other); + try v.elem(f, writer); + try writer.writeAll(" : "); + try f.writeCValue(writer, rhs, .Other); + try v.elem(f, writer); + try writer.writeAll(";\n"); + try v.end(f, inst, writer); + + return local; } fn airShuffle(f: *Function, inst: Air.Inst.Index) !CValue { - if (f.liveness.isUnused(inst)) return .none; + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.Shuffle, ty_pl.payload).data; - return f.fail("TODO: C backend: implement airShuffle", .{}); + if (f.liveness.isUnused(inst)) { + try reap(f, inst, &.{ extra.a, extra.b }); + return .none; + } + + const mask = f.air.values[extra.mask]; + const lhs = try f.resolveInst(extra.a); + const rhs = try f.resolveInst(extra.b); + + const module = f.object.dg.module; + const target = module.getTarget(); + const inst_ty = f.air.typeOfIndex(inst); + + const writer = f.object.writer(); + const local = try f.allocLocal(inst, inst_ty); + try reap(f, inst, &.{ extra.a, extra.b }); // local cannot alias operands + for (0..extra.mask_len) |index| { + var dst_pl = Value.Payload.U64{ + .base = .{ .tag = .int_u64 }, + .data = @intCast(u64, index), + }; + + try f.writeCValue(writer, local, .Other); + try writer.writeByte('['); + try f.object.dg.renderValue(writer, Type.usize, Value.initPayload(&dst_pl.base), .Other); + try writer.writeAll("] = "); + + var buf: Value.ElemValueBuffer = undefined; + const mask_elem = mask.elemValueBuffer(module, index, &buf).toSignedInt(target); + var src_pl = Value.Payload.U64{ + .base = .{ .tag = .int_u64 }, + .data = @intCast(u64, mask_elem ^ mask_elem >> 63), + }; + + try f.writeCValue(writer, if (mask_elem >= 0) lhs else rhs, .Other); + try writer.writeByte('['); + try f.object.dg.renderValue(writer, Type.usize, Value.initPayload(&src_pl.base), .Other); + try writer.writeAll("];\n"); + } + + return local; } fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { diff --git a/test/behavior/select.zig b/test/behavior/select.zig index d09683b67c..73d69c6530 100644 --- a/test/behavior/select.zig +++ b/test/behavior/select.zig @@ -4,7 +4,6 @@ const mem = std.mem; const expect = std.testing.expect; test "@select vectors" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -33,7 +32,6 @@ fn selectVectors() !void { } test "@select arrays" { - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/shuffle.zig b/test/behavior/shuffle.zig index bcc4618aee..b591aee2e2 100644 --- a/test/behavior/shuffle.zig +++ b/test/behavior/shuffle.zig @@ -8,7 +8,6 @@ test "@shuffle int" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -50,7 +49,6 @@ test "@shuffle bool 1" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 5d569bd815..816bd6c23a 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -804,7 +804,6 @@ test "vector @reduce comptime" { test "mask parameter of @shuffle is comptime scope" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -1212,7 +1211,6 @@ test "modRem with zero divisor" { test "array operands to shuffle are coerced to vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO From 33fa25ba4470bf000280a94f0376988b05918b75 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 02:35:32 -0500 Subject: [PATCH 030/294] CBE: ensure uniqueness of more internal identifiers --- src/codegen/c.zig | 35 +++++++++++++---------------------- test/behavior/vector.zig | 1 - 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5e64823a0d..f1761ed80d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1841,30 +1841,21 @@ pub const DeclGen = struct { dg.module.markDeclAlive(decl); if (dg.module.decl_exports.get(decl_index)) |exports| { - return writer.writeAll(exports.items[export_index].options.name); + try writer.writeAll(exports.items[export_index].options.name); } else if (decl.isExtern()) { - return writer.writeAll(mem.sliceTo(decl.name, 0)); - } else if (dg.module.test_functions.get(decl_index)) |_| { - const gpa = dg.gpa; - const name = try decl.getFullyQualifiedName(dg.module); - defer gpa.free(name); - return writer.print("{}_{d}", .{ fmtIdent(name), @enumToInt(decl_index) }); + try writer.writeAll(mem.sliceTo(decl.name, 0)); } else { - const gpa = dg.gpa; - const name = try decl.getFullyQualifiedName(dg.module); - defer gpa.free(name); - - // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case), expand - // to 3x the length of its input - if (name.len > 1365) { - var hash = ident_hasher_init; - hash.update(name); - const ident_hash = hash.finalInt(); - try writer.writeAll("zig_D_"); - return std.fmt.formatIntValue(ident_hash, "x", .{}, writer); - } else { - return writer.print("{}", .{fmtIdent(name)}); - } + // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case), + // expand to 3x the length of its input, but let's cut it off at a much shorter limit. + var name: [100]u8 = undefined; + var name_stream = std.io.fixedBufferStream(&name); + decl.renderFullyQualifiedName(dg.module, name_stream.writer()) catch |err| switch (err) { + error.NoSpaceLeft => {}, + }; + try writer.print("{}__{d}", .{ + fmtIdent(name_stream.getWritten()), + @enumToInt(decl_index), + }); } } diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 816bd6c23a..0215572f8f 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -91,7 +91,6 @@ test "vector int operators" { test "vector float operators" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO From 7352d461cff72d92b07cf2d2b7ee17714005b9cf Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 03:29:50 -0500 Subject: [PATCH 031/294] behavior: fix comptime issue and disable failing test --- test/behavior/muladd.zig | 7 +++++++ test/behavior/shuffle.zig | 3 +-- test/behavior/vector.zig | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig index 218edc5a2d..25ed3641b8 100644 --- a/test/behavior/muladd.zig +++ b/test/behavior/muladd.zig @@ -197,6 +197,13 @@ test "vector f128" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64 and + builtin.zig_backend == .stage2_c) + { + // https://github.com/ziglang/zig/issues/13876 + return error.SkipZigTest; + } + comptime try vector128(); try vector128(); } diff --git a/test/behavior/shuffle.zig b/test/behavior/shuffle.zig index b591aee2e2..97223cc263 100644 --- a/test/behavior/shuffle.zig +++ b/test/behavior/shuffle.zig @@ -69,7 +69,6 @@ test "@shuffle bool 2" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_llvm) { @@ -81,7 +80,7 @@ test "@shuffle bool 2" { fn doTheTest() !void { var x: @Vector(3, bool) = [3]bool{ false, true, false }; var v: @Vector(2, bool) = [2]bool{ true, false }; - const mask: @Vector(4, i32) = [4]i32{ 0, ~@as(i32, 1), 1, 2 }; + const mask = [4]i32{ 0, ~@as(i32, 1), 1, 2 }; var res = @shuffle(bool, x, v, mask); try expect(mem.eql(bool, &@as([4]bool, res), &[4]bool{ false, false, true, false })); } diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 0215572f8f..1d9d517a96 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -96,6 +96,13 @@ test "vector float operators" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64 and + builtin.zig_backend == .stage2_c) + { + // https://github.com/ziglang/zig/issues/13876 + return error.SkipZigTest; + } + inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { const S = struct { fn doTheTest() !void { From 8ea1c1932e7bd869ec77a161da7876d171d4ef1d Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 04:25:04 -0500 Subject: [PATCH 032/294] behavior: disable failing tests --- test/behavior/slice.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index ed5e2a721d..6239de2d76 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -749,6 +749,11 @@ test "slice decays to many pointer" { } test "write through pointer to optional slice arg" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + const S = struct { fn bar(foo: *?[]const u8) !void { foo.* = try baz(); From 1efd36cd5c9a1128ae702b081d60ee32f21bc258 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 06:32:23 -0500 Subject: [PATCH 033/294] CBE: fix reduce of emulated integers --- src/codegen/c.zig | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f1761ed80d..3d059adc15 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -6672,33 +6672,43 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { const operand_ty = f.air.typeOf(reduce.operand); const writer = f.object.writer(); + const use_operator = scalar_ty.bitSize(target) <= 64; const op: union(enum) { - float_op: []const u8, - builtin: []const u8, + const Func = struct { operation: []const u8, info: BuiltinInfo = .none }; + float_op: Func, + builtin: Func, infix: []const u8, ternary: []const u8, } = switch (reduce.operation) { - .And => .{ .infix = " &= " }, - .Or => .{ .infix = " |= " }, - .Xor => .{ .infix = " ^= " }, + .And => if (use_operator) .{ .infix = " &= " } else .{ .builtin = .{ .operation = "and" } }, + .Or => if (use_operator) .{ .infix = " |= " } else .{ .builtin = .{ .operation = "or" } }, + .Xor => if (use_operator) .{ .infix = " ^= " } else .{ .builtin = .{ .operation = "xor" } }, .Min => switch (scalar_ty.zigTypeTag()) { - .Int => .{ .ternary = " < " }, - .Float => .{ .float_op = "fmin" }, + .Int => if (use_operator) .{ .ternary = " < " } else .{ + .builtin = .{ .operation = "min" }, + }, + .Float => .{ .float_op = .{ .operation = "fmin" } }, else => unreachable, }, .Max => switch (scalar_ty.zigTypeTag()) { - .Int => .{ .ternary = " > " }, - .Float => .{ .float_op = "fmax" }, + .Int => if (use_operator) .{ .ternary = " > " } else .{ + .builtin = .{ .operation = "max" }, + }, + .Float => .{ .float_op = .{ .operation = "fmax" } }, else => unreachable, }, .Add => switch (scalar_ty.zigTypeTag()) { - .Int => .{ .infix = " += " }, - .Float => .{ .builtin = "add" }, + .Int => if (use_operator) .{ .infix = " += " } else .{ + .builtin = .{ .operation = "addw", .info = .bits }, + }, + .Float => .{ .builtin = .{ .operation = "add" } }, else => unreachable, }, .Mul => switch (scalar_ty.zigTypeTag()) { - .Int => .{ .infix = " *= " }, - .Float => .{ .builtin = "mul" }, + .Int => if (use_operator) .{ .infix = " *= " } else .{ + .builtin = .{ .operation = "mulw", .info = .bits }, + }, + .Float => .{ .builtin = .{ .operation = "mul" } }, else => unreachable, }, }; @@ -6762,24 +6772,26 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { const v = try Vectorizer.start(f, inst, writer, operand_ty); try f.writeCValue(writer, accum, .Other); switch (op) { - .float_op => |operation| { + .float_op => |func| { try writer.writeAll(" = zig_libc_name_"); try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); - try writer.print("({s})(", .{operation}); + try writer.print("({s})(", .{func.operation}); try f.writeCValue(writer, accum, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, operand, .Other); try v.elem(f, writer); + try f.object.dg.renderBuiltinInfo(writer, scalar_ty, func.info); try writer.writeByte(')'); }, - .builtin => |operation| { - try writer.print(" = zig_{s}_", .{operation}); + .builtin => |func| { + try writer.print(" = zig_{s}_", .{func.operation}); try f.object.dg.renderTypeForBuiltinFnName(writer, scalar_ty); try writer.writeByte('('); try f.writeCValue(writer, accum, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, operand, .Other); try v.elem(f, writer); + try f.object.dg.renderBuiltinInfo(writer, scalar_ty, func.info); try writer.writeByte(')'); }, .infix => |ass| { From a63134a4a56e8683aeee292b641b4e943cbfb999 Mon Sep 17 00:00:00 2001 From: jim price Date: Sat, 4 Mar 2023 18:03:37 -0800 Subject: [PATCH 034/294] std.os: Add DeviceBusy as a possible write error In Linux when writing to various files in the virtual file system, for example /sys/fs/cgroup, if you write an invalid value to a file you'll get errno 16. This change allows for these specific cases to be caught instead of being lumped together in UnexpectedError. --- lib/std/os.zig | 5 +++++ src/link.zig | 1 + 2 files changed, 6 insertions(+) diff --git a/lib/std/os.zig b/lib/std/os.zig index fe664302a7..3a3433d819 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1036,6 +1036,7 @@ pub const WriteError = error{ FileTooBig, InputOutput, NoSpaceLeft, + DeviceBusy, /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to write to it. @@ -1134,6 +1135,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { .PERM => return error.AccessDenied, .PIPE => return error.BrokenPipe, .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, else => |err| return unexpectedErrno(err), } } @@ -1203,6 +1205,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { .PERM => return error.AccessDenied, .PIPE => return error.BrokenPipe, .CONNRESET => return error.ConnectionResetByPeer, + .BUSY => return error.DeviceBusy, else => |err| return unexpectedErrno(err), } } @@ -1299,6 +1302,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, .OVERFLOW => return error.Unseekable, + .BUSY => return error.DeviceBusy, else => |err| return unexpectedErrno(err), } } @@ -1388,6 +1392,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, .OVERFLOW => return error.Unseekable, + .BUSY => return error.DeviceBusy, else => |err| return unexpectedErrno(err), } } diff --git a/src/link.zig b/src/link.zig index 4c4915441d..24cc0a3861 100644 --- a/src/link.zig +++ b/src/link.zig @@ -460,6 +460,7 @@ pub const File = struct { CurrentWorkingDirectoryUnlinked, LockViolation, NetNameDeleted, + DeviceBusy, }; /// Called from within the CodeGen to lower a local variable instantion as an unnamed From 29c56a8aa74d1b1a19bece5ba5d738af1e3c9f6d Mon Sep 17 00:00:00 2001 From: jiacai2050 Date: Sat, 4 Mar 2023 10:47:25 +0800 Subject: [PATCH 035/294] fix package redeclaration when cache is not found --- src/Package.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Package.zig b/src/Package.zig index 2aa5e85294..ed93500980 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -432,6 +432,12 @@ fn fetchAndUnpack( const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path}); errdefer gpa.free(build_root); + var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :cached, + else => |e| return e, + }; + errdefer pkg_dir.close(); + try build_roots_source.writer().print(" pub const {s} = \"{}\";\n", .{ std.zig.fmtId(fqn), std.zig.fmtEscapes(build_root), }); @@ -444,12 +450,6 @@ fn fetchAndUnpack( return gop.value_ptr.*; } - var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) { - error.FileNotFound => break :cached, - else => |e| return e, - }; - errdefer pkg_dir.close(); - const ptr = try gpa.create(Package); errdefer gpa.destroy(ptr); From f1ae688d371f49fdbf65f952d655905c74871fdb Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sun, 5 Mar 2023 15:45:23 +0100 Subject: [PATCH 036/294] AstGen: ensure certain builtin functions return void Fixes #14779 Co-authored-by: Veikka Tuominen --- src/AstGen.zig | 32 +++++++++---------- ...n_functions_returning_void_or_noreturn.zig | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 test/behavior/builtin_functions_returning_void_or_noreturn.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 587b574a01..20f4fb6df3 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -8060,35 +8060,35 @@ fn builtinCall( }, .fence => { const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[0]); - const result = try gz.addExtendedPayload(.fence, Zir.Inst.UnNode{ + _ = try gz.addExtendedPayload(.fence, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), .operand = order, }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .set_float_mode => { const order = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .float_mode_type } }, params[0]); - const result = try gz.addExtendedPayload(.set_float_mode, Zir.Inst.UnNode{ + _ = try gz.addExtendedPayload(.set_float_mode, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), .operand = order, }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .set_align_stack => { const order = try expr(gz, scope, align_ri, params[0]); - const result = try gz.addExtendedPayload(.set_align_stack, Zir.Inst.UnNode{ + _ = try gz.addExtendedPayload(.set_align_stack, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), .operand = order, }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .set_cold => { const order = try expr(gz, scope, ri, params[0]); - const result = try gz.addExtendedPayload(.set_cold, Zir.Inst.UnNode{ + _ = try gz.addExtendedPayload(.set_cold, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), .operand = order, }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .src => { @@ -8373,14 +8373,14 @@ fn builtinCall( }, .atomic_store => { const int_type = try typeExpr(gz, scope, params[0]); - const result = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{ + _ = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{ // zig fmt: off .ptr = try expr(gz, scope, .{ .rl = .none }, params[1]), .operand = try expr(gz, scope, .{ .rl = .{ .ty = int_type } }, params[2]), .ordering = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .atomic_order_type } }, params[3]), // zig fmt: on }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .mul_add => { const float_type = try typeExpr(gz, scope, params[0]); @@ -8421,20 +8421,20 @@ fn builtinCall( return rvalue(gz, ri, result, node); }, .memcpy => { - const result = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{ + _ = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{ .dest = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .manyptr_u8_type } }, params[0]), .source = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .manyptr_const_u8_type } }, params[1]), .byte_count = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, params[2]), }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .memset => { - const result = try gz.addPlNode(.memset, node, Zir.Inst.Memset{ + _ = try gz.addPlNode(.memset, node, Zir.Inst.Memset{ .dest = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .manyptr_u8_type } }, params[0]), .byte = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .u8_type } }, params[1]), .byte_count = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, params[2]), }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .shuffle => { const result = try gz.addPlNode(.shuffle, node, Zir.Inst.Shuffle{ @@ -8475,12 +8475,12 @@ fn builtinCall( .prefetch => { const ptr = try expr(gz, scope, .{ .rl = .none }, params[0]); const options = try comptimeExpr(gz, scope, .{ .rl = .{ .ty = .prefetch_options_type } }, params[1]); - const result = try gz.addExtendedPayload(.prefetch, Zir.Inst.BinNode{ + _ = try gz.addExtendedPayload(.prefetch, Zir.Inst.BinNode{ .node = gz.nodeIndexToRelative(node), .lhs = ptr, .rhs = options, }); - return rvalue(gz, ri, result, node); + return rvalue(gz, ri, .void_value, node); }, .c_va_arg => { if (astgen.fn_block == null) { diff --git a/test/behavior/builtin_functions_returning_void_or_noreturn.zig b/test/behavior/builtin_functions_returning_void_or_noreturn.zig new file mode 100644 index 0000000000..072f5576cc --- /dev/null +++ b/test/behavior/builtin_functions_returning_void_or_noreturn.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const testing = std.testing; + +var x: u8 = 1; + +// This excludes builtin functions that return void or noreturn that cannot be tested. +test { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO + + var val: u8 = undefined; + try testing.expectEqual({}, @atomicStore(u8, &val, 0, .Unordered)); + try testing.expectEqual(void, @TypeOf(@breakpoint())); + try testing.expectEqual({}, @export(x, .{ .name = "x" })); + try testing.expectEqual({}, @fence(.Acquire)); + try testing.expectEqual({}, @memcpy(@intToPtr([*]u8, 1), @intToPtr([*]u8, 1), 0)); + try testing.expectEqual({}, @memset(@intToPtr([*]u8, 1), undefined, 0)); + try testing.expectEqual(noreturn, @TypeOf(if (true) @panic("") else {})); + try testing.expectEqual({}, @prefetch(&val, .{})); + try testing.expectEqual({}, @setAlignStack(16)); + try testing.expectEqual({}, @setCold(true)); + try testing.expectEqual({}, @setEvalBranchQuota(0)); + try testing.expectEqual({}, @setFloatMode(.Optimized)); + try testing.expectEqual({}, @setRuntimeSafety(true)); + try testing.expectEqual(noreturn, @TypeOf(if (true) @trap() else {})); +} From 34a23db664e0fe50fb21c892f33b0aec8a7a2f7f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Mar 2023 14:21:57 -0700 Subject: [PATCH 037/294] zig.h: lower trap to SIGTRAP instead of SIGILL --- lib/zig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zig.h b/lib/zig.h index 22a9dbbb9e..65fb21f99a 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -193,7 +193,7 @@ typedef char bool; #elif defined(__i386__) || defined(__x86_64__) #define zig_trap() __asm__ volatile("ud2"); #else -#define zig_trap() raise(SIGILL) +#define zig_trap() raise(SIGTRAP) #endif #if zig_has_builtin(debugtrap) From fb04ff45cd1b4eca5c56e0295bbbe961557ef820 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Mar 2023 14:22:46 -0700 Subject: [PATCH 038/294] langref: small clarification to `@trap` --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index a413c3aab5..7044fe977f 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9403,7 +9403,7 @@ fn List(comptime T: type) type { Unlike for {#syntax#}@breakpoint(){#endsyntax#}, execution does not continue after this point.

- This function is only valid within function scope. + Outside function scope, this builtin causes a compile error.

{#see_also|@breakpoint#} {#header_close#} From 48e72960a496edc86b231d45bfa39d618b6adfaf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Mar 2023 14:48:31 -0700 Subject: [PATCH 039/294] llvm: fix lowering of `@trap` It needed an unreachable instruction after it. --- src/codegen/llvm.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index baeaeee58f..85a82f4eda 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -8261,6 +8261,7 @@ pub const FuncGen = struct { _ = inst; const llvm_fn = self.getIntrinsic("llvm.trap", &.{}); _ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, undefined, 0, .Cold, .Auto, ""); + _ = self.builder.buildUnreachable(); return null; } From c839c180ef1686794c039fc6d3c20a8716e87357 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Mar 2023 12:46:12 -0700 Subject: [PATCH 040/294] stage2: add zig_backend to ZIR cache namespace --- src/Module.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Module.zig b/src/Module.zig index a2502d36d3..7ea69a0a2e 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3528,6 +3528,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { const digest = hash: { var path_hash: Cache.HashHelper = .{}; path_hash.addBytes(build_options.version); + path_hash.add(builtin.zig_backend); if (!want_local_cache) { path_hash.addOptionalBytes(file.pkg.root_src_directory.path); } From cdb9cc8f6bda4b4faa270278e3b67c4ef9246a84 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Mar 2023 14:41:12 -0700 Subject: [PATCH 041/294] update zig1.wasm --- stage1/zig.h | 2759 +++++++++++++++++++++++++++++++--------------- stage1/zig1.wasm | Bin 2408069 -> 2412111 bytes 2 files changed, 1858 insertions(+), 901 deletions(-) diff --git a/stage1/zig.h b/stage1/zig.h index 0756d9f731..65fb21f99a 100644 --- a/stage1/zig.h +++ b/stage1/zig.h @@ -1,8 +1,11 @@ #undef linux +#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__ #define __STDC_WANT_IEC_60559_TYPES_EXT__ +#endif #include #include +#include #include #include @@ -34,6 +37,14 @@ typedef char bool; #define zig_has_attribute(attribute) 0 #endif +#if __LITTLE_ENDIAN__ || _MSC_VER +#define zig_little_endian 1 +#define zig_big_endian 0 +#else +#define zig_little_endian 0 +#define zig_big_endian 1 +#endif + #if __STDC_VERSION__ >= 201112L #define zig_threadlocal _Thread_local #elif defined(__GNUC__) @@ -75,6 +86,32 @@ typedef char bool; #define zig_cold #endif +#if zig_has_attribute(flatten) +#define zig_maybe_flatten __attribute__((flatten)) +#else +#define zig_maybe_flatten +#endif + +#if zig_has_attribute(noinline) +#define zig_never_inline __attribute__((noinline)) zig_maybe_flatten +#elif defined(_MSC_VER) +#define zig_never_inline __declspec(noinline) zig_maybe_flatten +#else +#define zig_never_inline zig_never_inline_unavailable +#endif + +#if zig_has_attribute(not_tail_called) +#define zig_never_tail __attribute__((not_tail_called)) zig_never_inline +#else +#define zig_never_tail zig_never_tail_unavailable +#endif + +#if zig_has_attribute(always_inline) +#define zig_always_tail __attribute__((musttail)) +#else +#define zig_always_tail zig_always_tail_unavailable +#endif + #if __STDC_VERSION__ >= 199901L #define zig_restrict restrict #elif defined(__GNUC__) @@ -151,10 +188,16 @@ typedef char bool; #define zig_export(sig, symbol, name) __asm(name " = " symbol) #endif +#if zig_has_builtin(trap) +#define zig_trap() __builtin_trap() +#elif defined(__i386__) || defined(__x86_64__) +#define zig_trap() __asm__ volatile("ud2"); +#else +#define zig_trap() raise(SIGTRAP) +#endif + #if zig_has_builtin(debugtrap) #define zig_breakpoint() __builtin_debugtrap() -#elif zig_has_builtin(trap) || defined(zig_gnuc) -#define zig_breakpoint() __builtin_trap() #elif defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__) #define zig_breakpoint() __debugbreak() #elif defined(__i386__) || defined(__x86_64__) @@ -286,701 +329,656 @@ typedef char bool; #endif #if __STDC_VERSION__ >= 201112L -#define zig_noreturn _Noreturn void +#define zig_noreturn _Noreturn #elif zig_has_attribute(noreturn) || defined(zig_gnuc) -#define zig_noreturn __attribute__((noreturn)) void +#define zig_noreturn __attribute__((noreturn)) #elif _MSC_VER -#define zig_noreturn __declspec(noreturn) void +#define zig_noreturn __declspec(noreturn) #else -#define zig_noreturn void +#define zig_noreturn #endif #define zig_bitSizeOf(T) (CHAR_BIT * sizeof(T)) -typedef uintptr_t zig_usize; -typedef intptr_t zig_isize; -typedef signed short int zig_c_short; -typedef unsigned short int zig_c_ushort; -typedef signed int zig_c_int; -typedef unsigned int zig_c_uint; -typedef signed long int zig_c_long; -typedef unsigned long int zig_c_ulong; -typedef signed long long int zig_c_longlong; -typedef unsigned long long int zig_c_ulonglong; +#define zig_compiler_rt_abbrev_uint32_t si +#define zig_compiler_rt_abbrev_int32_t si +#define zig_compiler_rt_abbrev_uint64_t di +#define zig_compiler_rt_abbrev_int64_t di +#define zig_compiler_rt_abbrev_zig_u128 ti +#define zig_compiler_rt_abbrev_zig_i128 ti +#define zig_compiler_rt_abbrev_zig_f16 hf +#define zig_compiler_rt_abbrev_zig_f32 sf +#define zig_compiler_rt_abbrev_zig_f64 df +#define zig_compiler_rt_abbrev_zig_f80 xf +#define zig_compiler_rt_abbrev_zig_f128 tf -typedef uint8_t zig_u8; -typedef int8_t zig_i8; -typedef uint16_t zig_u16; -typedef int16_t zig_i16; -typedef uint32_t zig_u32; -typedef int32_t zig_i32; -typedef uint64_t zig_u64; -typedef int64_t zig_i64; +zig_extern void *memcpy (void *zig_restrict, void const *zig_restrict, size_t); +zig_extern void *memset (void *, int, size_t); -#define zig_as_u8(val) UINT8_C(val) -#define zig_as_i8(val) INT8_C(val) -#define zig_as_u16(val) UINT16_C(val) -#define zig_as_i16(val) INT16_C(val) -#define zig_as_u32(val) UINT32_C(val) -#define zig_as_i32(val) INT32_C(val) -#define zig_as_u64(val) UINT64_C(val) -#define zig_as_i64(val) INT64_C(val) +/* ===================== 8/16/32/64-bit Integer Support ===================== */ + +#if __STDC_VERSION__ >= 199901L || _MSC_VER +#include +#else + +#if SCHAR_MIN == ~0x7F && SCHAR_MAX == 0x7F && UCHAR_MAX == 0xFF +typedef unsigned char uint8_t; +typedef signed char int8_t; +#define INT8_C(c) c +#define UINT8_C(c) c##U +#elif SHRT_MIN == ~0x7F && SHRT_MAX == 0x7F && USHRT_MAX == 0xFF +typedef unsigned short uint8_t; +typedef signed short int8_t; +#define INT8_C(c) c +#define UINT8_C(c) c##U +#elif INT_MIN == ~0x7F && INT_MAX == 0x7F && UINT_MAX == 0xFF +typedef unsigned int uint8_t; +typedef signed int int8_t; +#define INT8_C(c) c +#define UINT8_C(c) c##U +#elif LONG_MIN == ~0x7F && LONG_MAX == 0x7F && ULONG_MAX == 0xFF +typedef unsigned long uint8_t; +typedef signed long int8_t; +#define INT8_C(c) c##L +#define UINT8_C(c) c##LU +#elif LLONG_MIN == ~0x7F && LLONG_MAX == 0x7F && ULLONG_MAX == 0xFF +typedef unsigned long long uint8_t; +typedef signed long long int8_t; +#define INT8_C(c) c##LL +#define UINT8_C(c) c##LLU +#endif +#define INT8_MIN (~INT8_C(0x7F)) +#define INT8_MAX ( INT8_C(0x7F)) +#define UINT8_MAX ( INT8_C(0xFF)) + +#if SCHAR_MIN == ~0x7FFF && SCHAR_MAX == 0x7FFF && UCHAR_MAX == 0xFFFF +typedef unsigned char uint16_t; +typedef signed char int16_t; +#define INT16_C(c) c +#define UINT16_C(c) c##U +#elif SHRT_MIN == ~0x7FFF && SHRT_MAX == 0x7FFF && USHRT_MAX == 0xFFFF +typedef unsigned short uint16_t; +typedef signed short int16_t; +#define INT16_C(c) c +#define UINT16_C(c) c##U +#elif INT_MIN == ~0x7FFF && INT_MAX == 0x7FFF && UINT_MAX == 0xFFFF +typedef unsigned int uint16_t; +typedef signed int int16_t; +#define INT16_C(c) c +#define UINT16_C(c) c##U +#elif LONG_MIN == ~0x7FFF && LONG_MAX == 0x7FFF && ULONG_MAX == 0xFFFF +typedef unsigned long uint16_t; +typedef signed long int16_t; +#define INT16_C(c) c##L +#define UINT16_C(c) c##LU +#elif LLONG_MIN == ~0x7FFF && LLONG_MAX == 0x7FFF && ULLONG_MAX == 0xFFFF +typedef unsigned long long uint16_t; +typedef signed long long int16_t; +#define INT16_C(c) c##LL +#define UINT16_C(c) c##LLU +#endif +#define INT16_MIN (~INT16_C(0x7FFF)) +#define INT16_MAX ( INT16_C(0x7FFF)) +#define UINT16_MAX ( INT16_C(0xFFFF)) + +#if SCHAR_MIN == ~0x7FFFFFFF && SCHAR_MAX == 0x7FFFFFFF && UCHAR_MAX == 0xFFFFFFFF +typedef unsigned char uint32_t; +typedef signed char int32_t; +#define INT32_C(c) c +#define UINT32_C(c) c##U +#elif SHRT_MIN == ~0x7FFFFFFF && SHRT_MAX == 0x7FFFFFFF && USHRT_MAX == 0xFFFFFFFF +typedef unsigned short uint32_t; +typedef signed short int32_t; +#define INT32_C(c) c +#define UINT32_C(c) c##U +#elif INT_MIN == ~0x7FFFFFFF && INT_MAX == 0x7FFFFFFF && UINT_MAX == 0xFFFFFFFF +typedef unsigned int uint32_t; +typedef signed int int32_t; +#define INT32_C(c) c +#define UINT32_C(c) c##U +#elif LONG_MIN == ~0x7FFFFFFF && LONG_MAX == 0x7FFFFFFF && ULONG_MAX == 0xFFFFFFFF +typedef unsigned long uint32_t; +typedef signed long int32_t; +#define INT32_C(c) c##L +#define UINT32_C(c) c##LU +#elif LLONG_MIN == ~0x7FFFFFFF && LLONG_MAX == 0x7FFFFFFF && ULLONG_MAX == 0xFFFFFFFF +typedef unsigned long long uint32_t; +typedef signed long long int32_t; +#define INT32_C(c) c##LL +#define UINT32_C(c) c##LLU +#endif +#define INT32_MIN (~INT32_C(0x7FFFFFFF)) +#define INT32_MAX ( INT32_C(0x7FFFFFFF)) +#define UINT32_MAX ( INT32_C(0xFFFFFFFF)) + +#if SCHAR_MIN == ~0x7FFFFFFFFFFFFFFF && SCHAR_MAX == 0x7FFFFFFFFFFFFFFF && UCHAR_MAX == 0xFFFFFFFFFFFFFFFF +typedef unsigned char uint64_t; +typedef signed char int64_t; +#define INT64_C(c) c +#define UINT64_C(c) c##U +#elif SHRT_MIN == ~0x7FFFFFFFFFFFFFFF && SHRT_MAX == 0x7FFFFFFFFFFFFFFF && USHRT_MAX == 0xFFFFFFFFFFFFFFFF +typedef unsigned short uint64_t; +typedef signed short int64_t; +#define INT64_C(c) c +#define UINT64_C(c) c##U +#elif INT_MIN == ~0x7FFFFFFFFFFFFFFF && INT_MAX == 0x7FFFFFFFFFFFFFFF && UINT_MAX == 0xFFFFFFFFFFFFFFFF +typedef unsigned int uint64_t; +typedef signed int int64_t; +#define INT64_C(c) c +#define UINT64_C(c) c##U +#elif LONG_MIN == ~0x7FFFFFFFFFFFFFFF && LONG_MAX == 0x7FFFFFFFFFFFFFFF && ULONG_MAX == 0xFFFFFFFFFFFFFFFF +typedef unsigned long uint64_t; +typedef signed long int64_t; +#define INT64_C(c) c##L +#define UINT64_C(c) c##LU +#elif LLONG_MIN == ~0x7FFFFFFFFFFFFFFF && LLONG_MAX == 0x7FFFFFFFFFFFFFFF && ULLONG_MAX == 0xFFFFFFFFFFFFFFFF +typedef unsigned long long uint64_t; +typedef signed long long int64_t; +#define INT64_C(c) c##LL +#define UINT64_C(c) c##LLU +#endif +#define INT64_MIN (~INT64_C(0x7FFFFFFFFFFFFFFF)) +#define INT64_MAX ( INT64_C(0x7FFFFFFFFFFFFFFF)) +#define UINT64_MAX ( INT64_C(0xFFFFFFFFFFFFFFFF)) + +typedef size_t uintptr_t; +typedef ptrdiff_t intptr_t; + +#endif -#define zig_minInt_u8 zig_as_u8(0) -#define zig_maxInt_u8 UINT8_MAX #define zig_minInt_i8 INT8_MIN #define zig_maxInt_i8 INT8_MAX -#define zig_minInt_u16 zig_as_u16(0) -#define zig_maxInt_u16 UINT16_MAX +#define zig_minInt_u8 UINT8_C(0) +#define zig_maxInt_u8 UINT8_MAX #define zig_minInt_i16 INT16_MIN #define zig_maxInt_i16 INT16_MAX -#define zig_minInt_u32 zig_as_u32(0) -#define zig_maxInt_u32 UINT32_MAX +#define zig_minInt_u16 UINT16_C(0) +#define zig_maxInt_u16 UINT16_MAX #define zig_minInt_i32 INT32_MIN #define zig_maxInt_i32 INT32_MAX -#define zig_minInt_u64 zig_as_u64(0) -#define zig_maxInt_u64 UINT64_MAX +#define zig_minInt_u32 UINT32_C(0) +#define zig_maxInt_u32 UINT32_MAX #define zig_minInt_i64 INT64_MIN #define zig_maxInt_i64 INT64_MAX +#define zig_minInt_u64 UINT64_C(0) +#define zig_maxInt_u64 UINT64_MAX -#define zig_compiler_rt_abbrev_u32 si -#define zig_compiler_rt_abbrev_i32 si -#define zig_compiler_rt_abbrev_u64 di -#define zig_compiler_rt_abbrev_i64 di -#define zig_compiler_rt_abbrev_u128 ti -#define zig_compiler_rt_abbrev_i128 ti -#define zig_compiler_rt_abbrev_f16 hf -#define zig_compiler_rt_abbrev_f32 sf -#define zig_compiler_rt_abbrev_f64 df -#define zig_compiler_rt_abbrev_f80 xf -#define zig_compiler_rt_abbrev_f128 tf - -zig_extern void *memcpy (void *zig_restrict, void const *zig_restrict, zig_usize); -zig_extern void *memset (void *, int, zig_usize); - -/* ==================== 8/16/32/64-bit Integer Routines ===================== */ - -#define zig_maxInt(Type, bits) zig_shr_##Type(zig_maxInt_##Type, (zig_bitSizeOf(zig_##Type) - bits)) -#define zig_expand_maxInt(Type, bits) zig_maxInt(Type, bits) -#define zig_minInt(Type, bits) zig_not_##Type(zig_maxInt(Type, bits), bits) -#define zig_expand_minInt(Type, bits) zig_minInt(Type, bits) +#define zig_intLimit(s, w, limit, bits) zig_shr_##s##w(zig_##limit##Int_##s##w, w - (bits)) +#define zig_minInt_i(w, bits) zig_intLimit(i, w, min, bits) +#define zig_maxInt_i(w, bits) zig_intLimit(i, w, max, bits) +#define zig_minInt_u(w, bits) zig_intLimit(u, w, min, bits) +#define zig_maxInt_u(w, bits) zig_intLimit(u, w, max, bits) #define zig_int_operator(Type, RhsType, operation, operator) \ - static inline zig_##Type zig_##operation##_##Type(zig_##Type lhs, zig_##RhsType rhs) { \ + static inline Type zig_##operation(Type lhs, RhsType rhs) { \ return lhs operator rhs; \ } #define zig_int_basic_operator(Type, operation, operator) \ - zig_int_operator(Type, Type, operation, operator) + zig_int_operator(Type, Type, operation, operator) #define zig_int_shift_operator(Type, operation, operator) \ - zig_int_operator(Type, u8, operation, operator) + zig_int_operator(Type, uint8_t, operation, operator) #define zig_int_helpers(w) \ - zig_int_basic_operator(u##w, and, &) \ - zig_int_basic_operator(i##w, and, &) \ - zig_int_basic_operator(u##w, or, |) \ - zig_int_basic_operator(i##w, or, |) \ - zig_int_basic_operator(u##w, xor, ^) \ - zig_int_basic_operator(i##w, xor, ^) \ - zig_int_shift_operator(u##w, shl, <<) \ - zig_int_shift_operator(i##w, shl, <<) \ - zig_int_shift_operator(u##w, shr, >>) \ + zig_int_basic_operator(uint##w##_t, and_u##w, &) \ + zig_int_basic_operator( int##w##_t, and_i##w, &) \ + zig_int_basic_operator(uint##w##_t, or_u##w, |) \ + zig_int_basic_operator( int##w##_t, or_i##w, |) \ + zig_int_basic_operator(uint##w##_t, xor_u##w, ^) \ + zig_int_basic_operator( int##w##_t, xor_i##w, ^) \ + zig_int_shift_operator(uint##w##_t, shl_u##w, <<) \ + zig_int_shift_operator( int##w##_t, shl_i##w, <<) \ + zig_int_shift_operator(uint##w##_t, shr_u##w, >>) \ \ - static inline zig_i##w zig_shr_i##w(zig_i##w lhs, zig_u8 rhs) { \ - zig_i##w sign_mask = lhs < zig_as_i##w(0) ? -zig_as_i##w(1) : zig_as_i##w(0); \ + static inline int##w##_t zig_shr_i##w(int##w##_t lhs, uint8_t rhs) { \ + int##w##_t sign_mask = lhs < INT##w##_C(0) ? -INT##w##_C(1) : INT##w##_C(0); \ return ((lhs ^ sign_mask) >> rhs) ^ sign_mask; \ } \ \ - static inline zig_u##w zig_not_u##w(zig_u##w val, zig_u8 bits) { \ - return val ^ zig_maxInt(u##w, bits); \ + static inline uint##w##_t zig_not_u##w(uint##w##_t val, uint8_t bits) { \ + return val ^ zig_maxInt_u(w, bits); \ } \ \ - static inline zig_i##w zig_not_i##w(zig_i##w val, zig_u8 bits) { \ + static inline int##w##_t zig_not_i##w(int##w##_t val, uint8_t bits) { \ (void)bits; \ return ~val; \ } \ \ - static inline zig_u##w zig_wrap_u##w(zig_u##w val, zig_u8 bits) { \ - return val & zig_maxInt(u##w, bits); \ + static inline uint##w##_t zig_wrap_u##w(uint##w##_t val, uint8_t bits) { \ + return val & zig_maxInt_u(w, bits); \ } \ \ - static inline zig_i##w zig_wrap_i##w(zig_i##w val, zig_u8 bits) { \ - return (val & zig_as_u##w(1) << (bits - zig_as_u8(1))) != 0 \ - ? val | zig_minInt(i##w, bits) : val & zig_maxInt(i##w, bits); \ + static inline int##w##_t zig_wrap_i##w(int##w##_t val, uint8_t bits) { \ + return (val & UINT##w##_C(1) << (bits - UINT8_C(1))) != 0 \ + ? val | zig_minInt_i(w, bits) : val & zig_maxInt_i(w, bits); \ } \ \ - zig_int_basic_operator(u##w, div_floor, /) \ + zig_int_basic_operator(uint##w##_t, div_floor_u##w, /) \ \ - static inline zig_i##w zig_div_floor_i##w(zig_i##w lhs, zig_i##w rhs) { \ - return lhs / rhs - (((lhs ^ rhs) & (lhs % rhs)) < zig_as_i##w(0)); \ + static inline int##w##_t zig_div_floor_i##w(int##w##_t lhs, int##w##_t rhs) { \ + return lhs / rhs - (((lhs ^ rhs) & (lhs % rhs)) < INT##w##_C(0)); \ } \ \ - zig_int_basic_operator(u##w, mod, %) \ + zig_int_basic_operator(uint##w##_t, mod_u##w, %) \ \ - static inline zig_i##w zig_mod_i##w(zig_i##w lhs, zig_i##w rhs) { \ - zig_i##w rem = lhs % rhs; \ - return rem + (((lhs ^ rhs) & rem) < zig_as_i##w(0) ? rhs : zig_as_i##w(0)); \ + static inline int##w##_t zig_mod_i##w(int##w##_t lhs, int##w##_t rhs) { \ + int##w##_t rem = lhs % rhs; \ + return rem + (((lhs ^ rhs) & rem) < INT##w##_C(0) ? rhs : INT##w##_C(0)); \ } \ \ - static inline zig_u##w zig_shlw_u##w(zig_u##w lhs, zig_u8 rhs, zig_u8 bits) { \ + static inline uint##w##_t zig_shlw_u##w(uint##w##_t lhs, uint8_t rhs, uint8_t bits) { \ return zig_wrap_u##w(zig_shl_u##w(lhs, rhs), bits); \ } \ \ - static inline zig_i##w zig_shlw_i##w(zig_i##w lhs, zig_u8 rhs, zig_u8 bits) { \ - return zig_wrap_i##w((zig_i##w)zig_shl_u##w((zig_u##w)lhs, (zig_u##w)rhs), bits); \ + static inline int##w##_t zig_shlw_i##w(int##w##_t lhs, uint8_t rhs, uint8_t bits) { \ + return zig_wrap_i##w((int##w##_t)zig_shl_u##w((uint##w##_t)lhs, (uint##w##_t)rhs), bits); \ } \ \ - static inline zig_u##w zig_addw_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ + static inline uint##w##_t zig_addw_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ return zig_wrap_u##w(lhs + rhs, bits); \ } \ \ - static inline zig_i##w zig_addw_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - return zig_wrap_i##w((zig_i##w)((zig_u##w)lhs + (zig_u##w)rhs), bits); \ + static inline int##w##_t zig_addw_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + return zig_wrap_i##w((int##w##_t)((uint##w##_t)lhs + (uint##w##_t)rhs), bits); \ } \ \ - static inline zig_u##w zig_subw_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ + static inline uint##w##_t zig_subw_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ return zig_wrap_u##w(lhs - rhs, bits); \ } \ \ - static inline zig_i##w zig_subw_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - return zig_wrap_i##w((zig_i##w)((zig_u##w)lhs - (zig_u##w)rhs), bits); \ + static inline int##w##_t zig_subw_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + return zig_wrap_i##w((int##w##_t)((uint##w##_t)lhs - (uint##w##_t)rhs), bits); \ } \ \ - static inline zig_u##w zig_mulw_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ + static inline uint##w##_t zig_mulw_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ return zig_wrap_u##w(lhs * rhs, bits); \ } \ \ - static inline zig_i##w zig_mulw_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - return zig_wrap_i##w((zig_i##w)((zig_u##w)lhs * (zig_u##w)rhs), bits); \ + static inline int##w##_t zig_mulw_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + return zig_wrap_i##w((int##w##_t)((uint##w##_t)lhs * (uint##w##_t)rhs), bits); \ } zig_int_helpers(8) zig_int_helpers(16) zig_int_helpers(32) zig_int_helpers(64) -static inline bool zig_addo_u32(zig_u32 *res, zig_u32 lhs, zig_u32 rhs, zig_u8 bits) { +static inline bool zig_addo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_u32 full_res; + uint32_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_u32(full_res, bits); - return overflow || full_res < zig_minInt(u32, bits) || full_res > zig_maxInt(u32, bits); + return overflow || full_res < zig_minInt_u(32, bits) || full_res > zig_maxInt_u(32, bits); #else *res = zig_addw_u32(lhs, rhs, bits); return *res < lhs; #endif } -static inline void zig_vaddo_u32(zig_u8 *ov, zig_u32 *res, int n, - const zig_u32 *lhs, const zig_u32 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u32(&res[i], lhs[i], rhs[i], bits); -} - -zig_extern zig_i32 __addosi4(zig_i32 lhs, zig_i32 rhs, zig_c_int *overflow); -static inline bool zig_addo_i32(zig_i32 *res, zig_i32 lhs, zig_i32 rhs, zig_u8 bits) { +zig_extern int32_t __addosi4(int32_t lhs, int32_t rhs, int *overflow); +static inline bool zig_addo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_i32 full_res; + int32_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; - zig_i32 full_res = __addosi4(lhs, rhs, &overflow_int); + int overflow_int; + int32_t full_res = __addosi4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i32(full_res, bits); - return overflow || full_res < zig_minInt(i32, bits) || full_res > zig_maxInt(i32, bits); + return overflow || full_res < zig_minInt_i(32, bits) || full_res > zig_maxInt_i(32, bits); } -static inline void zig_vaddo_i32(zig_u8 *ov, zig_i32 *res, int n, - const zig_i32 *lhs, const zig_i32 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i32(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_addo_u64(zig_u64 *res, zig_u64 lhs, zig_u64 rhs, zig_u8 bits) { +static inline bool zig_addo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_u64 full_res; + uint64_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_u64(full_res, bits); - return overflow || full_res < zig_minInt(u64, bits) || full_res > zig_maxInt(u64, bits); + return overflow || full_res < zig_minInt_u(64, bits) || full_res > zig_maxInt_u(64, bits); #else *res = zig_addw_u64(lhs, rhs, bits); return *res < lhs; #endif } -static inline void zig_vaddo_u64(zig_u8 *ov, zig_u64 *res, int n, - const zig_u64 *lhs, const zig_u64 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u64(&res[i], lhs[i], rhs[i], bits); -} - -zig_extern zig_i64 __addodi4(zig_i64 lhs, zig_i64 rhs, zig_c_int *overflow); -static inline bool zig_addo_i64(zig_i64 *res, zig_i64 lhs, zig_i64 rhs, zig_u8 bits) { +zig_extern int64_t __addodi4(int64_t lhs, int64_t rhs, int *overflow); +static inline bool zig_addo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_i64 full_res; + int64_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; - zig_i64 full_res = __addodi4(lhs, rhs, &overflow_int); + int overflow_int; + int64_t full_res = __addodi4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i64(full_res, bits); - return overflow || full_res < zig_minInt(i64, bits) || full_res > zig_maxInt(i64, bits); + return overflow || full_res < zig_minInt_i(64, bits) || full_res > zig_maxInt_i(64, bits); } -static inline void zig_vaddo_i64(zig_u8 *ov, zig_i64 *res, int n, - const zig_i64 *lhs, const zig_i64 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i64(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_addo_u8(zig_u8 *res, zig_u8 lhs, zig_u8 rhs, zig_u8 bits) { +static inline bool zig_addo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_u8 full_res; + uint8_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_u8(full_res, bits); - return overflow || full_res < zig_minInt(u8, bits) || full_res > zig_maxInt(u8, bits); + return overflow || full_res < zig_minInt_u(8, bits) || full_res > zig_maxInt_u(8, bits); #else - zig_u32 full_res; + uint32_t full_res; bool overflow = zig_addo_u32(&full_res, lhs, rhs, bits); - *res = (zig_u8)full_res; + *res = (uint8_t)full_res; return overflow; #endif } -static inline void zig_vaddo_u8(zig_u8 *ov, zig_u8 *res, int n, - const zig_u8 *lhs, const zig_u8 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u8(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_addo_i8(zig_i8 *res, zig_i8 lhs, zig_i8 rhs, zig_u8 bits) { +static inline bool zig_addo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_i8 full_res; + int8_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_i8(full_res, bits); - return overflow || full_res < zig_minInt(i8, bits) || full_res > zig_maxInt(i8, bits); + return overflow || full_res < zig_minInt_i(8, bits) || full_res > zig_maxInt_i(8, bits); #else - zig_i32 full_res; + int32_t full_res; bool overflow = zig_addo_i32(&full_res, lhs, rhs, bits); - *res = (zig_i8)full_res; + *res = (int8_t)full_res; return overflow; #endif } -static inline void zig_vaddo_i8(zig_u8 *ov, zig_i8 *res, int n, - const zig_i8 *lhs, const zig_i8 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i8(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_addo_u16(zig_u16 *res, zig_u16 lhs, zig_u16 rhs, zig_u8 bits) { +static inline bool zig_addo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_u16 full_res; + uint16_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_u16(full_res, bits); - return overflow || full_res < zig_minInt(u16, bits) || full_res > zig_maxInt(u16, bits); + return overflow || full_res < zig_minInt_u(16, bits) || full_res > zig_maxInt_u(16, bits); #else - zig_u32 full_res; + uint32_t full_res; bool overflow = zig_addo_u32(&full_res, lhs, rhs, bits); - *res = (zig_u16)full_res; + *res = (uint16_t)full_res; return overflow; #endif } -static inline void zig_vaddo_u16(zig_u8 *ov, zig_u16 *res, int n, - const zig_u16 *lhs, const zig_u16 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_u16(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_addo_i16(zig_i16 *res, zig_i16 lhs, zig_i16 rhs, zig_u8 bits) { +static inline bool zig_addo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) || defined(zig_gnuc) - zig_i16 full_res; + int16_t full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_i16(full_res, bits); - return overflow || full_res < zig_minInt(i16, bits) || full_res > zig_maxInt(i16, bits); + return overflow || full_res < zig_minInt_i(16, bits) || full_res > zig_maxInt_i(16, bits); #else - zig_i32 full_res; + int32_t full_res; bool overflow = zig_addo_i32(&full_res, lhs, rhs, bits); - *res = (zig_i16)full_res; + *res = (int16_t)full_res; return overflow; #endif } -static inline void zig_vaddo_i16(zig_u8 *ov, zig_i16 *res, int n, - const zig_i16 *lhs, const zig_i16 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_addo_i16(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_subo_u32(zig_u32 *res, zig_u32 lhs, zig_u32 rhs, zig_u8 bits) { +static inline bool zig_subo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_u32 full_res; + uint32_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_u32(full_res, bits); - return overflow || full_res < zig_minInt(u32, bits) || full_res > zig_maxInt(u32, bits); + return overflow || full_res < zig_minInt_u(32, bits) || full_res > zig_maxInt_u(32, bits); #else *res = zig_subw_u32(lhs, rhs, bits); return *res > lhs; #endif } -static inline void zig_vsubo_u32(zig_u8 *ov, zig_u32 *res, int n, - const zig_u32 *lhs, const zig_u32 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u32(&res[i], lhs[i], rhs[i], bits); -} - -zig_extern zig_i32 __subosi4(zig_i32 lhs, zig_i32 rhs, zig_c_int *overflow); -static inline bool zig_subo_i32(zig_i32 *res, zig_i32 lhs, zig_i32 rhs, zig_u8 bits) { +zig_extern int32_t __subosi4(int32_t lhs, int32_t rhs, int *overflow); +static inline bool zig_subo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_i32 full_res; + int32_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; - zig_i32 full_res = __subosi4(lhs, rhs, &overflow_int); + int overflow_int; + int32_t full_res = __subosi4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i32(full_res, bits); - return overflow || full_res < zig_minInt(i32, bits) || full_res > zig_maxInt(i32, bits); + return overflow || full_res < zig_minInt_i(32, bits) || full_res > zig_maxInt_i(32, bits); } -static inline void zig_vsubo_i32(zig_u8 *ov, zig_i32 *res, int n, - const zig_i32 *lhs, const zig_i32 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i32(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_subo_u64(zig_u64 *res, zig_u64 lhs, zig_u64 rhs, zig_u8 bits) { +static inline bool zig_subo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_u64 full_res; + uint64_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_u64(full_res, bits); - return overflow || full_res < zig_minInt(u64, bits) || full_res > zig_maxInt(u64, bits); + return overflow || full_res < zig_minInt_u(64, bits) || full_res > zig_maxInt_u(64, bits); #else *res = zig_subw_u64(lhs, rhs, bits); return *res > lhs; #endif } -static inline void zig_vsubo_u64(zig_u8 *ov, zig_u64 *res, int n, - const zig_u64 *lhs, const zig_u64 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u64(&res[i], lhs[i], rhs[i], bits); -} - -zig_extern zig_i64 __subodi4(zig_i64 lhs, zig_i64 rhs, zig_c_int *overflow); -static inline bool zig_subo_i64(zig_i64 *res, zig_i64 lhs, zig_i64 rhs, zig_u8 bits) { +zig_extern int64_t __subodi4(int64_t lhs, int64_t rhs, int *overflow); +static inline bool zig_subo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_i64 full_res; + int64_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; - zig_i64 full_res = __subodi4(lhs, rhs, &overflow_int); + int overflow_int; + int64_t full_res = __subodi4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i64(full_res, bits); - return overflow || full_res < zig_minInt(i64, bits) || full_res > zig_maxInt(i64, bits); + return overflow || full_res < zig_minInt_i(64, bits) || full_res > zig_maxInt_i(64, bits); } -static inline void zig_vsubo_i64(zig_u8 *ov, zig_i64 *res, int n, - const zig_i64 *lhs, const zig_i64 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i64(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_subo_u8(zig_u8 *res, zig_u8 lhs, zig_u8 rhs, zig_u8 bits) { +static inline bool zig_subo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_u8 full_res; + uint8_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_u8(full_res, bits); - return overflow || full_res < zig_minInt(u8, bits) || full_res > zig_maxInt(u8, bits); + return overflow || full_res < zig_minInt_u(8, bits) || full_res > zig_maxInt_u(8, bits); #else - zig_u32 full_res; + uint32_t full_res; bool overflow = zig_subo_u32(&full_res, lhs, rhs, bits); - *res = (zig_u8)full_res; + *res = (uint8_t)full_res; return overflow; #endif } -static inline void zig_vsubo_u8(zig_u8 *ov, zig_u8 *res, int n, - const zig_u8 *lhs, const zig_u8 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u8(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_subo_i8(zig_i8 *res, zig_i8 lhs, zig_i8 rhs, zig_u8 bits) { +static inline bool zig_subo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_i8 full_res; + int8_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_i8(full_res, bits); - return overflow || full_res < zig_minInt(i8, bits) || full_res > zig_maxInt(i8, bits); + return overflow || full_res < zig_minInt_i(8, bits) || full_res > zig_maxInt_i(8, bits); #else - zig_i32 full_res; + int32_t full_res; bool overflow = zig_subo_i32(&full_res, lhs, rhs, bits); - *res = (zig_i8)full_res; + *res = (int8_t)full_res; return overflow; #endif } -static inline void zig_vsubo_i8(zig_u8 *ov, zig_i8 *res, int n, - const zig_i8 *lhs, const zig_i8 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i8(&res[i], lhs[i], rhs[i], bits); -} - - -static inline bool zig_subo_u16(zig_u16 *res, zig_u16 lhs, zig_u16 rhs, zig_u8 bits) { +static inline bool zig_subo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_u16 full_res; + uint16_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_u16(full_res, bits); - return overflow || full_res < zig_minInt(u16, bits) || full_res > zig_maxInt(u16, bits); + return overflow || full_res < zig_minInt_u(16, bits) || full_res > zig_maxInt_u(16, bits); #else - zig_u32 full_res; + uint32_t full_res; bool overflow = zig_subo_u32(&full_res, lhs, rhs, bits); - *res = (zig_u16)full_res; + *res = (uint16_t)full_res; return overflow; #endif } -static inline void zig_vsubo_u16(zig_u8 *ov, zig_u16 *res, int n, - const zig_u16 *lhs, const zig_u16 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_u16(&res[i], lhs[i], rhs[i], bits); -} - - -static inline bool zig_subo_i16(zig_i16 *res, zig_i16 lhs, zig_i16 rhs, zig_u8 bits) { +static inline bool zig_subo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) || defined(zig_gnuc) - zig_i16 full_res; + int16_t full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_i16(full_res, bits); - return overflow || full_res < zig_minInt(i16, bits) || full_res > zig_maxInt(i16, bits); + return overflow || full_res < zig_minInt_i(16, bits) || full_res > zig_maxInt_i(16, bits); #else - zig_i32 full_res; + int32_t full_res; bool overflow = zig_subo_i32(&full_res, lhs, rhs, bits); - *res = (zig_i16)full_res; + *res = (int16_t)full_res; return overflow; #endif } -static inline void zig_vsubo_i16(zig_u8 *ov, zig_i16 *res, int n, - const zig_i16 *lhs, const zig_i16 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_subo_i16(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_mulo_u32(zig_u32 *res, zig_u32 lhs, zig_u32 rhs, zig_u8 bits) { +static inline bool zig_mulo_u32(uint32_t *res, uint32_t lhs, uint32_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_u32 full_res; + uint32_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_u32(full_res, bits); - return overflow || full_res < zig_minInt(u32, bits) || full_res > zig_maxInt(u32, bits); + return overflow || full_res < zig_minInt_u(32, bits) || full_res > zig_maxInt_u(32, bits); #else *res = zig_mulw_u32(lhs, rhs, bits); - return rhs != zig_as_u32(0) && lhs > zig_maxInt(u32, bits) / rhs; + return rhs != UINT32_C(0) && lhs > zig_maxInt_u(32, bits) / rhs; #endif } -static inline void zig_vmulo_u32(zig_u8 *ov, zig_u32 *res, int n, - const zig_u32 *lhs, const zig_u32 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u32(&res[i], lhs[i], rhs[i], bits); -} - -zig_extern zig_i32 __mulosi4(zig_i32 lhs, zig_i32 rhs, zig_c_int *overflow); -static inline bool zig_mulo_i32(zig_i32 *res, zig_i32 lhs, zig_i32 rhs, zig_u8 bits) { +zig_extern int32_t __mulosi4(int32_t lhs, int32_t rhs, int *overflow); +static inline bool zig_mulo_i32(int32_t *res, int32_t lhs, int32_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_i32 full_res; + int32_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; - zig_i32 full_res = __mulosi4(lhs, rhs, &overflow_int); + int overflow_int; + int32_t full_res = __mulosi4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i32(full_res, bits); - return overflow || full_res < zig_minInt(i32, bits) || full_res > zig_maxInt(i32, bits); + return overflow || full_res < zig_minInt_i(32, bits) || full_res > zig_maxInt_i(32, bits); } -static inline void zig_vmulo_i32(zig_u8 *ov, zig_i32 *res, int n, - const zig_i32 *lhs, const zig_i32 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i32(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_mulo_u64(zig_u64 *res, zig_u64 lhs, zig_u64 rhs, zig_u8 bits) { +static inline bool zig_mulo_u64(uint64_t *res, uint64_t lhs, uint64_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_u64 full_res; + uint64_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_u64(full_res, bits); - return overflow || full_res < zig_minInt(u64, bits) || full_res > zig_maxInt(u64, bits); + return overflow || full_res < zig_minInt_u(64, bits) || full_res > zig_maxInt_u(64, bits); #else *res = zig_mulw_u64(lhs, rhs, bits); - return rhs != zig_as_u64(0) && lhs > zig_maxInt(u64, bits) / rhs; + return rhs != UINT64_C(0) && lhs > zig_maxInt_u(64, bits) / rhs; #endif } -static inline void zig_vmulo_u64(zig_u8 *ov, zig_u64 *res, int n, - const zig_u64 *lhs, const zig_u64 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u64(&res[i], lhs[i], rhs[i], bits); -} - -zig_extern zig_i64 __mulodi4(zig_i64 lhs, zig_i64 rhs, zig_c_int *overflow); -static inline bool zig_mulo_i64(zig_i64 *res, zig_i64 lhs, zig_i64 rhs, zig_u8 bits) { +zig_extern int64_t __mulodi4(int64_t lhs, int64_t rhs, int *overflow); +static inline bool zig_mulo_i64(int64_t *res, int64_t lhs, int64_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_i64 full_res; + int64_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; - zig_i64 full_res = __mulodi4(lhs, rhs, &overflow_int); + int overflow_int; + int64_t full_res = __mulodi4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i64(full_res, bits); - return overflow || full_res < zig_minInt(i64, bits) || full_res > zig_maxInt(i64, bits); + return overflow || full_res < zig_minInt_i(64, bits) || full_res > zig_maxInt_i(64, bits); } -static inline void zig_vmulo_i64(zig_u8 *ov, zig_i64 *res, int n, - const zig_i64 *lhs, const zig_i64 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i64(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_mulo_u8(zig_u8 *res, zig_u8 lhs, zig_u8 rhs, zig_u8 bits) { +static inline bool zig_mulo_u8(uint8_t *res, uint8_t lhs, uint8_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_u8 full_res; + uint8_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_u8(full_res, bits); - return overflow || full_res < zig_minInt(u8, bits) || full_res > zig_maxInt(u8, bits); + return overflow || full_res < zig_minInt_u(8, bits) || full_res > zig_maxInt_u(8, bits); #else - zig_u32 full_res; + uint32_t full_res; bool overflow = zig_mulo_u32(&full_res, lhs, rhs, bits); - *res = (zig_u8)full_res; + *res = (uint8_t)full_res; return overflow; #endif } -static inline void zig_vmulo_u8(zig_u8 *ov, zig_u8 *res, int n, - const zig_u8 *lhs, const zig_u8 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u8(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_mulo_i8(zig_i8 *res, zig_i8 lhs, zig_i8 rhs, zig_u8 bits) { +static inline bool zig_mulo_i8(int8_t *res, int8_t lhs, int8_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_i8 full_res; + int8_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_i8(full_res, bits); - return overflow || full_res < zig_minInt(i8, bits) || full_res > zig_maxInt(i8, bits); + return overflow || full_res < zig_minInt_i(8, bits) || full_res > zig_maxInt_i(8, bits); #else - zig_i32 full_res; + int32_t full_res; bool overflow = zig_mulo_i32(&full_res, lhs, rhs, bits); - *res = (zig_i8)full_res; + *res = (int8_t)full_res; return overflow; #endif } -static inline void zig_vmulo_i8(zig_u8 *ov, zig_i8 *res, int n, - const zig_i8 *lhs, const zig_i8 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i8(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_mulo_u16(zig_u16 *res, zig_u16 lhs, zig_u16 rhs, zig_u8 bits) { +static inline bool zig_mulo_u16(uint16_t *res, uint16_t lhs, uint16_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_u16 full_res; + uint16_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_u16(full_res, bits); - return overflow || full_res < zig_minInt(u16, bits) || full_res > zig_maxInt(u16, bits); + return overflow || full_res < zig_minInt_u(16, bits) || full_res > zig_maxInt_u(16, bits); #else - zig_u32 full_res; + uint32_t full_res; bool overflow = zig_mulo_u32(&full_res, lhs, rhs, bits); - *res = (zig_u16)full_res; + *res = (uint16_t)full_res; return overflow; #endif } -static inline void zig_vmulo_u16(zig_u8 *ov, zig_u16 *res, int n, - const zig_u16 *lhs, const zig_u16 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_u16(&res[i], lhs[i], rhs[i], bits); -} - -static inline bool zig_mulo_i16(zig_i16 *res, zig_i16 lhs, zig_i16 rhs, zig_u8 bits) { +static inline bool zig_mulo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) || defined(zig_gnuc) - zig_i16 full_res; + int16_t full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_i16(full_res, bits); - return overflow || full_res < zig_minInt(i16, bits) || full_res > zig_maxInt(i16, bits); + return overflow || full_res < zig_minInt_i(16, bits) || full_res > zig_maxInt_i(16, bits); #else - zig_i32 full_res; + int32_t full_res; bool overflow = zig_mulo_i32(&full_res, lhs, rhs, bits); - *res = (zig_i16)full_res; + *res = (int16_t)full_res; return overflow; #endif } -static inline void zig_vmulo_i16(zig_u8 *ov, zig_i16 *res, int n, - const zig_i16 *lhs, const zig_i16 *rhs, zig_u8 bits) -{ - for (int i = 0; i < n; ++i) ov[i] = zig_mulo_i16(&res[i], lhs[i], rhs[i], bits); -} - #define zig_int_builtins(w) \ - static inline bool zig_shlo_u##w(zig_u##w *res, zig_u##w lhs, zig_u8 rhs, zig_u8 bits) { \ + static inline bool zig_shlo_u##w(uint##w##_t *res, uint##w##_t lhs, uint8_t rhs, uint8_t bits) { \ *res = zig_shlw_u##w(lhs, rhs, bits); \ - return lhs > zig_maxInt(u##w, bits) >> rhs; \ + return lhs > zig_maxInt_u(w, bits) >> rhs; \ } \ \ - static inline bool zig_shlo_i##w(zig_i##w *res, zig_i##w lhs, zig_u8 rhs, zig_u8 bits) { \ + static inline bool zig_shlo_i##w(int##w##_t *res, int##w##_t lhs, uint8_t rhs, uint8_t bits) { \ *res = zig_shlw_i##w(lhs, rhs, bits); \ - zig_i##w mask = (zig_i##w)(zig_maxInt_u##w << (bits - rhs - 1)); \ - return (lhs & mask) != zig_as_i##w(0) && (lhs & mask) != mask; \ + int##w##_t mask = (int##w##_t)(UINT##w##_MAX << (bits - rhs - 1)); \ + return (lhs & mask) != INT##w##_C(0) && (lhs & mask) != mask; \ } \ \ - static inline zig_u##w zig_shls_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ - zig_u##w res; \ - if (rhs >= bits) return lhs != zig_as_u##w(0) ? zig_maxInt(u##w, bits) : lhs; \ - return zig_shlo_u##w(&res, lhs, (zig_u8)rhs, bits) ? zig_maxInt(u##w, bits) : res; \ + static inline uint##w##_t zig_shls_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ + uint##w##_t res; \ + if (rhs >= bits) return lhs != UINT##w##_C(0) ? zig_maxInt_u(w, bits) : lhs; \ + return zig_shlo_u##w(&res, lhs, (uint8_t)rhs, bits) ? zig_maxInt_u(w, bits) : res; \ } \ \ - static inline zig_i##w zig_shls_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - zig_i##w res; \ - if ((zig_u##w)rhs < (zig_u##w)bits && !zig_shlo_i##w(&res, lhs, rhs, bits)) return res; \ - return lhs < zig_as_i##w(0) ? zig_minInt(i##w, bits) : zig_maxInt(i##w, bits); \ + static inline int##w##_t zig_shls_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + int##w##_t res; \ + if ((uint##w##_t)rhs < (uint##w##_t)bits && !zig_shlo_i##w(&res, lhs, (uint8_t)rhs, bits)) return res; \ + return lhs < INT##w##_C(0) ? zig_minInt_i(w, bits) : zig_maxInt_i(w, bits); \ } \ \ - static inline zig_u##w zig_adds_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ - zig_u##w res; \ - return zig_addo_u##w(&res, lhs, rhs, bits) ? zig_maxInt(u##w, bits) : res; \ + static inline uint##w##_t zig_adds_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ + uint##w##_t res; \ + return zig_addo_u##w(&res, lhs, rhs, bits) ? zig_maxInt_u(w, bits) : res; \ } \ \ - static inline zig_i##w zig_adds_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - zig_i##w res; \ + static inline int##w##_t zig_adds_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + int##w##_t res; \ if (!zig_addo_i##w(&res, lhs, rhs, bits)) return res; \ - return res >= zig_as_i##w(0) ? zig_minInt(i##w, bits) : zig_maxInt(i##w, bits); \ + return res >= INT##w##_C(0) ? zig_minInt_i(w, bits) : zig_maxInt_i(w, bits); \ } \ \ - static inline zig_u##w zig_subs_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ - zig_u##w res; \ - return zig_subo_u##w(&res, lhs, rhs, bits) ? zig_minInt(u##w, bits) : res; \ + static inline uint##w##_t zig_subs_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ + uint##w##_t res; \ + return zig_subo_u##w(&res, lhs, rhs, bits) ? zig_minInt_u(w, bits) : res; \ } \ \ - static inline zig_i##w zig_subs_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - zig_i##w res; \ + static inline int##w##_t zig_subs_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + int##w##_t res; \ if (!zig_subo_i##w(&res, lhs, rhs, bits)) return res; \ - return res >= zig_as_i##w(0) ? zig_minInt(i##w, bits) : zig_maxInt(i##w, bits); \ + return res >= INT##w##_C(0) ? zig_minInt_i(w, bits) : zig_maxInt_i(w, bits); \ } \ \ - static inline zig_u##w zig_muls_u##w(zig_u##w lhs, zig_u##w rhs, zig_u8 bits) { \ - zig_u##w res; \ - return zig_mulo_u##w(&res, lhs, rhs, bits) ? zig_maxInt(u##w, bits) : res; \ + static inline uint##w##_t zig_muls_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \ + uint##w##_t res; \ + return zig_mulo_u##w(&res, lhs, rhs, bits) ? zig_maxInt_u(w, bits) : res; \ } \ \ - static inline zig_i##w zig_muls_i##w(zig_i##w lhs, zig_i##w rhs, zig_u8 bits) { \ - zig_i##w res; \ + static inline int##w##_t zig_muls_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \ + int##w##_t res; \ if (!zig_mulo_i##w(&res, lhs, rhs, bits)) return res; \ - return (lhs ^ rhs) < zig_as_i##w(0) ? zig_minInt(i##w, bits) : zig_maxInt(i##w, bits); \ + return (lhs ^ rhs) < INT##w##_C(0) ? zig_minInt_i(w, bits) : zig_maxInt_i(w, bits); \ } zig_int_builtins(8) zig_int_builtins(16) @@ -988,89 +986,89 @@ zig_int_builtins(32) zig_int_builtins(64) #define zig_builtin8(name, val) __builtin_##name(val) -typedef zig_c_uint zig_Builtin8; +typedef unsigned int zig_Builtin8; #define zig_builtin16(name, val) __builtin_##name(val) -typedef zig_c_uint zig_Builtin16; +typedef unsigned int zig_Builtin16; #if INT_MIN <= INT32_MIN #define zig_builtin32(name, val) __builtin_##name(val) -typedef zig_c_uint zig_Builtin32; +typedef unsigned int zig_Builtin32; #elif LONG_MIN <= INT32_MIN #define zig_builtin32(name, val) __builtin_##name##l(val) -typedef zig_c_ulong zig_Builtin32; +typedef unsigned long zig_Builtin32; #endif #if INT_MIN <= INT64_MIN #define zig_builtin64(name, val) __builtin_##name(val) -typedef zig_c_uint zig_Builtin64; +typedef unsigned int zig_Builtin64; #elif LONG_MIN <= INT64_MIN #define zig_builtin64(name, val) __builtin_##name##l(val) -typedef zig_c_ulong zig_Builtin64; +typedef unsigned long zig_Builtin64; #elif LLONG_MIN <= INT64_MIN #define zig_builtin64(name, val) __builtin_##name##ll(val) -typedef zig_c_ulonglong zig_Builtin64; +typedef unsigned long long zig_Builtin64; #endif -static inline zig_u8 zig_byte_swap_u8(zig_u8 val, zig_u8 bits) { +static inline uint8_t zig_byte_swap_u8(uint8_t val, uint8_t bits) { return zig_wrap_u8(val >> (8 - bits), bits); } -static inline zig_i8 zig_byte_swap_i8(zig_i8 val, zig_u8 bits) { - return zig_wrap_i8((zig_i8)zig_byte_swap_u8((zig_u8)val, bits), bits); +static inline int8_t zig_byte_swap_i8(int8_t val, uint8_t bits) { + return zig_wrap_i8((int8_t)zig_byte_swap_u8((uint8_t)val, bits), bits); } -static inline zig_u16 zig_byte_swap_u16(zig_u16 val, zig_u8 bits) { - zig_u16 full_res; +static inline uint16_t zig_byte_swap_u16(uint16_t val, uint8_t bits) { + uint16_t full_res; #if zig_has_builtin(bswap16) || defined(zig_gnuc) full_res = __builtin_bswap16(val); #else - full_res = (zig_u16)zig_byte_swap_u8((zig_u8)(val >> 0), 8) << 8 | - (zig_u16)zig_byte_swap_u8((zig_u8)(val >> 8), 8) >> 0; + full_res = (uint16_t)zig_byte_swap_u8((uint8_t)(val >> 0), 8) << 8 | + (uint16_t)zig_byte_swap_u8((uint8_t)(val >> 8), 8) >> 0; #endif return zig_wrap_u16(full_res >> (16 - bits), bits); } -static inline zig_i16 zig_byte_swap_i16(zig_i16 val, zig_u8 bits) { - return zig_wrap_i16((zig_i16)zig_byte_swap_u16((zig_u16)val, bits), bits); +static inline int16_t zig_byte_swap_i16(int16_t val, uint8_t bits) { + return zig_wrap_i16((int16_t)zig_byte_swap_u16((uint16_t)val, bits), bits); } -static inline zig_u32 zig_byte_swap_u32(zig_u32 val, zig_u8 bits) { - zig_u32 full_res; +static inline uint32_t zig_byte_swap_u32(uint32_t val, uint8_t bits) { + uint32_t full_res; #if zig_has_builtin(bswap32) || defined(zig_gnuc) full_res = __builtin_bswap32(val); #else - full_res = (zig_u32)zig_byte_swap_u16((zig_u16)(val >> 0), 16) << 16 | - (zig_u32)zig_byte_swap_u16((zig_u16)(val >> 16), 16) >> 0; + full_res = (uint32_t)zig_byte_swap_u16((uint16_t)(val >> 0), 16) << 16 | + (uint32_t)zig_byte_swap_u16((uint16_t)(val >> 16), 16) >> 0; #endif return zig_wrap_u32(full_res >> (32 - bits), bits); } -static inline zig_i32 zig_byte_swap_i32(zig_i32 val, zig_u8 bits) { - return zig_wrap_i32((zig_i32)zig_byte_swap_u32((zig_u32)val, bits), bits); +static inline int32_t zig_byte_swap_i32(int32_t val, uint8_t bits) { + return zig_wrap_i32((int32_t)zig_byte_swap_u32((uint32_t)val, bits), bits); } -static inline zig_u64 zig_byte_swap_u64(zig_u64 val, zig_u8 bits) { - zig_u64 full_res; +static inline uint64_t zig_byte_swap_u64(uint64_t val, uint8_t bits) { + uint64_t full_res; #if zig_has_builtin(bswap64) || defined(zig_gnuc) full_res = __builtin_bswap64(val); #else - full_res = (zig_u64)zig_byte_swap_u32((zig_u32)(val >> 0), 32) << 32 | - (zig_u64)zig_byte_swap_u32((zig_u32)(val >> 32), 32) >> 0; + full_res = (uint64_t)zig_byte_swap_u32((uint32_t)(val >> 0), 32) << 32 | + (uint64_t)zig_byte_swap_u32((uint32_t)(val >> 32), 32) >> 0; #endif return zig_wrap_u64(full_res >> (64 - bits), bits); } -static inline zig_i64 zig_byte_swap_i64(zig_i64 val, zig_u8 bits) { - return zig_wrap_i64((zig_i64)zig_byte_swap_u64((zig_u64)val, bits), bits); +static inline int64_t zig_byte_swap_i64(int64_t val, uint8_t bits) { + return zig_wrap_i64((int64_t)zig_byte_swap_u64((uint64_t)val, bits), bits); } -static inline zig_u8 zig_bit_reverse_u8(zig_u8 val, zig_u8 bits) { - zig_u8 full_res; +static inline uint8_t zig_bit_reverse_u8(uint8_t val, uint8_t bits) { + uint8_t full_res; #if zig_has_builtin(bitreverse8) full_res = __builtin_bitreverse8(val); #else - static zig_u8 const lut[0x10] = { + static uint8_t const lut[0x10] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; @@ -1079,62 +1077,62 @@ static inline zig_u8 zig_bit_reverse_u8(zig_u8 val, zig_u8 bits) { return zig_wrap_u8(full_res >> (8 - bits), bits); } -static inline zig_i8 zig_bit_reverse_i8(zig_i8 val, zig_u8 bits) { - return zig_wrap_i8((zig_i8)zig_bit_reverse_u8((zig_u8)val, bits), bits); +static inline int8_t zig_bit_reverse_i8(int8_t val, uint8_t bits) { + return zig_wrap_i8((int8_t)zig_bit_reverse_u8((uint8_t)val, bits), bits); } -static inline zig_u16 zig_bit_reverse_u16(zig_u16 val, zig_u8 bits) { - zig_u16 full_res; +static inline uint16_t zig_bit_reverse_u16(uint16_t val, uint8_t bits) { + uint16_t full_res; #if zig_has_builtin(bitreverse16) full_res = __builtin_bitreverse16(val); #else - full_res = (zig_u16)zig_bit_reverse_u8((zig_u8)(val >> 0), 8) << 8 | - (zig_u16)zig_bit_reverse_u8((zig_u8)(val >> 8), 8) >> 0; + full_res = (uint16_t)zig_bit_reverse_u8((uint8_t)(val >> 0), 8) << 8 | + (uint16_t)zig_bit_reverse_u8((uint8_t)(val >> 8), 8) >> 0; #endif return zig_wrap_u16(full_res >> (16 - bits), bits); } -static inline zig_i16 zig_bit_reverse_i16(zig_i16 val, zig_u8 bits) { - return zig_wrap_i16((zig_i16)zig_bit_reverse_u16((zig_u16)val, bits), bits); +static inline int16_t zig_bit_reverse_i16(int16_t val, uint8_t bits) { + return zig_wrap_i16((int16_t)zig_bit_reverse_u16((uint16_t)val, bits), bits); } -static inline zig_u32 zig_bit_reverse_u32(zig_u32 val, zig_u8 bits) { - zig_u32 full_res; +static inline uint32_t zig_bit_reverse_u32(uint32_t val, uint8_t bits) { + uint32_t full_res; #if zig_has_builtin(bitreverse32) full_res = __builtin_bitreverse32(val); #else - full_res = (zig_u32)zig_bit_reverse_u16((zig_u16)(val >> 0), 16) << 16 | - (zig_u32)zig_bit_reverse_u16((zig_u16)(val >> 16), 16) >> 0; + full_res = (uint32_t)zig_bit_reverse_u16((uint16_t)(val >> 0), 16) << 16 | + (uint32_t)zig_bit_reverse_u16((uint16_t)(val >> 16), 16) >> 0; #endif return zig_wrap_u32(full_res >> (32 - bits), bits); } -static inline zig_i32 zig_bit_reverse_i32(zig_i32 val, zig_u8 bits) { - return zig_wrap_i32((zig_i32)zig_bit_reverse_u32((zig_u32)val, bits), bits); +static inline int32_t zig_bit_reverse_i32(int32_t val, uint8_t bits) { + return zig_wrap_i32((int32_t)zig_bit_reverse_u32((uint32_t)val, bits), bits); } -static inline zig_u64 zig_bit_reverse_u64(zig_u64 val, zig_u8 bits) { - zig_u64 full_res; +static inline uint64_t zig_bit_reverse_u64(uint64_t val, uint8_t bits) { + uint64_t full_res; #if zig_has_builtin(bitreverse64) full_res = __builtin_bitreverse64(val); #else - full_res = (zig_u64)zig_bit_reverse_u32((zig_u32)(val >> 0), 32) << 32 | - (zig_u64)zig_bit_reverse_u32((zig_u32)(val >> 32), 32) >> 0; + full_res = (uint64_t)zig_bit_reverse_u32((uint32_t)(val >> 0), 32) << 32 | + (uint64_t)zig_bit_reverse_u32((uint32_t)(val >> 32), 32) >> 0; #endif return zig_wrap_u64(full_res >> (64 - bits), bits); } -static inline zig_i64 zig_bit_reverse_i64(zig_i64 val, zig_u8 bits) { - return zig_wrap_i64((zig_i64)zig_bit_reverse_u64((zig_u64)val, bits), bits); +static inline int64_t zig_bit_reverse_i64(int64_t val, uint8_t bits) { + return zig_wrap_i64((int64_t)zig_bit_reverse_u64((uint64_t)val, bits), bits); } #define zig_builtin_popcount_common(w) \ - static inline zig_u8 zig_popcount_i##w(zig_i##w val, zig_u8 bits) { \ - return zig_popcount_u##w((zig_u##w)val, bits); \ + static inline uint8_t zig_popcount_i##w(int##w##_t val, uint8_t bits) { \ + return zig_popcount_u##w((uint##w##_t)val, bits); \ } #if zig_has_builtin(popcount) || defined(zig_gnuc) #define zig_builtin_popcount(w) \ - static inline zig_u8 zig_popcount_u##w(zig_u##w val, zig_u8 bits) { \ + static inline uint8_t zig_popcount_u##w(uint##w##_t val, uint8_t bits) { \ (void)bits; \ return zig_builtin##w(popcount, val); \ } \ @@ -1142,12 +1140,12 @@ static inline zig_i64 zig_bit_reverse_i64(zig_i64 val, zig_u8 bits) { zig_builtin_popcount_common(w) #else #define zig_builtin_popcount(w) \ - static inline zig_u8 zig_popcount_u##w(zig_u##w val, zig_u8 bits) { \ + static inline uint8_t zig_popcount_u##w(uint##w##_t val, uint8_t bits) { \ (void)bits; \ - zig_u##w temp = val - ((val >> 1) & (zig_maxInt_u##w / 3)); \ - temp = (temp & (zig_maxInt_u##w / 5)) + ((temp >> 2) & (zig_maxInt_u##w / 5)); \ - temp = (temp + (temp >> 4)) & (zig_maxInt_u##w / 17); \ - return temp * (zig_maxInt_u##w / 255) >> (w - 8); \ + uint##w##_t temp = val - ((val >> 1) & (UINT##w##_MAX / 3)); \ + temp = (temp & (UINT##w##_MAX / 5)) + ((temp >> 2) & (UINT##w##_MAX / 5)); \ + temp = (temp + (temp >> 4)) & (UINT##w##_MAX / 17); \ + return temp * (UINT##w##_MAX / 255) >> (w - 8); \ } \ \ zig_builtin_popcount_common(w) @@ -1158,12 +1156,12 @@ zig_builtin_popcount(32) zig_builtin_popcount(64) #define zig_builtin_ctz_common(w) \ - static inline zig_u8 zig_ctz_i##w(zig_i##w val, zig_u8 bits) { \ - return zig_ctz_u##w((zig_u##w)val, bits); \ + static inline uint8_t zig_ctz_i##w(int##w##_t val, uint8_t bits) { \ + return zig_ctz_u##w((uint##w##_t)val, bits); \ } #if zig_has_builtin(ctz) || defined(zig_gnuc) #define zig_builtin_ctz(w) \ - static inline zig_u8 zig_ctz_u##w(zig_u##w val, zig_u8 bits) { \ + static inline uint8_t zig_ctz_u##w(uint##w##_t val, uint8_t bits) { \ if (val == 0) return bits; \ return zig_builtin##w(ctz, val); \ } \ @@ -1171,7 +1169,7 @@ zig_builtin_popcount(64) zig_builtin_ctz_common(w) #else #define zig_builtin_ctz(w) \ - static inline zig_u8 zig_ctz_u##w(zig_u##w val, zig_u8 bits) { \ + static inline uint8_t zig_ctz_u##w(uint##w##_t val, uint8_t bits) { \ return zig_popcount_u##w(zig_not_u##w(val, bits) & zig_subw_u##w(val, 1, bits), bits); \ } \ \ @@ -1183,12 +1181,12 @@ zig_builtin_ctz(32) zig_builtin_ctz(64) #define zig_builtin_clz_common(w) \ - static inline zig_u8 zig_clz_i##w(zig_i##w val, zig_u8 bits) { \ - return zig_clz_u##w((zig_u##w)val, bits); \ + static inline uint8_t zig_clz_i##w(int##w##_t val, uint8_t bits) { \ + return zig_clz_u##w((uint##w##_t)val, bits); \ } #if zig_has_builtin(clz) || defined(zig_gnuc) #define zig_builtin_clz(w) \ - static inline zig_u8 zig_clz_u##w(zig_u##w val, zig_u8 bits) { \ + static inline uint8_t zig_clz_u##w(uint##w##_t val, uint8_t bits) { \ if (val == 0) return bits; \ return zig_builtin##w(clz, val) - (zig_bitSizeOf(zig_Builtin##w) - bits); \ } \ @@ -1196,7 +1194,7 @@ zig_builtin_ctz(64) zig_builtin_clz_common(w) #else #define zig_builtin_clz(w) \ - static inline zig_u8 zig_clz_u##w(zig_u##w val, zig_u8 bits) { \ + static inline uint8_t zig_clz_u##w(uint##w##_t val, uint8_t bits) { \ return zig_ctz_u##w(zig_bit_reverse_u##w(val, bits), bits); \ } \ \ @@ -1207,7 +1205,7 @@ zig_builtin_clz(16) zig_builtin_clz(32) zig_builtin_clz(64) -/* ======================== 128-bit Integer Routines ======================== */ +/* ======================== 128-bit Integer Support ========================= */ #if !defined(zig_has_int128) # if defined(__SIZEOF_INT128__) @@ -1222,18 +1220,18 @@ zig_builtin_clz(64) typedef unsigned __int128 zig_u128; typedef signed __int128 zig_i128; -#define zig_as_u128(hi, lo) ((zig_u128)(hi)<<64|(lo)) -#define zig_as_i128(hi, lo) ((zig_i128)zig_as_u128(hi, lo)) -#define zig_as_constant_u128(hi, lo) zig_as_u128(hi, lo) -#define zig_as_constant_i128(hi, lo) zig_as_i128(hi, lo) -#define zig_hi_u128(val) ((zig_u64)((val) >> 64)) -#define zig_lo_u128(val) ((zig_u64)((val) >> 0)) -#define zig_hi_i128(val) ((zig_i64)((val) >> 64)) -#define zig_lo_i128(val) ((zig_u64)((val) >> 0)) +#define zig_make_u128(hi, lo) ((zig_u128)(hi)<<64|(lo)) +#define zig_make_i128(hi, lo) ((zig_i128)zig_make_u128(hi, lo)) +#define zig_init_u128(hi, lo) zig_make_u128(hi, lo) +#define zig_init_i128(hi, lo) zig_make_i128(hi, lo) +#define zig_hi_u128(val) ((uint64_t)((val) >> 64)) +#define zig_lo_u128(val) ((uint64_t)((val) >> 0)) +#define zig_hi_i128(val) (( int64_t)((val) >> 64)) +#define zig_lo_i128(val) ((uint64_t)((val) >> 0)) #define zig_bitcast_u128(val) ((zig_u128)(val)) #define zig_bitcast_i128(val) ((zig_i128)(val)) #define zig_cmp_int128(Type) \ - static inline zig_i32 zig_cmp_##Type(zig_##Type lhs, zig_##Type rhs) { \ + static inline int32_t zig_cmp_##Type(zig_##Type lhs, zig_##Type rhs) { \ return (lhs > rhs) - (lhs < rhs); \ } #define zig_bit_int128(Type, operation, operator) \ @@ -1243,32 +1241,32 @@ typedef signed __int128 zig_i128; #else /* zig_has_int128 */ -#if __LITTLE_ENDIAN__ || _MSC_VER -typedef struct { zig_align(16) zig_u64 lo; zig_u64 hi; } zig_u128; -typedef struct { zig_align(16) zig_u64 lo; zig_i64 hi; } zig_i128; +#if zig_little_endian +typedef struct { zig_align(16) uint64_t lo; uint64_t hi; } zig_u128; +typedef struct { zig_align(16) uint64_t lo; int64_t hi; } zig_i128; #else -typedef struct { zig_align(16) zig_u64 hi; zig_u64 lo; } zig_u128; -typedef struct { zig_align(16) zig_i64 hi; zig_u64 lo; } zig_i128; +typedef struct { zig_align(16) uint64_t hi; uint64_t lo; } zig_u128; +typedef struct { zig_align(16) int64_t hi; uint64_t lo; } zig_i128; #endif -#define zig_as_u128(hi, lo) ((zig_u128){ .h##i = (hi), .l##o = (lo) }) -#define zig_as_i128(hi, lo) ((zig_i128){ .h##i = (hi), .l##o = (lo) }) +#define zig_make_u128(hi, lo) ((zig_u128){ .h##i = (hi), .l##o = (lo) }) +#define zig_make_i128(hi, lo) ((zig_i128){ .h##i = (hi), .l##o = (lo) }) -#if _MSC_VER -#define zig_as_constant_u128(hi, lo) { .h##i = (hi), .l##o = (lo) } -#define zig_as_constant_i128(hi, lo) { .h##i = (hi), .l##o = (lo) } -#else -#define zig_as_constant_u128(hi, lo) zig_as_u128(hi, lo) -#define zig_as_constant_i128(hi, lo) zig_as_i128(hi, lo) +#if _MSC_VER /* MSVC doesn't allow struct literals in constant expressions */ +#define zig_init_u128(hi, lo) { .h##i = (hi), .l##o = (lo) } +#define zig_init_i128(hi, lo) { .h##i = (hi), .l##o = (lo) } +#else /* But non-MSVC doesn't like the unprotected commas */ +#define zig_init_u128(hi, lo) zig_make_u128(hi, lo) +#define zig_init_i128(hi, lo) zig_make_i128(hi, lo) #endif #define zig_hi_u128(val) ((val).hi) #define zig_lo_u128(val) ((val).lo) #define zig_hi_i128(val) ((val).hi) #define zig_lo_i128(val) ((val).lo) -#define zig_bitcast_u128(val) zig_as_u128((zig_u64)(val).hi, (val).lo) -#define zig_bitcast_i128(val) zig_as_i128((zig_i64)(val).hi, (val).lo) +#define zig_bitcast_u128(val) zig_make_u128((uint64_t)(val).hi, (val).lo) +#define zig_bitcast_i128(val) zig_make_i128(( int64_t)(val).hi, (val).lo) #define zig_cmp_int128(Type) \ - static inline zig_i32 zig_cmp_##Type(zig_##Type lhs, zig_##Type rhs) { \ + static inline int32_t zig_cmp_##Type(zig_##Type lhs, zig_##Type rhs) { \ return (lhs.hi == rhs.hi) \ ? (lhs.lo > rhs.lo) - (lhs.lo < rhs.lo) \ : (lhs.hi > rhs.hi) - (lhs.hi < rhs.hi); \ @@ -1280,10 +1278,10 @@ typedef struct { zig_align(16) zig_i64 hi; zig_u64 lo; } zig_i128; #endif /* zig_has_int128 */ -#define zig_minInt_u128 zig_as_u128(zig_minInt_u64, zig_minInt_u64) -#define zig_maxInt_u128 zig_as_u128(zig_maxInt_u64, zig_maxInt_u64) -#define zig_minInt_i128 zig_as_i128(zig_minInt_i64, zig_minInt_u64) -#define zig_maxInt_i128 zig_as_i128(zig_maxInt_i64, zig_maxInt_u64) +#define zig_minInt_u128 zig_make_u128(zig_minInt_u64, zig_minInt_u64) +#define zig_maxInt_u128 zig_make_u128(zig_maxInt_u64, zig_maxInt_u64) +#define zig_minInt_i128 zig_make_i128(zig_minInt_i64, zig_minInt_u64) +#define zig_maxInt_i128 zig_make_i128(zig_maxInt_i64, zig_maxInt_u64) zig_cmp_int128(u128) zig_cmp_int128(i128) @@ -1297,28 +1295,33 @@ zig_bit_int128(i128, or, |) zig_bit_int128(u128, xor, ^) zig_bit_int128(i128, xor, ^) -static inline zig_u128 zig_shr_u128(zig_u128 lhs, zig_u8 rhs); +static inline zig_u128 zig_shr_u128(zig_u128 lhs, uint8_t rhs); #if zig_has_int128 -static inline zig_u128 zig_not_u128(zig_u128 val, zig_u8 bits) { - return val ^ zig_maxInt(u128, bits); +static inline zig_u128 zig_not_u128(zig_u128 val, uint8_t bits) { + return val ^ zig_maxInt_u(128, bits); } -static inline zig_i128 zig_not_i128(zig_i128 val, zig_u8 bits) { +static inline zig_i128 zig_not_i128(zig_i128 val, uint8_t bits) { (void)bits; return ~val; } -static inline zig_u128 zig_shr_u128(zig_u128 lhs, zig_u8 rhs) { +static inline zig_u128 zig_shr_u128(zig_u128 lhs, uint8_t rhs) { return lhs >> rhs; } -static inline zig_u128 zig_shl_u128(zig_u128 lhs, zig_u8 rhs) { +static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) { return lhs << rhs; } -static inline zig_i128 zig_shl_i128(zig_i128 lhs, zig_u8 rhs) { +static inline zig_i128 zig_shr_i128(zig_i128 lhs, uint8_t rhs) { + zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0); + return ((lhs ^ sign_mask) >> rhs) ^ sign_mask; +} + +static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) { return lhs << rhs; } @@ -1363,40 +1366,46 @@ static inline zig_i128 zig_rem_i128(zig_i128 lhs, zig_i128 rhs) { } static inline zig_i128 zig_div_floor_i128(zig_i128 lhs, zig_i128 rhs) { - return zig_div_trunc_i128(lhs, rhs) - (((lhs ^ rhs) & zig_rem_i128(lhs, rhs)) < zig_as_i128(0, 0)); + return zig_div_trunc_i128(lhs, rhs) - (((lhs ^ rhs) & zig_rem_i128(lhs, rhs)) < zig_make_i128(0, 0)); } static inline zig_i128 zig_mod_i128(zig_i128 lhs, zig_i128 rhs) { zig_i128 rem = zig_rem_i128(lhs, rhs); - return rem + (((lhs ^ rhs) & rem) < zig_as_i128(0, 0) ? rhs : zig_as_i128(0, 0)); + return rem + (((lhs ^ rhs) & rem) < zig_make_i128(0, 0) ? rhs : zig_make_i128(0, 0)); } #else /* zig_has_int128 */ -static inline zig_u128 zig_not_u128(zig_u128 val, zig_u8 bits) { - return (zig_u128){ .hi = zig_not_u64(val.hi, bits - zig_as_u8(64)), .lo = zig_not_u64(val.lo, zig_as_u8(64)) }; +static inline zig_u128 zig_not_u128(zig_u128 val, uint8_t bits) { + return (zig_u128){ .hi = zig_not_u64(val.hi, bits - UINT8_C(64)), .lo = zig_not_u64(val.lo, UINT8_C(64)) }; } -static inline zig_i128 zig_not_i128(zig_i128 val, zig_u8 bits) { - return (zig_i128){ .hi = zig_not_i64(val.hi, bits - zig_as_u8(64)), .lo = zig_not_u64(val.lo, zig_as_u8(64)) }; +static inline zig_i128 zig_not_i128(zig_i128 val, uint8_t bits) { + return (zig_i128){ .hi = zig_not_i64(val.hi, bits - UINT8_C(64)), .lo = zig_not_u64(val.lo, UINT8_C(64)) }; } -static inline zig_u128 zig_shr_u128(zig_u128 lhs, zig_u8 rhs) { - if (rhs == zig_as_u8(0)) return lhs; - if (rhs >= zig_as_u8(64)) return (zig_u128){ .hi = zig_minInt_u64, .lo = lhs.hi >> (rhs - zig_as_u8(64)) }; - return (zig_u128){ .hi = lhs.hi >> rhs, .lo = lhs.hi << (zig_as_u8(64) - rhs) | lhs.lo >> rhs }; +static inline zig_u128 zig_shr_u128(zig_u128 lhs, uint8_t rhs) { + if (rhs == UINT8_C(0)) return lhs; + if (rhs >= UINT8_C(64)) return (zig_u128){ .hi = zig_minInt_u64, .lo = lhs.hi >> (rhs - UINT8_C(64)) }; + return (zig_u128){ .hi = lhs.hi >> rhs, .lo = lhs.hi << (UINT8_C(64) - rhs) | lhs.lo >> rhs }; } -static inline zig_u128 zig_shl_u128(zig_u128 lhs, zig_u8 rhs) { - if (rhs == zig_as_u8(0)) return lhs; - if (rhs >= zig_as_u8(64)) return (zig_u128){ .hi = lhs.lo << (rhs - zig_as_u8(64)), .lo = zig_minInt_u64 }; - return (zig_u128){ .hi = lhs.hi << rhs | lhs.lo >> (zig_as_u8(64) - rhs), .lo = lhs.lo << rhs }; +static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) { + if (rhs == UINT8_C(0)) return lhs; + if (rhs >= UINT8_C(64)) return (zig_u128){ .hi = lhs.lo << (rhs - UINT8_C(64)), .lo = zig_minInt_u64 }; + return (zig_u128){ .hi = lhs.hi << rhs | lhs.lo >> (UINT8_C(64) - rhs), .lo = lhs.lo << rhs }; } -static inline zig_i128 zig_shl_i128(zig_i128 lhs, zig_u8 rhs) { - if (rhs == zig_as_u8(0)) return lhs; - if (rhs >= zig_as_u8(64)) return (zig_i128){ .hi = lhs.lo << (rhs - zig_as_u8(64)), .lo = zig_minInt_u64 }; - return (zig_i128){ .hi = lhs.hi << rhs | lhs.lo >> (zig_as_u8(64) - rhs), .lo = lhs.lo << rhs }; +static inline zig_i128 zig_shr_i128(zig_i128 lhs, uint8_t rhs) { + if (rhs == UINT8_C(0)) return lhs; + if (rhs >= UINT8_C(64)) return (zig_i128){ .hi = zig_shr_i64(lhs.hi, 63), .lo = zig_shr_i64(lhs.hi, (rhs - UINT8_C(64))) }; + return (zig_i128){ .hi = zig_shr_i64(lhs.hi, rhs), .lo = lhs.lo >> rhs | (uint64_t)lhs.hi << (UINT8_C(64) - rhs) }; +} + +static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) { + if (rhs == UINT8_C(0)) return lhs; + if (rhs >= UINT8_C(64)) return (zig_i128){ .hi = lhs.lo << (rhs - UINT8_C(64)), .lo = zig_minInt_u64 }; + return (zig_i128){ .hi = lhs.hi << rhs | lhs.lo >> (UINT8_C(64) - rhs), .lo = lhs.lo << rhs }; } static inline zig_u128 zig_add_u128(zig_u128 lhs, zig_u128 rhs) { @@ -1424,14 +1433,14 @@ static inline zig_i128 zig_sub_i128(zig_i128 lhs, zig_i128 rhs) { } zig_extern zig_i128 __multi3(zig_i128 lhs, zig_i128 rhs); -static zig_u128 zig_mul_u128(zig_u128 lhs, zig_u128 rhs) { - return zig_bitcast_u128(__multi3(zig_bitcast_i128(lhs), zig_bitcast_i128(rhs))); -} - static zig_i128 zig_mul_i128(zig_i128 lhs, zig_i128 rhs) { return __multi3(lhs, rhs); } +static zig_u128 zig_mul_u128(zig_u128 lhs, zig_u128 rhs) { + return zig_bitcast_u128(zig_mul_i128(zig_bitcast_i128(lhs), zig_bitcast_i128(rhs))); +} + zig_extern zig_u128 __udivti3(zig_u128 lhs, zig_u128 rhs); static zig_u128 zig_div_trunc_u128(zig_u128 lhs, zig_u128 rhs) { return __udivti3(lhs, rhs); @@ -1454,11 +1463,11 @@ static zig_i128 zig_rem_i128(zig_i128 lhs, zig_i128 rhs) { static inline zig_i128 zig_mod_i128(zig_i128 lhs, zig_i128 rhs) { zig_i128 rem = zig_rem_i128(lhs, rhs); - return zig_add_i128(rem, (((lhs.hi ^ rhs.hi) & rem.hi) < zig_as_i64(0) ? rhs : zig_as_i128(0, 0))); + return zig_add_i128(rem, ((lhs.hi ^ rhs.hi) & rem.hi) < INT64_C(0) ? rhs : zig_make_i128(0, 0)); } static inline zig_i128 zig_div_floor_i128(zig_i128 lhs, zig_i128 rhs) { - return zig_sub_i128(zig_div_trunc_i128(lhs, rhs), zig_as_i128(0, zig_cmp_i128(zig_and_i128(zig_xor_i128(lhs, rhs), zig_rem_i128(lhs, rhs)), zig_as_i128(0, 0)) < zig_as_i32(0))); + return zig_sub_i128(zig_div_trunc_i128(lhs, rhs), zig_make_i128(0, zig_cmp_i128(zig_and_i128(zig_xor_i128(lhs, rhs), zig_rem_i128(lhs, rhs)), zig_make_i128(0, 0)) < INT32_C(0))); } #endif /* zig_has_int128 */ @@ -1471,326 +1480,1265 @@ static inline zig_u128 zig_nand_u128(zig_u128 lhs, zig_u128 rhs) { } static inline zig_u128 zig_min_u128(zig_u128 lhs, zig_u128 rhs) { - return zig_cmp_u128(lhs, rhs) < zig_as_i32(0) ? lhs : rhs; + return zig_cmp_u128(lhs, rhs) < INT32_C(0) ? lhs : rhs; } static inline zig_i128 zig_min_i128(zig_i128 lhs, zig_i128 rhs) { - return zig_cmp_i128(lhs, rhs) < zig_as_i32(0) ? lhs : rhs; + return zig_cmp_i128(lhs, rhs) < INT32_C(0) ? lhs : rhs; } static inline zig_u128 zig_max_u128(zig_u128 lhs, zig_u128 rhs) { - return zig_cmp_u128(lhs, rhs) > zig_as_i32(0) ? lhs : rhs; + return zig_cmp_u128(lhs, rhs) > INT32_C(0) ? lhs : rhs; } static inline zig_i128 zig_max_i128(zig_i128 lhs, zig_i128 rhs) { - return zig_cmp_i128(lhs, rhs) > zig_as_i32(0) ? lhs : rhs; + return zig_cmp_i128(lhs, rhs) > INT32_C(0) ? lhs : rhs; } -static inline zig_i128 zig_shr_i128(zig_i128 lhs, zig_u8 rhs) { - zig_i128 sign_mask = zig_cmp_i128(lhs, zig_as_i128(0, 0)) < zig_as_i32(0) ? zig_sub_i128(zig_as_i128(0, 0), zig_as_i128(0, 1)) : zig_as_i128(0, 0); - return zig_xor_i128(zig_bitcast_i128(zig_shr_u128(zig_bitcast_u128(zig_xor_i128(lhs, sign_mask)), rhs)), sign_mask); +static inline zig_u128 zig_wrap_u128(zig_u128 val, uint8_t bits) { + return zig_and_u128(val, zig_maxInt_u(128, bits)); } -static inline zig_u128 zig_wrap_u128(zig_u128 val, zig_u8 bits) { - return zig_and_u128(val, zig_maxInt(u128, bits)); +static inline zig_i128 zig_wrap_i128(zig_i128 val, uint8_t bits) { + if (bits > UINT8_C(64)) return zig_make_i128(zig_wrap_i64(zig_hi_i128(val), bits - UINT8_C(64)), zig_lo_i128(val)); + int64_t lo = zig_wrap_i64((int64_t)zig_lo_i128(val), bits); + return zig_make_i128(zig_shr_i64(lo, 63), (uint64_t)lo); } -static inline zig_i128 zig_wrap_i128(zig_i128 val, zig_u8 bits) { - return zig_as_i128(zig_wrap_i64(zig_hi_i128(val), bits - zig_as_u8(64)), zig_lo_i128(val)); -} - -static inline zig_u128 zig_shlw_u128(zig_u128 lhs, zig_u8 rhs, zig_u8 bits) { +static inline zig_u128 zig_shlw_u128(zig_u128 lhs, uint8_t rhs, uint8_t bits) { return zig_wrap_u128(zig_shl_u128(lhs, rhs), bits); } -static inline zig_i128 zig_shlw_i128(zig_i128 lhs, zig_u8 rhs, zig_u8 bits) { +static inline zig_i128 zig_shlw_i128(zig_i128 lhs, uint8_t rhs, uint8_t bits) { return zig_wrap_i128(zig_bitcast_i128(zig_shl_u128(zig_bitcast_u128(lhs), rhs)), bits); } -static inline zig_u128 zig_addw_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_addw_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { return zig_wrap_u128(zig_add_u128(lhs, rhs), bits); } -static inline zig_i128 zig_addw_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_addw_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { return zig_wrap_i128(zig_bitcast_i128(zig_add_u128(zig_bitcast_u128(lhs), zig_bitcast_u128(rhs))), bits); } -static inline zig_u128 zig_subw_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_subw_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { return zig_wrap_u128(zig_sub_u128(lhs, rhs), bits); } -static inline zig_i128 zig_subw_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_subw_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { return zig_wrap_i128(zig_bitcast_i128(zig_sub_u128(zig_bitcast_u128(lhs), zig_bitcast_u128(rhs))), bits); } -static inline zig_u128 zig_mulw_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_mulw_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { return zig_wrap_u128(zig_mul_u128(lhs, rhs), bits); } -static inline zig_i128 zig_mulw_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_mulw_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { return zig_wrap_i128(zig_bitcast_i128(zig_mul_u128(zig_bitcast_u128(lhs), zig_bitcast_u128(rhs))), bits); } #if zig_has_int128 -static inline bool zig_addo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline bool zig_addo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) zig_u128 full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); *res = zig_wrap_u128(full_res, bits); - return overflow || full_res < zig_minInt(u128, bits) || full_res > zig_maxInt(u128, bits); + return overflow || full_res < zig_minInt_u(128, bits) || full_res > zig_maxInt_u(128, bits); #else *res = zig_addw_u128(lhs, rhs, bits); return *res < lhs; #endif } -zig_extern zig_i128 __addoti4(zig_i128 lhs, zig_i128 rhs, zig_c_int *overflow); -static inline bool zig_addo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +zig_extern zig_i128 __addoti4(zig_i128 lhs, zig_i128 rhs, int *overflow); +static inline bool zig_addo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, uint8_t bits) { #if zig_has_builtin(add_overflow) zig_i128 full_res; bool overflow = __builtin_add_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; + int overflow_int; zig_i128 full_res = __addoti4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i128(full_res, bits); - return overflow || full_res < zig_minInt(i128, bits) || full_res > zig_maxInt(i128, bits); + return overflow || full_res < zig_minInt_i(128, bits) || full_res > zig_maxInt_i(128, bits); } -static inline bool zig_subo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline bool zig_subo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) zig_u128 full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); *res = zig_wrap_u128(full_res, bits); - return overflow || full_res < zig_minInt(u128, bits) || full_res > zig_maxInt(u128, bits); + return overflow || full_res < zig_minInt_u(128, bits) || full_res > zig_maxInt_u(128, bits); #else *res = zig_subw_u128(lhs, rhs, bits); return *res > lhs; #endif } -zig_extern zig_i128 __suboti4(zig_i128 lhs, zig_i128 rhs, zig_c_int *overflow); -static inline bool zig_subo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +zig_extern zig_i128 __suboti4(zig_i128 lhs, zig_i128 rhs, int *overflow); +static inline bool zig_subo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, uint8_t bits) { #if zig_has_builtin(sub_overflow) zig_i128 full_res; bool overflow = __builtin_sub_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; + int overflow_int; zig_i128 full_res = __suboti4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i128(full_res, bits); - return overflow || full_res < zig_minInt(i128, bits) || full_res > zig_maxInt(i128, bits); + return overflow || full_res < zig_minInt_i(128, bits) || full_res > zig_maxInt_i(128, bits); } -static inline bool zig_mulo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline bool zig_mulo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) zig_u128 full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); *res = zig_wrap_u128(full_res, bits); - return overflow || full_res < zig_minInt(u128, bits) || full_res > zig_maxInt(u128, bits); + return overflow || full_res < zig_minInt_u(128, bits) || full_res > zig_maxInt_u(128, bits); #else *res = zig_mulw_u128(lhs, rhs, bits); - return rhs != zig_as_u128(0, 0) && lhs > zig_maxInt(u128, bits) / rhs; + return rhs != zig_make_u128(0, 0) && lhs > zig_maxInt_u(128, bits) / rhs; #endif } -zig_extern zig_i128 __muloti4(zig_i128 lhs, zig_i128 rhs, zig_c_int *overflow); -static inline bool zig_mulo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +zig_extern zig_i128 __muloti4(zig_i128 lhs, zig_i128 rhs, int *overflow); +static inline bool zig_mulo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, uint8_t bits) { #if zig_has_builtin(mul_overflow) zig_i128 full_res; bool overflow = __builtin_mul_overflow(lhs, rhs, &full_res); #else - zig_c_int overflow_int; + int overflow_int; zig_i128 full_res = __muloti4(lhs, rhs, &overflow_int); bool overflow = overflow_int != 0; #endif *res = zig_wrap_i128(full_res, bits); - return overflow || full_res < zig_minInt(i128, bits) || full_res > zig_maxInt(i128, bits); + return overflow || full_res < zig_minInt_i(128, bits) || full_res > zig_maxInt_i(128, bits); } #else /* zig_has_int128 */ -static inline bool zig_overflow_u128(bool overflow, zig_u128 full_res, zig_u8 bits) { - return overflow || - zig_cmp_u128(full_res, zig_minInt(u128, bits)) < zig_as_i32(0) || - zig_cmp_u128(full_res, zig_maxInt(u128, bits)) > zig_as_i32(0); +static inline bool zig_addo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, uint8_t bits) { + uint64_t hi; + bool overflow = zig_addo_u64(&hi, lhs.hi, rhs.hi, bits - 64); + return overflow ^ zig_addo_u64(&res->hi, hi, zig_addo_u64(&res->lo, lhs.lo, rhs.lo, 64), bits - 64); } -static inline bool zig_overflow_i128(bool overflow, zig_i128 full_res, zig_u8 bits) { - return overflow || - zig_cmp_i128(full_res, zig_minInt(i128, bits)) < zig_as_i32(0) || - zig_cmp_i128(full_res, zig_maxInt(i128, bits)) > zig_as_i32(0); +static inline bool zig_addo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, uint8_t bits) { + int64_t hi; + bool overflow = zig_addo_i64(&hi, lhs.hi, rhs.hi, bits - 64); + return overflow ^ zig_addo_i64(&res->hi, hi, zig_addo_u64(&res->lo, lhs.lo, rhs.lo, 64), bits - 64); } -static inline bool zig_addo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { - zig_u128 full_res; - bool overflow = - zig_addo_u64(&full_res.hi, lhs.hi, rhs.hi, 64) | - zig_addo_u64(&full_res.hi, full_res.hi, zig_addo_u64(&full_res.lo, lhs.lo, rhs.lo, 64), 64); - *res = zig_wrap_u128(full_res, bits); - return zig_overflow_u128(overflow, full_res, bits); +static inline bool zig_subo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, uint8_t bits) { + uint64_t hi; + bool overflow = zig_subo_u64(&hi, lhs.hi, rhs.hi, bits - 64); + return overflow ^ zig_subo_u64(&res->hi, hi, zig_subo_u64(&res->lo, lhs.lo, rhs.lo, 64), bits - 64); } -zig_extern zig_i128 __addoti4(zig_i128 lhs, zig_i128 rhs, zig_c_int *overflow); -static inline bool zig_addo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { - zig_c_int overflow_int; - zig_i128 full_res = __addoti4(lhs, rhs, &overflow_int); - *res = zig_wrap_i128(full_res, bits); - return zig_overflow_i128(overflow_int, full_res, bits); +static inline bool zig_subo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, uint8_t bits) { + int64_t hi; + bool overflow = zig_subo_i64(&hi, lhs.hi, rhs.hi, bits - 64); + return overflow ^ zig_subo_i64(&res->hi, hi, zig_subo_u64(&res->lo, lhs.lo, rhs.lo, 64), bits - 64); } -static inline bool zig_subo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { - zig_u128 full_res; - bool overflow = - zig_subo_u64(&full_res.hi, lhs.hi, rhs.hi, 64) | - zig_subo_u64(&full_res.hi, full_res.hi, zig_subo_u64(&full_res.lo, lhs.lo, rhs.lo, 64), 64); - *res = zig_wrap_u128(full_res, bits); - return zig_overflow_u128(overflow, full_res, bits); -} - -zig_extern zig_i128 __suboti4(zig_i128 lhs, zig_i128 rhs, zig_c_int *overflow); -static inline bool zig_subo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { - zig_c_int overflow_int; - zig_i128 full_res = __suboti4(lhs, rhs, &overflow_int); - *res = zig_wrap_i128(full_res, bits); - return zig_overflow_i128(overflow_int, full_res, bits); -} - -static inline bool zig_mulo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline bool zig_mulo_u128(zig_u128 *res, zig_u128 lhs, zig_u128 rhs, uint8_t bits) { *res = zig_mulw_u128(lhs, rhs, bits); - return zig_cmp_u128(*res, zig_as_u128(0, 0)) != zig_as_i32(0) && - zig_cmp_u128(lhs, zig_div_trunc_u128(zig_maxInt(u128, bits), rhs)) > zig_as_i32(0); + return zig_cmp_u128(*res, zig_make_u128(0, 0)) != INT32_C(0) && + zig_cmp_u128(lhs, zig_div_trunc_u128(zig_maxInt_u(128, bits), rhs)) > INT32_C(0); } -zig_extern zig_i128 __muloti4(zig_i128 lhs, zig_i128 rhs, zig_c_int *overflow); -static inline bool zig_mulo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { - zig_c_int overflow_int; +zig_extern zig_i128 __muloti4(zig_i128 lhs, zig_i128 rhs, int *overflow); +static inline bool zig_mulo_i128(zig_i128 *res, zig_i128 lhs, zig_i128 rhs, uint8_t bits) { + int overflow_int; zig_i128 full_res = __muloti4(lhs, rhs, &overflow_int); + bool overflow = overflow_int != 0 || + zig_cmp_i128(full_res, zig_minInt_i(128, bits)) < INT32_C(0) || + zig_cmp_i128(full_res, zig_maxInt_i(128, bits)) > INT32_C(0); *res = zig_wrap_i128(full_res, bits); - return zig_overflow_i128(overflow_int, full_res, bits); + return overflow; } #endif /* zig_has_int128 */ -static inline bool zig_shlo_u128(zig_u128 *res, zig_u128 lhs, zig_u8 rhs, zig_u8 bits) { +static inline bool zig_shlo_u128(zig_u128 *res, zig_u128 lhs, uint8_t rhs, uint8_t bits) { *res = zig_shlw_u128(lhs, rhs, bits); - return zig_cmp_u128(lhs, zig_shr_u128(zig_maxInt(u128, bits), rhs)) > zig_as_i32(0); + return zig_cmp_u128(lhs, zig_shr_u128(zig_maxInt_u(128, bits), rhs)) > INT32_C(0); } -static inline bool zig_shlo_i128(zig_i128 *res, zig_i128 lhs, zig_u8 rhs, zig_u8 bits) { +static inline bool zig_shlo_i128(zig_i128 *res, zig_i128 lhs, uint8_t rhs, uint8_t bits) { *res = zig_shlw_i128(lhs, rhs, bits); - zig_i128 mask = zig_bitcast_i128(zig_shl_u128(zig_maxInt_u128, bits - rhs - zig_as_u8(1))); - return zig_cmp_i128(zig_and_i128(lhs, mask), zig_as_i128(0, 0)) != zig_as_i32(0) && - zig_cmp_i128(zig_and_i128(lhs, mask), mask) != zig_as_i32(0); + zig_i128 mask = zig_bitcast_i128(zig_shl_u128(zig_maxInt_u128, bits - rhs - UINT8_C(1))); + return zig_cmp_i128(zig_and_i128(lhs, mask), zig_make_i128(0, 0)) != INT32_C(0) && + zig_cmp_i128(zig_and_i128(lhs, mask), mask) != INT32_C(0); } -static inline zig_u128 zig_shls_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_shls_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { zig_u128 res; - if (zig_cmp_u128(rhs, zig_as_u128(0, bits)) >= zig_as_i32(0)) - return zig_cmp_u128(lhs, zig_as_u128(0, 0)) != zig_as_i32(0) ? zig_maxInt(u128, bits) : lhs; - -#if zig_has_int128 - return zig_shlo_u128(&res, lhs, (zig_u8)rhs, bits) ? zig_maxInt(u128, bits) : res; -#else - return zig_shlo_u128(&res, lhs, (zig_u8)rhs.lo, bits) ? zig_maxInt(u128, bits) : res; -#endif + if (zig_cmp_u128(rhs, zig_make_u128(0, bits)) >= INT32_C(0)) + return zig_cmp_u128(lhs, zig_make_u128(0, 0)) != INT32_C(0) ? zig_maxInt_u(128, bits) : lhs; + return zig_shlo_u128(&res, lhs, (uint8_t)zig_lo_u128(rhs), bits) ? zig_maxInt_u(128, bits) : res; } -static inline zig_i128 zig_shls_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_shls_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { zig_i128 res; - if (zig_cmp_u128(zig_bitcast_u128(rhs), zig_as_u128(0, bits)) < zig_as_i32(0) && !zig_shlo_i128(&res, lhs, zig_lo_i128(rhs), bits)) return res; - return zig_cmp_i128(lhs, zig_as_i128(0, 0)) < zig_as_i32(0) ? zig_minInt(i128, bits) : zig_maxInt(i128, bits); + if (zig_cmp_u128(zig_bitcast_u128(rhs), zig_make_u128(0, bits)) < INT32_C(0) && !zig_shlo_i128(&res, lhs, (uint8_t)zig_lo_i128(rhs), bits)) return res; + return zig_cmp_i128(lhs, zig_make_i128(0, 0)) < INT32_C(0) ? zig_minInt_i(128, bits) : zig_maxInt_i(128, bits); } -static inline zig_u128 zig_adds_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_adds_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { zig_u128 res; - return zig_addo_u128(&res, lhs, rhs, bits) ? zig_maxInt(u128, bits) : res; + return zig_addo_u128(&res, lhs, rhs, bits) ? zig_maxInt_u(128, bits) : res; } -static inline zig_i128 zig_adds_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_adds_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { zig_i128 res; if (!zig_addo_i128(&res, lhs, rhs, bits)) return res; - return zig_cmp_i128(res, zig_as_i128(0, 0)) >= zig_as_i32(0) ? zig_minInt(i128, bits) : zig_maxInt(i128, bits); + return zig_cmp_i128(res, zig_make_i128(0, 0)) >= INT32_C(0) ? zig_minInt_i(128, bits) : zig_maxInt_i(128, bits); } -static inline zig_u128 zig_subs_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_subs_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { zig_u128 res; - return zig_subo_u128(&res, lhs, rhs, bits) ? zig_minInt(u128, bits) : res; + return zig_subo_u128(&res, lhs, rhs, bits) ? zig_minInt_u(128, bits) : res; } -static inline zig_i128 zig_subs_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_subs_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { zig_i128 res; if (!zig_subo_i128(&res, lhs, rhs, bits)) return res; - return zig_cmp_i128(res, zig_as_i128(0, 0)) >= zig_as_i32(0) ? zig_minInt(i128, bits) : zig_maxInt(i128, bits); + return zig_cmp_i128(res, zig_make_i128(0, 0)) >= INT32_C(0) ? zig_minInt_i(128, bits) : zig_maxInt_i(128, bits); } -static inline zig_u128 zig_muls_u128(zig_u128 lhs, zig_u128 rhs, zig_u8 bits) { +static inline zig_u128 zig_muls_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) { zig_u128 res; - return zig_mulo_u128(&res, lhs, rhs, bits) ? zig_maxInt(u128, bits) : res; + return zig_mulo_u128(&res, lhs, rhs, bits) ? zig_maxInt_u(128, bits) : res; } -static inline zig_i128 zig_muls_i128(zig_i128 lhs, zig_i128 rhs, zig_u8 bits) { +static inline zig_i128 zig_muls_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) { zig_i128 res; if (!zig_mulo_i128(&res, lhs, rhs, bits)) return res; - return zig_cmp_i128(zig_xor_i128(lhs, rhs), zig_as_i128(0, 0)) < zig_as_i32(0) ? zig_minInt(i128, bits) : zig_maxInt(i128, bits); + return zig_cmp_i128(zig_xor_i128(lhs, rhs), zig_make_i128(0, 0)) < INT32_C(0) ? zig_minInt_i(128, bits) : zig_maxInt_i(128, bits); } -static inline zig_u8 zig_clz_u128(zig_u128 val, zig_u8 bits) { - if (bits <= zig_as_u8(64)) return zig_clz_u64(zig_lo_u128(val), bits); - if (zig_hi_u128(val) != 0) return zig_clz_u64(zig_hi_u128(val), bits - zig_as_u8(64)); - return zig_clz_u64(zig_lo_u128(val), zig_as_u8(64)) + (bits - zig_as_u8(64)); +static inline uint8_t zig_clz_u128(zig_u128 val, uint8_t bits) { + if (bits <= UINT8_C(64)) return zig_clz_u64(zig_lo_u128(val), bits); + if (zig_hi_u128(val) != 0) return zig_clz_u64(zig_hi_u128(val), bits - UINT8_C(64)); + return zig_clz_u64(zig_lo_u128(val), UINT8_C(64)) + (bits - UINT8_C(64)); } -static inline zig_u8 zig_clz_i128(zig_i128 val, zig_u8 bits) { +static inline uint8_t zig_clz_i128(zig_i128 val, uint8_t bits) { return zig_clz_u128(zig_bitcast_u128(val), bits); } -static inline zig_u8 zig_ctz_u128(zig_u128 val, zig_u8 bits) { - if (zig_lo_u128(val) != 0) return zig_ctz_u64(zig_lo_u128(val), zig_as_u8(64)); - return zig_ctz_u64(zig_hi_u128(val), bits - zig_as_u8(64)) + zig_as_u8(64); +static inline uint8_t zig_ctz_u128(zig_u128 val, uint8_t bits) { + if (zig_lo_u128(val) != 0) return zig_ctz_u64(zig_lo_u128(val), UINT8_C(64)); + return zig_ctz_u64(zig_hi_u128(val), bits - UINT8_C(64)) + UINT8_C(64); } -static inline zig_u8 zig_ctz_i128(zig_i128 val, zig_u8 bits) { +static inline uint8_t zig_ctz_i128(zig_i128 val, uint8_t bits) { return zig_ctz_u128(zig_bitcast_u128(val), bits); } -static inline zig_u8 zig_popcount_u128(zig_u128 val, zig_u8 bits) { - return zig_popcount_u64(zig_hi_u128(val), bits - zig_as_u8(64)) + - zig_popcount_u64(zig_lo_u128(val), zig_as_u8(64)); +static inline uint8_t zig_popcount_u128(zig_u128 val, uint8_t bits) { + return zig_popcount_u64(zig_hi_u128(val), bits - UINT8_C(64)) + + zig_popcount_u64(zig_lo_u128(val), UINT8_C(64)); } -static inline zig_u8 zig_popcount_i128(zig_i128 val, zig_u8 bits) { +static inline uint8_t zig_popcount_i128(zig_i128 val, uint8_t bits) { return zig_popcount_u128(zig_bitcast_u128(val), bits); } -static inline zig_u128 zig_byte_swap_u128(zig_u128 val, zig_u8 bits) { +static inline zig_u128 zig_byte_swap_u128(zig_u128 val, uint8_t bits) { zig_u128 full_res; #if zig_has_builtin(bswap128) full_res = __builtin_bswap128(val); #else - full_res = zig_as_u128(zig_byte_swap_u64(zig_lo_u128(val), zig_as_u8(64)), - zig_byte_swap_u64(zig_hi_u128(val), zig_as_u8(64))); + full_res = zig_make_u128(zig_byte_swap_u64(zig_lo_u128(val), UINT8_C(64)), + zig_byte_swap_u64(zig_hi_u128(val), UINT8_C(64))); #endif - return zig_shr_u128(full_res, zig_as_u8(128) - bits); + return zig_shr_u128(full_res, UINT8_C(128) - bits); } -static inline zig_i128 zig_byte_swap_i128(zig_i128 val, zig_u8 bits) { +static inline zig_i128 zig_byte_swap_i128(zig_i128 val, uint8_t bits) { return zig_bitcast_i128(zig_byte_swap_u128(zig_bitcast_u128(val), bits)); } -static inline zig_u128 zig_bit_reverse_u128(zig_u128 val, zig_u8 bits) { - return zig_shr_u128(zig_as_u128(zig_bit_reverse_u64(zig_lo_u128(val), zig_as_u8(64)), - zig_bit_reverse_u64(zig_hi_u128(val), zig_as_u8(64))), - zig_as_u8(128) - bits); +static inline zig_u128 zig_bit_reverse_u128(zig_u128 val, uint8_t bits) { + return zig_shr_u128(zig_make_u128(zig_bit_reverse_u64(zig_lo_u128(val), UINT8_C(64)), + zig_bit_reverse_u64(zig_hi_u128(val), UINT8_C(64))), + UINT8_C(128) - bits); } -static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, zig_u8 bits) { +static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, uint8_t bits) { return zig_bitcast_i128(zig_bit_reverse_u128(zig_bitcast_u128(val), bits)); } +/* ========================== Big Integer Support =========================== */ + +static inline uint16_t zig_int_bytes(uint16_t bits) { + uint16_t bytes = (bits + CHAR_BIT - 1) / CHAR_BIT; + uint16_t alignment = ZIG_TARGET_MAX_INT_ALIGNMENT; + while (alignment / 2 >= bytes) alignment /= 2; + return (bytes + alignment - 1) / alignment * alignment; +} + +static inline int32_t zig_cmp_big(const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + const uint8_t *lhs_bytes = lhs; + const uint8_t *rhs_bytes = rhs; + uint16_t byte_offset = 0; + bool do_signed = is_signed; + uint16_t remaining_bytes = zig_int_bytes(bits); + +#if zig_little_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { + int32_t limb_cmp; + +#if zig_little_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + if (do_signed) { + zig_i128 lhs_limb; + zig_i128 rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_cmp = zig_cmp_i128(lhs_limb, rhs_limb); + do_signed = false; + } else { + zig_u128 lhs_limb; + zig_u128 rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_cmp = zig_cmp_u128(lhs_limb, rhs_limb); + } + + if (limb_cmp != 0) return limb_cmp; + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + if (do_signed) { + int64_t lhs_limb; + int64_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint64_t lhs_limb; + uint64_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + if (do_signed) { + int32_t lhs_limb; + int32_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint32_t lhs_limb; + uint32_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + if (do_signed) { + int16_t lhs_limb; + int16_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint16_t lhs_limb; + uint16_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + if (do_signed) { + int8_t lhs_limb; + int8_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + do_signed = false; + } else { + uint8_t lhs_limb; + uint8_t rhs_limb; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + if (lhs_limb != rhs_limb) return (lhs_limb > rhs_limb) - (lhs_limb < rhs_limb); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return 0; +} + +static inline bool zig_addo_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + uint8_t *res_bytes = res; + const uint8_t *lhs_bytes = lhs; + const uint8_t *rhs_bytes = rhs; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t top_bits = remaining_bytes * 8 - bits; + bool overflow = false; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { + uint16_t limb_bits = 128 - (remaining_bytes == 128 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + if (remaining_bytes == 128 / CHAR_BIT && is_signed) { + zig_i128 res_limb; + zig_i128 tmp_limb; + zig_i128 lhs_limb; + zig_i128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i128(&res_limb, tmp_limb, zig_make_i128(INT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + zig_u128 res_limb; + zig_u128 tmp_limb; + zig_u128 lhs_limb; + zig_u128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u128(&res_limb, tmp_limb, zig_make_u128(UINT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { + uint16_t limb_bits = 64 - (remaining_bytes == 64 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + if (remaining_bytes == 64 / CHAR_BIT && is_signed) { + int64_t res_limb; + int64_t tmp_limb; + int64_t lhs_limb; + int64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i64(&res_limb, tmp_limb, overflow ? INT64_C(1) : INT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint64_t res_limb; + uint64_t tmp_limb; + uint64_t lhs_limb; + uint64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u64(&res_limb, tmp_limb, overflow ? UINT64_C(1) : UINT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { + uint16_t limb_bits = 32 - (remaining_bytes == 32 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + if (remaining_bytes == 32 / CHAR_BIT && is_signed) { + int32_t res_limb; + int32_t tmp_limb; + int32_t lhs_limb; + int32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i32(&res_limb, tmp_limb, overflow ? INT32_C(1) : INT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint32_t res_limb; + uint32_t tmp_limb; + uint32_t lhs_limb; + uint32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u32(&res_limb, tmp_limb, overflow ? UINT32_C(1) : UINT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { + uint16_t limb_bits = 16 - (remaining_bytes == 16 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + if (remaining_bytes == 16 / CHAR_BIT && is_signed) { + int16_t res_limb; + int16_t tmp_limb; + int16_t lhs_limb; + int16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i16(&res_limb, tmp_limb, overflow ? INT16_C(1) : INT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint16_t res_limb; + uint16_t tmp_limb; + uint16_t lhs_limb; + uint16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u16(&res_limb, tmp_limb, overflow ? UINT16_C(1) : UINT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { + uint16_t limb_bits = 8 - (remaining_bytes == 8 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + if (remaining_bytes == 8 / CHAR_BIT && is_signed) { + int8_t res_limb; + int8_t tmp_limb; + int8_t lhs_limb; + int8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_i8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_i8(&res_limb, tmp_limb, overflow ? INT8_C(1) : INT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint8_t res_limb; + uint8_t tmp_limb; + uint8_t lhs_limb; + uint8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_addo_u8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_addo_u8(&res_limb, tmp_limb, overflow ? UINT8_C(1) : UINT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return overflow; +} + +static inline bool zig_subo_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + uint8_t *res_bytes = res; + const uint8_t *lhs_bytes = lhs; + const uint8_t *rhs_bytes = rhs; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t top_bits = remaining_bytes * 8 - bits; + bool overflow = false; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { + uint16_t limb_bits = 128 - (remaining_bytes == 128 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + if (remaining_bytes == 128 / CHAR_BIT && is_signed) { + zig_i128 res_limb; + zig_i128 tmp_limb; + zig_i128 lhs_limb; + zig_i128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i128(&res_limb, tmp_limb, zig_make_i128(INT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + zig_u128 res_limb; + zig_u128 tmp_limb; + zig_u128 lhs_limb; + zig_u128 rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u128(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u128(&res_limb, tmp_limb, zig_make_u128(UINT64_C(0), overflow ? UINT64_C(1) : UINT64_C(0)), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { + uint16_t limb_bits = 64 - (remaining_bytes == 64 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + if (remaining_bytes == 64 / CHAR_BIT && is_signed) { + int64_t res_limb; + int64_t tmp_limb; + int64_t lhs_limb; + int64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i64(&res_limb, tmp_limb, overflow ? INT64_C(1) : INT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint64_t res_limb; + uint64_t tmp_limb; + uint64_t lhs_limb; + uint64_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u64(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u64(&res_limb, tmp_limb, overflow ? UINT64_C(1) : UINT64_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { + uint16_t limb_bits = 32 - (remaining_bytes == 32 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + if (remaining_bytes == 32 / CHAR_BIT && is_signed) { + int32_t res_limb; + int32_t tmp_limb; + int32_t lhs_limb; + int32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i32(&res_limb, tmp_limb, overflow ? INT32_C(1) : INT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint32_t res_limb; + uint32_t tmp_limb; + uint32_t lhs_limb; + uint32_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u32(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u32(&res_limb, tmp_limb, overflow ? UINT32_C(1) : UINT32_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { + uint16_t limb_bits = 16 - (remaining_bytes == 16 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + if (remaining_bytes == 16 / CHAR_BIT && is_signed) { + int16_t res_limb; + int16_t tmp_limb; + int16_t lhs_limb; + int16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i16(&res_limb, tmp_limb, overflow ? INT16_C(1) : INT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint16_t res_limb; + uint16_t tmp_limb; + uint16_t lhs_limb; + uint16_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u16(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u16(&res_limb, tmp_limb, overflow ? UINT16_C(1) : UINT16_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { + uint16_t limb_bits = 8 - (remaining_bytes == 8 / CHAR_BIT ? top_bits : 0); + +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + if (remaining_bytes == 8 / CHAR_BIT && is_signed) { + int8_t res_limb; + int8_t tmp_limb; + int8_t lhs_limb; + int8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_i8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_i8(&res_limb, tmp_limb, overflow ? INT8_C(1) : INT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } else { + uint8_t res_limb; + uint8_t tmp_limb; + uint8_t lhs_limb; + uint8_t rhs_limb; + bool limb_overflow; + + memcpy(&lhs_limb, &lhs_bytes[byte_offset], sizeof(lhs_limb)); + memcpy(&rhs_limb, &rhs_bytes[byte_offset], sizeof(rhs_limb)); + limb_overflow = zig_subo_u8(&tmp_limb, lhs_limb, rhs_limb, limb_bits); + overflow = limb_overflow ^ zig_subo_u8(&res_limb, tmp_limb, overflow ? UINT8_C(1) : UINT8_C(0), limb_bits); + memcpy(&res_bytes[byte_offset], &res_limb, sizeof(res_limb)); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return overflow; +} + +static inline void zig_addw_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + (void)zig_addo_big(res, lhs, rhs, is_signed, bits); +} + +static inline void zig_subw_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + (void)zig_subo_big(res, lhs, rhs, is_signed, bits); +} + +static inline uint16_t zig_clz_big(const void *val, bool is_signed, uint16_t bits) { + const uint8_t *val_bytes = val; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t skip_bits = remaining_bytes * 8 - bits; + uint16_t total_lz = 0; + uint16_t limb_lz; + (void)is_signed; + +#if zig_little_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + { + zig_u128 val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u128(val_limb, 128 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 128 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + { + uint64_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u64(val_limb, 64 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 64 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + { + uint32_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u32(val_limb, 32 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 32 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + { + uint16_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u16(val_limb, 16 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 16 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_little_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + { + uint8_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_lz = zig_clz_u8(val_limb, 8 - skip_bits); + } + + total_lz += limb_lz; + if (limb_lz < 8 - skip_bits) return total_lz; + skip_bits = 0; + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_big_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return total_lz; +} + +static inline uint16_t zig_ctz_big(const void *val, bool is_signed, uint16_t bits) { + const uint8_t *val_bytes = val; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t total_tz = 0; + uint16_t limb_tz; + (void)is_signed; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + { + zig_u128 val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u128(val_limb, 128); + } + + total_tz += limb_tz; + if (limb_tz < 128) return total_tz; + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + { + uint64_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u64(val_limb, 64); + } + + total_tz += limb_tz; + if (limb_tz < 64) return total_tz; + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + { + uint32_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u32(val_limb, 32); + } + + total_tz += limb_tz; + if (limb_tz < 32) return total_tz; + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + { + uint16_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u16(val_limb, 16); + } + + total_tz += limb_tz; + if (limb_tz < 16) return total_tz; + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + { + uint8_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + limb_tz = zig_ctz_u8(val_limb, 8); + } + + total_tz += limb_tz; + if (limb_tz < 8) return total_tz; + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return total_tz; +} + +static inline uint16_t zig_popcount_big(const void *val, bool is_signed, uint16_t bits) { + const uint8_t *val_bytes = val; + uint16_t byte_offset = 0; + uint16_t remaining_bytes = zig_int_bytes(bits); + uint16_t total_pc = 0; + (void)is_signed; + +#if zig_big_endian + byte_offset = remaining_bytes; +#endif + + while (remaining_bytes >= 128 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 128 / CHAR_BIT; +#endif + + { + zig_u128 val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc += zig_popcount_u128(val_limb, 128); + } + + remaining_bytes -= 128 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 128 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 64 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 64 / CHAR_BIT; +#endif + + { + uint64_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc += zig_popcount_u64(val_limb, 64); + } + + remaining_bytes -= 64 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 64 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 32 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 32 / CHAR_BIT; +#endif + + { + uint32_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc += zig_popcount_u32(val_limb, 32); + } + + remaining_bytes -= 32 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 32 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 16 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 16 / CHAR_BIT; +#endif + + { + uint16_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc = zig_popcount_u16(val_limb, 16); + } + + remaining_bytes -= 16 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 16 / CHAR_BIT; +#endif + } + + while (remaining_bytes >= 8 / CHAR_BIT) { +#if zig_big_endian + byte_offset -= 8 / CHAR_BIT; +#endif + + { + uint8_t val_limb; + + memcpy(&val_limb, &val_bytes[byte_offset], sizeof(val_limb)); + total_pc = zig_popcount_u8(val_limb, 8); + } + + remaining_bytes -= 8 / CHAR_BIT; + +#if zig_little_endian + byte_offset += 8 / CHAR_BIT; +#endif + } + + return total_pc; +} + /* ========================= Floating Point Support ========================= */ #if _MSC_VER @@ -1810,252 +2758,253 @@ static inline zig_i128 zig_bit_reverse_i128(zig_i128 val, zig_u8 bits) { #if (zig_has_builtin(nan) && zig_has_builtin(nans) && zig_has_builtin(inf)) || defined(zig_gnuc) #define zig_has_float_builtins 1 -#define zig_as_special_f16(sign, name, arg, repr) sign zig_as_f16(__builtin_##name, )(arg) -#define zig_as_special_f32(sign, name, arg, repr) sign zig_as_f32(__builtin_##name, )(arg) -#define zig_as_special_f64(sign, name, arg, repr) sign zig_as_f64(__builtin_##name, )(arg) -#define zig_as_special_f80(sign, name, arg, repr) sign zig_as_f80(__builtin_##name, )(arg) -#define zig_as_special_f128(sign, name, arg, repr) sign zig_as_f128(__builtin_##name, )(arg) -#define zig_as_special_c_longdouble(sign, name, arg, repr) sign zig_as_c_longdouble(__builtin_##name, )(arg) +#define zig_make_special_f16(sign, name, arg, repr) sign zig_make_f16(__builtin_##name, )(arg) +#define zig_make_special_f32(sign, name, arg, repr) sign zig_make_f32(__builtin_##name, )(arg) +#define zig_make_special_f64(sign, name, arg, repr) sign zig_make_f64(__builtin_##name, )(arg) +#define zig_make_special_f80(sign, name, arg, repr) sign zig_make_f80(__builtin_##name, )(arg) +#define zig_make_special_f128(sign, name, arg, repr) sign zig_make_f128(__builtin_##name, )(arg) #else #define zig_has_float_builtins 0 -#define zig_as_special_f16(sign, name, arg, repr) zig_float_from_repr_f16(repr) -#define zig_as_special_f32(sign, name, arg, repr) zig_float_from_repr_f32(repr) -#define zig_as_special_f64(sign, name, arg, repr) zig_float_from_repr_f64(repr) -#define zig_as_special_f80(sign, name, arg, repr) zig_float_from_repr_f80(repr) -#define zig_as_special_f128(sign, name, arg, repr) zig_float_from_repr_f128(repr) -#define zig_as_special_c_longdouble(sign, name, arg, repr) zig_float_from_repr_c_longdouble(repr) +#define zig_make_special_f16(sign, name, arg, repr) zig_float_from_repr_f16(repr) +#define zig_make_special_f32(sign, name, arg, repr) zig_float_from_repr_f32(repr) +#define zig_make_special_f64(sign, name, arg, repr) zig_float_from_repr_f64(repr) +#define zig_make_special_f80(sign, name, arg, repr) zig_float_from_repr_f80(repr) +#define zig_make_special_f128(sign, name, arg, repr) zig_float_from_repr_f128(repr) #endif #define zig_has_f16 1 #define zig_bitSizeOf_f16 16 +typedef int16_t zig_repr_f16; #define zig_libc_name_f16(name) __##name##h -#define zig_as_special_constant_f16(sign, name, arg, repr) zig_as_special_f16(sign, name, arg, repr) +#define zig_init_special_f16(sign, name, arg, repr) zig_make_special_f16(sign, name, arg, repr) #if FLT_MANT_DIG == 11 typedef float zig_f16; -#define zig_as_f16(fp, repr) fp##f +#define zig_make_f16(fp, repr) fp##f #elif DBL_MANT_DIG == 11 typedef double zig_f16; -#define zig_as_f16(fp, repr) fp +#define zig_make_f16(fp, repr) fp #elif LDBL_MANT_DIG == 11 #define zig_bitSizeOf_c_longdouble 16 +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f16 zig_repr_c_longdouble; +#endif typedef long double zig_f16; -#define zig_as_f16(fp, repr) fp##l +#define zig_make_f16(fp, repr) fp##l #elif FLT16_MANT_DIG == 11 && (zig_has_builtin(inff16) || defined(zig_gnuc)) typedef _Float16 zig_f16; -#define zig_as_f16(fp, repr) fp##f16 +#define zig_make_f16(fp, repr) fp##f16 #elif defined(__SIZEOF_FP16__) typedef __fp16 zig_f16; -#define zig_as_f16(fp, repr) fp##f16 +#define zig_make_f16(fp, repr) fp##f16 #else #undef zig_has_f16 #define zig_has_f16 0 -#define zig_repr_f16 i16 -typedef zig_i16 zig_f16; -#define zig_as_f16(fp, repr) repr -#undef zig_as_special_f16 -#define zig_as_special_f16(sign, name, arg, repr) repr -#undef zig_as_special_constant_f16 -#define zig_as_special_constant_f16(sign, name, arg, repr) repr +#define zig_bitSizeOf_repr_f16 16 +typedef int16_t zig_f16; +#define zig_make_f16(fp, repr) repr +#undef zig_make_special_f16 +#define zig_make_special_f16(sign, name, arg, repr) repr +#undef zig_init_special_f16 +#define zig_init_special_f16(sign, name, arg, repr) repr #endif #define zig_has_f32 1 #define zig_bitSizeOf_f32 32 +typedef int32_t zig_repr_f32; #define zig_libc_name_f32(name) name##f #if _MSC_VER -#define zig_as_special_constant_f32(sign, name, arg, repr) sign zig_as_f32(zig_msvc_flt_##name, ) +#define zig_init_special_f32(sign, name, arg, repr) sign zig_make_f32(zig_msvc_flt_##name, ) #else -#define zig_as_special_constant_f32(sign, name, arg, repr) zig_as_special_f32(sign, name, arg, repr) +#define zig_init_special_f32(sign, name, arg, repr) zig_make_special_f32(sign, name, arg, repr) #endif #if FLT_MANT_DIG == 24 typedef float zig_f32; -#define zig_as_f32(fp, repr) fp##f +#define zig_make_f32(fp, repr) fp##f #elif DBL_MANT_DIG == 24 typedef double zig_f32; -#define zig_as_f32(fp, repr) fp +#define zig_make_f32(fp, repr) fp #elif LDBL_MANT_DIG == 24 #define zig_bitSizeOf_c_longdouble 32 +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f32 zig_repr_c_longdouble; +#endif typedef long double zig_f32; -#define zig_as_f32(fp, repr) fp##l +#define zig_make_f32(fp, repr) fp##l #elif FLT32_MANT_DIG == 24 typedef _Float32 zig_f32; -#define zig_as_f32(fp, repr) fp##f32 +#define zig_make_f32(fp, repr) fp##f32 #else #undef zig_has_f32 #define zig_has_f32 0 -#define zig_repr_f32 i32 -typedef zig_i32 zig_f32; -#define zig_as_f32(fp, repr) repr -#undef zig_as_special_f32 -#define zig_as_special_f32(sign, name, arg, repr) repr -#undef zig_as_special_constant_f32 -#define zig_as_special_constant_f32(sign, name, arg, repr) repr +#define zig_bitSizeOf_repr_f32 32 +typedef int32_t zig_f32; +#define zig_make_f32(fp, repr) repr +#undef zig_make_special_f32 +#define zig_make_special_f32(sign, name, arg, repr) repr +#undef zig_init_special_f32 +#define zig_init_special_f32(sign, name, arg, repr) repr #endif #define zig_has_f64 1 #define zig_bitSizeOf_f64 64 +typedef int64_t zig_repr_f64; #define zig_libc_name_f64(name) name #if _MSC_VER #ifdef ZIG_TARGET_ABI_MSVC #define zig_bitSizeOf_c_longdouble 64 +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f64 zig_repr_c_longdouble; #endif -#define zig_as_special_constant_f64(sign, name, arg, repr) sign zig_as_f64(zig_msvc_flt_##name, ) +#endif +#define zig_init_special_f64(sign, name, arg, repr) sign zig_make_f64(zig_msvc_flt_##name, ) #else /* _MSC_VER */ -#define zig_as_special_constant_f64(sign, name, arg, repr) zig_as_special_f64(sign, name, arg, repr) +#define zig_init_special_f64(sign, name, arg, repr) zig_make_special_f64(sign, name, arg, repr) #endif /* _MSC_VER */ #if FLT_MANT_DIG == 53 typedef float zig_f64; -#define zig_as_f64(fp, repr) fp##f +#define zig_make_f64(fp, repr) fp##f #elif DBL_MANT_DIG == 53 typedef double zig_f64; -#define zig_as_f64(fp, repr) fp +#define zig_make_f64(fp, repr) fp #elif LDBL_MANT_DIG == 53 #define zig_bitSizeOf_c_longdouble 64 +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f64 zig_repr_c_longdouble; +#endif typedef long double zig_f64; -#define zig_as_f64(fp, repr) fp##l +#define zig_make_f64(fp, repr) fp##l #elif FLT64_MANT_DIG == 53 typedef _Float64 zig_f64; -#define zig_as_f64(fp, repr) fp##f64 +#define zig_make_f64(fp, repr) fp##f64 #elif FLT32X_MANT_DIG == 53 typedef _Float32x zig_f64; -#define zig_as_f64(fp, repr) fp##f32x +#define zig_make_f64(fp, repr) fp##f32x #else #undef zig_has_f64 #define zig_has_f64 0 -#define zig_repr_f64 i64 -typedef zig_i64 zig_f64; -#define zig_as_f64(fp, repr) repr -#undef zig_as_special_f64 -#define zig_as_special_f64(sign, name, arg, repr) repr -#undef zig_as_special_constant_f64 -#define zig_as_special_constant_f64(sign, name, arg, repr) repr +#define zig_bitSizeOf_repr_f64 64 +typedef int64_t zig_f64; +#define zig_make_f64(fp, repr) repr +#undef zig_make_special_f64 +#define zig_make_special_f64(sign, name, arg, repr) repr +#undef zig_init_special_f64 +#define zig_init_special_f64(sign, name, arg, repr) repr #endif #define zig_has_f80 1 #define zig_bitSizeOf_f80 80 +typedef zig_i128 zig_repr_f80; #define zig_libc_name_f80(name) __##name##x -#define zig_as_special_constant_f80(sign, name, arg, repr) zig_as_special_f80(sign, name, arg, repr) +#define zig_init_special_f80(sign, name, arg, repr) zig_make_special_f80(sign, name, arg, repr) #if FLT_MANT_DIG == 64 typedef float zig_f80; -#define zig_as_f80(fp, repr) fp##f +#define zig_make_f80(fp, repr) fp##f #elif DBL_MANT_DIG == 64 typedef double zig_f80; -#define zig_as_f80(fp, repr) fp +#define zig_make_f80(fp, repr) fp #elif LDBL_MANT_DIG == 64 #define zig_bitSizeOf_c_longdouble 80 +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f80 zig_repr_c_longdouble; +#endif typedef long double zig_f80; -#define zig_as_f80(fp, repr) fp##l +#define zig_make_f80(fp, repr) fp##l #elif FLT80_MANT_DIG == 64 typedef _Float80 zig_f80; -#define zig_as_f80(fp, repr) fp##f80 +#define zig_make_f80(fp, repr) fp##f80 #elif FLT64X_MANT_DIG == 64 typedef _Float64x zig_f80; -#define zig_as_f80(fp, repr) fp##f64x +#define zig_make_f80(fp, repr) fp##f64x #elif defined(__SIZEOF_FLOAT80__) typedef __float80 zig_f80; -#define zig_as_f80(fp, repr) fp##l +#define zig_make_f80(fp, repr) fp##l #else #undef zig_has_f80 #define zig_has_f80 0 -#define zig_repr_f80 i128 +#define zig_bitSizeOf_repr_f80 128 typedef zig_i128 zig_f80; -#define zig_as_f80(fp, repr) repr -#undef zig_as_special_f80 -#define zig_as_special_f80(sign, name, arg, repr) repr -#undef zig_as_special_constant_f80 -#define zig_as_special_constant_f80(sign, name, arg, repr) repr +#define zig_make_f80(fp, repr) repr +#undef zig_make_special_f80 +#define zig_make_special_f80(sign, name, arg, repr) repr +#undef zig_init_special_f80 +#define zig_init_special_f80(sign, name, arg, repr) repr #endif #define zig_has_f128 1 #define zig_bitSizeOf_f128 128 +typedef zig_i128 zig_repr_f128; #define zig_libc_name_f128(name) name##q -#define zig_as_special_constant_f128(sign, name, arg, repr) zig_as_special_f128(sign, name, arg, repr) +#define zig_init_special_f128(sign, name, arg, repr) zig_make_special_f128(sign, name, arg, repr) #if FLT_MANT_DIG == 113 typedef float zig_f128; -#define zig_as_f128(fp, repr) fp##f +#define zig_make_f128(fp, repr) fp##f #elif DBL_MANT_DIG == 113 typedef double zig_f128; -#define zig_as_f128(fp, repr) fp +#define zig_make_f128(fp, repr) fp #elif LDBL_MANT_DIG == 113 #define zig_bitSizeOf_c_longdouble 128 +#ifndef ZIG_TARGET_ABI_MSVC +typedef zig_repr_f128 zig_repr_c_longdouble; +#endif typedef long double zig_f128; -#define zig_as_f128(fp, repr) fp##l +#define zig_make_f128(fp, repr) fp##l #elif FLT128_MANT_DIG == 113 typedef _Float128 zig_f128; -#define zig_as_f128(fp, repr) fp##f128 +#define zig_make_f128(fp, repr) fp##f128 #elif FLT64X_MANT_DIG == 113 typedef _Float64x zig_f128; -#define zig_as_f128(fp, repr) fp##f64x +#define zig_make_f128(fp, repr) fp##f64x #elif defined(__SIZEOF_FLOAT128__) typedef __float128 zig_f128; -#define zig_as_f128(fp, repr) fp##q -#undef zig_as_special_f128 -#define zig_as_special_f128(sign, name, arg, repr) sign __builtin_##name##f128(arg) +#define zig_make_f128(fp, repr) fp##q +#undef zig_make_special_f128 +#define zig_make_special_f128(sign, name, arg, repr) sign __builtin_##name##f128(arg) #else #undef zig_has_f128 #define zig_has_f128 0 -#define zig_repr_f128 i128 +#define zig_bitSizeOf_repr_f128 128 typedef zig_i128 zig_f128; -#define zig_as_f128(fp, repr) repr -#undef zig_as_special_f128 -#define zig_as_special_f128(sign, name, arg, repr) repr -#undef zig_as_special_constant_f128 -#define zig_as_special_constant_f128(sign, name, arg, repr) repr +#define zig_make_f128(fp, repr) repr +#undef zig_make_special_f128 +#define zig_make_special_f128(sign, name, arg, repr) repr +#undef zig_init_special_f128 +#define zig_init_special_f128(sign, name, arg, repr) repr #endif -#define zig_has_c_longdouble 1 - -#ifdef ZIG_TARGET_ABI_MSVC -#define zig_libc_name_c_longdouble(name) name -#else -#define zig_libc_name_c_longdouble(name) name##l -#endif - -#define zig_as_special_constant_c_longdouble(sign, name, arg, repr) zig_as_special_c_longdouble(sign, name, arg, repr) #ifdef zig_bitSizeOf_c_longdouble +#define zig_has_c_longdouble 1 #ifdef ZIG_TARGET_ABI_MSVC -typedef double zig_c_longdouble; #undef zig_bitSizeOf_c_longdouble #define zig_bitSizeOf_c_longdouble 64 -#define zig_as_c_longdouble(fp, repr) fp +typedef zig_f64 zig_c_longdouble; +typedef zig_repr_f64 zig_repr_c_longdouble; #else typedef long double zig_c_longdouble; -#define zig_as_c_longdouble(fp, repr) fp##l #endif #else /* zig_bitSizeOf_c_longdouble */ -#undef zig_has_c_longdouble #define zig_has_c_longdouble 0 -#define zig_bitSizeOf_c_longdouble 80 -#define zig_compiler_rt_abbrev_c_longdouble zig_compiler_rt_abbrev_f80 -#define zig_repr_c_longdouble i128 -typedef zig_i128 zig_c_longdouble; -#define zig_as_c_longdouble(fp, repr) repr -#undef zig_as_special_c_longdouble -#define zig_as_special_c_longdouble(sign, name, arg, repr) repr -#undef zig_as_special_constant_c_longdouble -#define zig_as_special_constant_c_longdouble(sign, name, arg, repr) repr +#define zig_bitSizeOf_repr_c_longdouble 128 +typedef zig_f128 zig_c_longdouble; +typedef zig_repr_f128 zig_repr_c_longdouble; #endif /* zig_bitSizeOf_c_longdouble */ #if !zig_has_float_builtins -#define zig_float_from_repr(Type, ReprType) \ - static inline zig_##Type zig_float_from_repr_##Type(zig_##ReprType repr) { \ - return *((zig_##Type*)&repr); \ +#define zig_float_from_repr(Type) \ + static inline zig_##Type zig_float_from_repr_##Type(zig_repr_##Type repr) { \ + zig_##Type result; \ + memcpy(&result, &repr, sizeof(result)); \ + return result; \ } -zig_float_from_repr(f16, u16) -zig_float_from_repr(f32, u32) -zig_float_from_repr(f64, u64) -zig_float_from_repr(f80, u128) -zig_float_from_repr(f128, u128) -#if zig_bitSizeOf_c_longdouble == 80 -zig_float_from_repr(c_longdouble, u128) -#else -#define zig_expand_float_from_repr(Type, ReprType) zig_float_from_repr(Type, ReprType) -zig_expand_float_from_repr(c_longdouble, zig_expand_concat(u, zig_bitSizeOf_c_longdouble)) -#endif +zig_float_from_repr(f16) +zig_float_from_repr(f32) +zig_float_from_repr(f64) +zig_float_from_repr(f80) +zig_float_from_repr(f128) #endif #define zig_cast_f16 (zig_f16) @@ -2064,41 +3013,42 @@ zig_expand_float_from_repr(c_longdouble, zig_expand_concat(u, zig_bitSizeOf_c_lo #if _MSC_VER && !zig_has_f128 #define zig_cast_f80 -#define zig_cast_c_longdouble #define zig_cast_f128 #else #define zig_cast_f80 (zig_f80) -#define zig_cast_c_longdouble (zig_c_longdouble) #define zig_cast_f128 (zig_f128) #endif #define zig_convert_builtin(ResType, operation, ArgType, version) \ - zig_extern zig_##ResType zig_expand_concat(zig_expand_concat(zig_expand_concat(__##operation, \ - zig_compiler_rt_abbrev_##ArgType), zig_compiler_rt_abbrev_##ResType), version)(zig_##ArgType); -zig_convert_builtin(f16, trunc, f32, 2) -zig_convert_builtin(f16, trunc, f64, 2) -zig_convert_builtin(f16, trunc, f80, 2) -zig_convert_builtin(f16, trunc, f128, 2) -zig_convert_builtin(f32, extend, f16, 2) -zig_convert_builtin(f32, trunc, f64, 2) -zig_convert_builtin(f32, trunc, f80, 2) -zig_convert_builtin(f32, trunc, f128, 2) -zig_convert_builtin(f64, extend, f16, 2) -zig_convert_builtin(f64, extend, f32, 2) -zig_convert_builtin(f64, trunc, f80, 2) -zig_convert_builtin(f64, trunc, f128, 2) -zig_convert_builtin(f80, extend, f16, 2) -zig_convert_builtin(f80, extend, f32, 2) -zig_convert_builtin(f80, extend, f64, 2) -zig_convert_builtin(f80, trunc, f128, 2) -zig_convert_builtin(f128, extend, f16, 2) -zig_convert_builtin(f128, extend, f32, 2) -zig_convert_builtin(f128, extend, f64, 2) -zig_convert_builtin(f128, extend, f80, 2) + zig_extern ResType zig_expand_concat(zig_expand_concat(zig_expand_concat(__##operation, \ + zig_compiler_rt_abbrev_##ArgType), zig_compiler_rt_abbrev_##ResType), version)(ArgType); +zig_convert_builtin(zig_f16, trunc, zig_f32, 2) +zig_convert_builtin(zig_f16, trunc, zig_f64, 2) +zig_convert_builtin(zig_f16, trunc, zig_f80, 2) +zig_convert_builtin(zig_f16, trunc, zig_f128, 2) +zig_convert_builtin(zig_f32, extend, zig_f16, 2) +zig_convert_builtin(zig_f32, trunc, zig_f64, 2) +zig_convert_builtin(zig_f32, trunc, zig_f80, 2) +zig_convert_builtin(zig_f32, trunc, zig_f128, 2) +zig_convert_builtin(zig_f64, extend, zig_f16, 2) +zig_convert_builtin(zig_f64, extend, zig_f32, 2) +zig_convert_builtin(zig_f64, trunc, zig_f80, 2) +zig_convert_builtin(zig_f64, trunc, zig_f128, 2) +zig_convert_builtin(zig_f80, extend, zig_f16, 2) +zig_convert_builtin(zig_f80, extend, zig_f32, 2) +zig_convert_builtin(zig_f80, extend, zig_f64, 2) +zig_convert_builtin(zig_f80, trunc, zig_f128, 2) +zig_convert_builtin(zig_f128, extend, zig_f16, 2) +zig_convert_builtin(zig_f128, extend, zig_f32, 2) +zig_convert_builtin(zig_f128, extend, zig_f64, 2) +zig_convert_builtin(zig_f128, extend, zig_f80, 2) #define zig_float_negate_builtin_0(Type) \ static inline zig_##Type zig_neg_##Type(zig_##Type arg) { \ - return zig_expand_concat(zig_xor_, zig_repr_##Type)(arg, zig_expand_minInt(zig_repr_##Type, zig_bitSizeOf_##Type)); \ + return zig_expand_concat(zig_xor_i, zig_bitSizeOf_repr_##Type)( \ + arg, \ + zig_minInt_i(zig_bitSizeOf_repr_##Type, zig_bitSizeOf_##Type) \ + ); \ } #define zig_float_negate_builtin_1(Type) \ static inline zig_##Type zig_neg_##Type(zig_##Type arg) { \ @@ -2106,28 +3056,28 @@ zig_convert_builtin(f128, extend, f80, 2) } #define zig_float_less_builtin_0(Type, operation) \ - zig_extern zig_i32 zig_expand_concat(zig_expand_concat(__##operation, \ - zig_compiler_rt_abbrev_##Type), 2)(zig_##Type, zig_##Type); \ - static inline zig_i32 zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ - return zig_expand_concat(zig_expand_concat(__##operation, zig_compiler_rt_abbrev_##Type), 2)(lhs, rhs); \ + zig_extern int32_t zig_expand_concat(zig_expand_concat(__##operation, \ + zig_compiler_rt_abbrev_zig_##Type), 2)(zig_##Type, zig_##Type); \ + static inline int32_t zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ + return zig_expand_concat(zig_expand_concat(__##operation, zig_compiler_rt_abbrev_zig_##Type), 2)(lhs, rhs); \ } #define zig_float_less_builtin_1(Type, operation) \ - static inline zig_i32 zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ + static inline int32_t zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ return (!(lhs <= rhs) - (lhs < rhs)); \ } #define zig_float_greater_builtin_0(Type, operation) \ zig_float_less_builtin_0(Type, operation) #define zig_float_greater_builtin_1(Type, operation) \ - static inline zig_i32 zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ + static inline int32_t zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ return ((lhs > rhs) - !(lhs >= rhs)); \ } #define zig_float_binary_builtin_0(Type, operation, operator) \ zig_extern zig_##Type zig_expand_concat(zig_expand_concat(__##operation, \ - zig_compiler_rt_abbrev_##Type), 3)(zig_##Type, zig_##Type); \ + zig_compiler_rt_abbrev_zig_##Type), 3)(zig_##Type, zig_##Type); \ static inline zig_##Type zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ - return zig_expand_concat(zig_expand_concat(__##operation, zig_compiler_rt_abbrev_##Type), 3)(lhs, rhs); \ + return zig_expand_concat(zig_expand_concat(__##operation, zig_compiler_rt_abbrev_zig_##Type), 3)(lhs, rhs); \ } #define zig_float_binary_builtin_1(Type, operation, operator) \ static inline zig_##Type zig_##operation##_##Type(zig_##Type lhs, zig_##Type rhs) { \ @@ -2135,18 +3085,18 @@ zig_convert_builtin(f128, extend, f80, 2) } #define zig_float_builtins(Type) \ - zig_convert_builtin(i32, fix, Type, ) \ - zig_convert_builtin(u32, fixuns, Type, ) \ - zig_convert_builtin(i64, fix, Type, ) \ - zig_convert_builtin(u64, fixuns, Type, ) \ - zig_convert_builtin(i128, fix, Type, ) \ - zig_convert_builtin(u128, fixuns, Type, ) \ - zig_convert_builtin(Type, float, i32, ) \ - zig_convert_builtin(Type, floatun, u32, ) \ - zig_convert_builtin(Type, float, i64, ) \ - zig_convert_builtin(Type, floatun, u64, ) \ - zig_convert_builtin(Type, float, i128, ) \ - zig_convert_builtin(Type, floatun, u128, ) \ + zig_convert_builtin( int32_t, fix, zig_##Type, ) \ + zig_convert_builtin(uint32_t, fixuns, zig_##Type, ) \ + zig_convert_builtin( int64_t, fix, zig_##Type, ) \ + zig_convert_builtin(uint64_t, fixuns, zig_##Type, ) \ + zig_convert_builtin(zig_i128, fix, zig_##Type, ) \ + zig_convert_builtin(zig_u128, fixuns, zig_##Type, ) \ + zig_convert_builtin(zig_##Type, float, int32_t, ) \ + zig_convert_builtin(zig_##Type, floatun, uint32_t, ) \ + zig_convert_builtin(zig_##Type, float, int64_t, ) \ + zig_convert_builtin(zig_##Type, floatun, uint64_t, ) \ + zig_convert_builtin(zig_##Type, float, zig_i128, ) \ + zig_convert_builtin(zig_##Type, floatun, zig_u128, ) \ zig_expand_concat(zig_float_negate_builtin_, zig_has_##Type)(Type) \ zig_expand_concat(zig_float_less_builtin_, zig_has_##Type)(Type, cmp) \ zig_expand_concat(zig_float_less_builtin_, zig_has_##Type)(Type, ne) \ @@ -2194,155 +3144,162 @@ zig_float_builtins(f32) zig_float_builtins(f64) zig_float_builtins(f80) zig_float_builtins(f128) -zig_float_builtins(c_longdouble) #if _MSC_VER && (_M_IX86 || _M_X64) // TODO: zig_msvc_atomic_load should load 32 bit without interlocked on x86, and load 64 bit without interlocked on x64 -#define zig_msvc_atomics(Type, suffix) \ - static inline bool zig_msvc_cmpxchg_##Type(zig_##Type volatile* obj, zig_##Type* expected, zig_##Type desired) { \ - zig_##Type comparand = *expected; \ - zig_##Type initial = _InterlockedCompareExchange##suffix(obj, desired, comparand); \ +#define zig_msvc_atomics(ZigType, Type, suffix) \ + static inline bool zig_msvc_cmpxchg_##ZigType(Type volatile* obj, Type* expected, Type desired) { \ + Type comparand = *expected; \ + Type initial = _InterlockedCompareExchange##suffix(obj, desired, comparand); \ bool exchanged = initial == comparand; \ if (!exchanged) { \ *expected = initial; \ } \ return exchanged; \ } \ - static inline zig_##Type zig_msvc_atomicrmw_xchg_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_xchg_##ZigType(Type volatile* obj, Type value) { \ return _InterlockedExchange##suffix(obj, value); \ } \ - static inline zig_##Type zig_msvc_atomicrmw_add_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_add_##ZigType(Type volatile* obj, Type value) { \ return _InterlockedExchangeAdd##suffix(obj, value); \ } \ - static inline zig_##Type zig_msvc_atomicrmw_sub_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_sub_##ZigType(Type volatile* obj, Type value) { \ bool success = false; \ - zig_##Type new; \ - zig_##Type prev; \ + Type new; \ + Type prev; \ while (!success) { \ prev = *obj; \ new = prev - value; \ - success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \ + success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \ } \ return prev; \ } \ - static inline zig_##Type zig_msvc_atomicrmw_or_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_or_##ZigType(Type volatile* obj, Type value) { \ return _InterlockedOr##suffix(obj, value); \ } \ - static inline zig_##Type zig_msvc_atomicrmw_xor_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_xor_##ZigType(Type volatile* obj, Type value) { \ return _InterlockedXor##suffix(obj, value); \ } \ - static inline zig_##Type zig_msvc_atomicrmw_and_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_and_##ZigType(Type volatile* obj, Type value) { \ return _InterlockedAnd##suffix(obj, value); \ } \ - static inline zig_##Type zig_msvc_atomicrmw_nand_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_nand_##ZigType(Type volatile* obj, Type value) { \ bool success = false; \ - zig_##Type new; \ - zig_##Type prev; \ + Type new; \ + Type prev; \ while (!success) { \ prev = *obj; \ new = ~(prev & value); \ - success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \ + success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \ } \ return prev; \ } \ - static inline zig_##Type zig_msvc_atomicrmw_min_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_min_##ZigType(Type volatile* obj, Type value) { \ bool success = false; \ - zig_##Type new; \ - zig_##Type prev; \ + Type new; \ + Type prev; \ while (!success) { \ prev = *obj; \ new = value < prev ? value : prev; \ - success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \ + success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \ } \ return prev; \ } \ - static inline zig_##Type zig_msvc_atomicrmw_max_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline Type zig_msvc_atomicrmw_max_##ZigType(Type volatile* obj, Type value) { \ bool success = false; \ - zig_##Type new; \ - zig_##Type prev; \ + Type new; \ + Type prev; \ while (!success) { \ prev = *obj; \ new = value > prev ? value : prev; \ - success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \ + success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \ } \ return prev; \ } \ - static inline void zig_msvc_atomic_store_##Type(zig_##Type volatile* obj, zig_##Type value) { \ + static inline void zig_msvc_atomic_store_##ZigType(Type volatile* obj, Type value) { \ _InterlockedExchange##suffix(obj, value); \ } \ - static inline zig_##Type zig_msvc_atomic_load_##Type(zig_##Type volatile* obj) { \ + static inline Type zig_msvc_atomic_load_##ZigType(Type volatile* obj) { \ return _InterlockedOr##suffix(obj, 0); \ } -zig_msvc_atomics(u8, 8) -zig_msvc_atomics(i8, 8) -zig_msvc_atomics(u16, 16) -zig_msvc_atomics(i16, 16) -zig_msvc_atomics(u32, ) -zig_msvc_atomics(i32, ) +zig_msvc_atomics( u8, uint8_t, 8) +zig_msvc_atomics( i8, int8_t, 8) +zig_msvc_atomics(u16, uint16_t, 16) +zig_msvc_atomics(i16, int16_t, 16) +zig_msvc_atomics(u32, uint32_t, ) +zig_msvc_atomics(i32, int32_t, ) #if _M_X64 -zig_msvc_atomics(u64, 64) -zig_msvc_atomics(i64, 64) +zig_msvc_atomics(u64, uint64_t, 64) +zig_msvc_atomics(i64, int64_t, 64) #endif #define zig_msvc_flt_atomics(Type, ReprType, suffix) \ static inline bool zig_msvc_cmpxchg_##Type(zig_##Type volatile* obj, zig_##Type* expected, zig_##Type desired) { \ - zig_##ReprType comparand = *((zig_##ReprType*)expected); \ - zig_##ReprType initial = _InterlockedCompareExchange##suffix((zig_##ReprType volatile*)obj, *((zig_##ReprType*)&desired), comparand); \ - bool exchanged = initial == comparand; \ - if (!exchanged) { \ - *expected = *((zig_##Type*)&initial); \ - } \ - return exchanged; \ + ReprType exchange; \ + ReprType comparand; \ + ReprType initial; \ + bool success; \ + memcpy(&comparand, expected, sizeof(comparand)); \ + memcpy(&exchange, &desired, sizeof(exchange)); \ + initial = _InterlockedCompareExchange##suffix((ReprType volatile*)obj, exchange, comparand); \ + success = initial == comparand; \ + if (!success) memcpy(expected, &initial, sizeof(*expected)); \ + return success; \ } \ static inline zig_##Type zig_msvc_atomicrmw_xchg_##Type(zig_##Type volatile* obj, zig_##Type value) { \ - zig_##ReprType initial = _InterlockedExchange##suffix((zig_##ReprType volatile*)obj, *((zig_##ReprType*)&value)); \ - return *((zig_##Type*)&initial); \ + ReprType repr; \ + ReprType initial; \ + zig_##Type result; \ + memcpy(&repr, &value, sizeof(repr)); \ + initial = _InterlockedExchange##suffix((ReprType volatile*)obj, repr); \ + memcpy(&result, &initial, sizeof(result)); \ + return result; \ } \ static inline zig_##Type zig_msvc_atomicrmw_add_##Type(zig_##Type volatile* obj, zig_##Type value) { \ - bool success = false; \ - zig_##ReprType new; \ - zig_##Type prev; \ - while (!success) { \ - prev = *obj; \ - new = prev + value; \ - success = zig_msvc_cmpxchg_##Type(obj, &prev, *((zig_##ReprType*)&new)); \ - } \ - return prev; \ + ReprType repr; \ + zig_##Type expected; \ + zig_##Type desired; \ + repr = *(ReprType volatile*)obj; \ + memcpy(&expected, &repr, sizeof(expected)); \ + do { \ + desired = expected + value; \ + } while (!zig_msvc_cmpxchg_##Type(obj, &expected, desired)); \ + return expected; \ } \ static inline zig_##Type zig_msvc_atomicrmw_sub_##Type(zig_##Type volatile* obj, zig_##Type value) { \ - bool success = false; \ - zig_##ReprType new; \ - zig_##Type prev; \ - while (!success) { \ - prev = *obj; \ - new = prev - value; \ - success = zig_msvc_cmpxchg_##Type(obj, &prev, *((zig_##ReprType*)&new)); \ - } \ - return prev; \ + ReprType repr; \ + zig_##Type expected; \ + zig_##Type desired; \ + repr = *(ReprType volatile*)obj; \ + memcpy(&expected, &repr, sizeof(expected)); \ + do { \ + desired = expected - value; \ + } while (!zig_msvc_cmpxchg_##Type(obj, &expected, desired)); \ + return expected; \ } -zig_msvc_flt_atomics(f32, u32, ) +zig_msvc_flt_atomics(f32, uint32_t, ) #if _M_X64 -zig_msvc_flt_atomics(f64, u64, 64) +zig_msvc_flt_atomics(f64, uint64_t, 64) #endif #if _M_IX86 static inline void zig_msvc_atomic_barrier() { - zig_i32 barrier; + int32_t barrier; __asm { xchg barrier, eax } } -static inline void* zig_msvc_atomicrmw_xchg_p32(void** obj, zig_u32* arg) { +static inline void* zig_msvc_atomicrmw_xchg_p32(void** obj, void* arg) { return _InterlockedExchangePointer(obj, arg); } -static inline void zig_msvc_atomic_store_p32(void** obj, zig_u32* arg) { +static inline void zig_msvc_atomic_store_p32(void** obj, void* arg) { _InterlockedExchangePointer(obj, arg); } @@ -2360,11 +3317,11 @@ static inline bool zig_msvc_cmpxchg_p32(void** obj, void** expected, void* desir return exchanged; } #else /* _M_IX86 */ -static inline void* zig_msvc_atomicrmw_xchg_p64(void** obj, zig_u64* arg) { +static inline void* zig_msvc_atomicrmw_xchg_p64(void** obj, void* arg) { return _InterlockedExchangePointer(obj, arg); } -static inline void zig_msvc_atomic_store_p64(void** obj, zig_u64* arg) { +static inline void zig_msvc_atomic_store_p64(void** obj, void* arg) { _InterlockedExchangePointer(obj, arg); } @@ -2383,11 +3340,11 @@ static inline bool zig_msvc_cmpxchg_p64(void** obj, void** expected, void* desir } static inline bool zig_msvc_cmpxchg_u128(zig_u128 volatile* obj, zig_u128* expected, zig_u128 desired) { - return _InterlockedCompareExchange128((zig_i64 volatile*)obj, desired.hi, desired.lo, (zig_i64*)expected); + return _InterlockedCompareExchange128((int64_t volatile*)obj, desired.hi, desired.lo, (int64_t*)expected); } static inline bool zig_msvc_cmpxchg_i128(zig_i128 volatile* obj, zig_i128* expected, zig_i128 desired) { - return _InterlockedCompareExchange128((zig_i64 volatile*)obj, desired.hi, desired.lo, (zig_u64*)expected); + return _InterlockedCompareExchange128((int64_t volatile*)obj, desired.hi, desired.lo, (uint64_t*)expected); } #define zig_msvc_atomics_128xchg(Type) \ @@ -2429,7 +3386,7 @@ zig_msvc_atomics_128op(u128, max) #endif /* _MSC_VER && (_M_IX86 || _M_X64) */ -/* ========================= Special Case Intrinsics ========================= */ +/* ======================== Special Case Intrinsics ========================= */ #if (_MSC_VER && _M_X64) || defined(__x86_64__) @@ -2459,8 +3416,8 @@ static inline void* zig_x86_windows_teb(void) { #if (_MSC_VER && (_M_IX86 || _M_X64)) || defined(__i386__) || defined(__x86_64__) -static inline void zig_x86_cpuid(zig_u32 leaf_id, zig_u32 subid, zig_u32* eax, zig_u32* ebx, zig_u32* ecx, zig_u32* edx) { - zig_u32 cpu_info[4]; +static inline void zig_x86_cpuid(uint32_t leaf_id, uint32_t subid, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx) { + uint32_t cpu_info[4]; #if _MSC_VER __cpuidex(cpu_info, leaf_id, subid); #else @@ -2472,12 +3429,12 @@ static inline void zig_x86_cpuid(zig_u32 leaf_id, zig_u32 subid, zig_u32* eax, z *edx = cpu_info[3]; } -static inline zig_u32 zig_x86_get_xcr0(void) { +static inline uint32_t zig_x86_get_xcr0(void) { #if _MSC_VER - return (zig_u32)_xgetbv(0); + return (uint32_t)_xgetbv(0); #else - zig_u32 eax; - zig_u32 edx; + uint32_t eax; + uint32_t edx; __asm__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); return eax; #endif diff --git a/stage1/zig1.wasm b/stage1/zig1.wasm index d7bf519b41a74966a3bc46b133fd9b34aa200f75..fa2b2efa03b4ed138a7f8c52e72648d828f2116a 100644 GIT binary patch delta 933731 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%0D`tiYI)C9hW8; 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ΝtL^>Um!=?y=+}h?sX1dz-EPKJI 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 z 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�R(jIvPyqJC#;r~R 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

-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 hKTyN>_R%xIutIX6O-WL&iS#SNOt`s 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 delta 919109 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 P#eUA8ix@WNP) z*$m?zFNwH%A~Ksvl#~D*`Gf$KZn=d-h()^l7FB-yVj-0xgtnxcdYZHB81!d&d!G7R zl;r5_D)&Z1c&!k{w&bl4Q+?!ym`HovX!dUC(>Rv&akr|;vu-kA+P(!C5iIqQ+woo* zB~tXvVs0A{ZD|Nj`_ZjP(ycS%-P;T!KDBK~QPA9`PWWH2D44>xXHmbrO%+vbIYlBh z(=zpk*kU4Dk1=v36s_!`BuS0Jjb}&)(E}Vq>v7bBL_t0}C`d zE6E{r3kYtqK+G#ytw-Ii#-?rw%_xCvOWj+e;x?1qAXE=BnM5SOljyTOj{SxC2LD*z%p}}&<#;{HP%2Au)%Uy2Konmz` zco{Znm7+C)KHF>=rCdLC2MX<@`olX=@g9DI$aKG*{|>}fG+(d;c_Lrfy4uBpuDjf3hN_>pzfF%j7q+p@O?hf*Z%SA?zng^q9#GAerAQ*m3tZr z|BgBTflmEhU1Ig?;yYD>hZFBq-ys|QukOUQ{7B!@4o?dnek>2G^!N_7vp%w29cFzJ zzJ9Eowft#&euo-kS)b`Ccd=W$O#k98HDJA`e|on%l!Vi_-K~D?w^^x^_i&=?1O5Gb z)c#e1NU@i3u0l!>7pJ6A<&tstLw(CVB#!+^uenDh)W@*`OW($~5$-4HDv|6e?x%Xx zN(Qz&AO=d<^Ked)1YAZ@zyYUWzaD>`ub5 zHr}U(liX+1ed>xWAC1y$?^kE|&y$oorBj{7uZ^ARN5$*TgSISaOK!;|+vLFysNa+a z4}FddK?Lt!<{iB_v1>TyOWNs;XXw&5UadJsZtZhSFgSbugzr2GumN4! zMQ+&^{fjR3GkcZ8rIRY5*LA7gY;HL@xKouD6eRP6V~Co1zTWFG^%U!3^`c+COpjX4ap@Ml*J={~p09tl8W5{=^J>yF zZLO(EI`O1>bg1@5=^w69J65j3ivucQT)AE(-v|&kEw~u%uWQsr;J*GT*3uh#zo*o? zDKD@eGR3My=!pw3f1G3*i5MkEoJR1lT8VX2XliUpx;^c;bgOEMGv7}@ur4q6eMX%%>J?UoY}oiWL*N?r%RFa_FW0X>!|u!3 zO6NVx-6^l?3!YU+#qcD=+(-03o>dF2*YuyB!*+c}w?3y%i9Kc2#@!3_sOMFKb)o+2 z^XeqtQOd;H7{KX6)bM$3BWU07HKivcQUe*3?5385{{c^2J zj%i|(C~%?IF-N55hyUP}b$LPCjAv2%^=nCIjmLBLE9!8vJh#e6m$qJIU%yLNzN(Uy zkCELkfYK$W`vJibP)>v#{3<+dQKpRs*^}ZJDt|e{imPSCtKfB!9`zb)_f7r6Yp~sy zbjj;#%y^;}LzrBl6ZJTiOFFoyoZpp*MzbRffTLeWtgO?2d|g$I4GyT$XSRO|Bq&WX zZS^S#Rocbz!HJuDUx)kNqc>1*ELaW(DHSkrOogUsPys36d^$H!%UkAawq^GTe=e(p3Sf{Eg?uv0xAl-A>4V0utcj-U!(z;z=wN6baVy>fP z4>R5t#_-fS82!`wgLN#Mr*-a|YDYlt@umTN!ke`IvObRw`(;~q{@pFrE8j$~T-3ht zO_gKOmrvh<4Bytry^Z{NTc7*3nnz!(^=hwiZ{5hQ5P@`Icba8#TUZWJuNhyb|62cI zz1pGTq0AKi&YwY|7Tt&RE$ivqgL=(+60Sd}*RO|dtu*CLO;_iYU z*yvLxYaeG%+;{bD8_47RC#9d+z}&9T7xbx-3T{1GAG_R5wm;FQPDo!f7B$z52~xOb z%zIb83HKQH5A{brmhmB?VC+UUd&F`wMZugs!?9UDBm;)~B=cB97j0BGanIPU??L0N z$-~}9VBMg*-dFp^+Wra_ZqTJ4;MKc9@9}}!#kx`d&j)I|+M8HqXe|~#2czJ%ap$T) zUBMHCa1?#`p%2vl#9kHls~=XJ%lfX5%tO(@c~9Vi#R4gS?ftG_O)R<}>(eD%%74~= zYd@Lr;Gwx6fvOdH_m9*}e*NYn^yI(m8|2qD`pu8jWFER7t6A2a`jC&+h1|6D>c?mb zEqeMV>KKY#_K7-?%R#>UM7_qlwVxWk{I}24(Y)LA84LNY_Om|sTY7!~d2qL$@C8S| z?h#x{hK8uOQnW$0MgQyzR`JUAoPUy|oyv!Osisx5kXRs}lu=Rf0rIz1+(VRI^rgCt zn@P6+ilx$`5B~~sYtfBg(dqm217AVw_vlx?B4zytI_GOL1>Udgzec)r>Vv;V=)R-x z|5{Df%f6U)N2m0N27W~cG(P~-n zf-?)6&9pIDA8R?oU}4#h?vh83Wj|^!v3X?O&^|8e47b=sx>PxP>fb77g#Z0;dO6K; zChKyCN9Oy!;iF}aGfb_I)mZvzcy}V$KZf0sX6(GnIL{ zI_69Q*^kGZ{X|xk#EE^qMt>)6p8j{-IYOR39yevbj5|UBqoRKpF`1L_sp-Is{R~??`6vze^5tzL+wA#}P$@q>~cq z9;=TW;hPj(DLv>7A`y+@di}lO&KOq1$-|wWpzJ?9+!=2VbnA`7oe9IUDzIH90Q9&L zXLJ$b3Au<|7O}caA6i1smgzs2I6IF;#G^fsu2|Y9_-s(C*hOee-MY8LsqsYzyJ`WD z;!1mYuo=YsGAFrD7wxE8oN7O2Z?MRYSXk@QyEOJN$awpsA+X0A!JY#z{;e0qWOww(gYjTmq8~SvJJq{qE|wsDppmfvkDA=oAu>B`unA2G zv7mbXBi|~Jz2{C_s|u%{tB8_0k_Z)kKRrm?&(~SCv8o!;7|^u1Z3x=qw{a}aG&j(q zA?%AD28lu~Vsm1~;%k-sW&YJ6v8p5~Ve3pvSTY*8|3IRk=GCHT1W(x|0^{`&|7QW` zg@uwO`UAy_cB3ZB9Dy3~y#$h2Uaze{pKZ`fDxC^nB10d4n4_?u;1vLlJdw5-53ZAoX4bKtNV(3gg6KdvgL`a^n9`V zce6yF{r|0NN&VCaXNMz9|5y?0<%X~IcIK; zS*|JB3T78pJ0rFW5etQ}=Km#fzgz9>oHCskdKsegJl?GHJjowKWre{nGC7BXf)Wh{ z25_~hc&YJu$%!1&={fFvy>O(n zGm6)fBb{Gb@9XKKoS9|s+)t!n1-^kG|4avLX|Xxh6X-7d;!6ka~ zXs4?5Pn{+XJ9O^a`EnEPQKOwJY3tL`&TqynBO{C-4P*%qg=&`!e!?Xx7bTquBmS0t zENKtrMr1h>h^h0p_BBa5z3a6EGu7EgvjSmYKNpW+goW$1KHg*`gB{^_3qQ=hKa530 z6k!-~_K|*F>+BQMKN(Gg`e#UelSnkHzBQ=69CrxnzgLUsTB3Iw18dg$=VP1++>Sec z4B}nuyT%|V9@FoQaV8>`E5A1g`PF-Y`Gp;y!>@CeN-c4Z;PcFLUnC2V zcsldsBL_DG7MEV&w!$UtwQ=wjy7Q>U z>7f&W%ATY5uoMw`;MPC!6-}7n{9Ni^G{G5D9@I~hf)7UCRZZUvK)Q?%2VWvd0kKv> zU!qSWWp&O4*44VD&e#gO>}m_Jo?2%XG$S*)0P>4HM6m`bRrt(&HBEH z&WwuJW3@p5j%?NUu>AZo1=~4$BK3c;opTIDuiwt8OZwJ%pWH!)phgz$Y;L%pHJ6Gr z#l-z^J7;G3%~@{Y{Io+r?={IO!)E)@B&QZd^mp>HSTCC7><07~CyAV1IN2E&`UY;+ z_43QNTCq)zoa`hgpjAlUZr;oX(1fTJJ%GRB2Y*>Fo$TxlRGa{%4)>8z{p-mP)*3yn zo^i8@TJI#rwBf6*kI2nJu>1n?=^%8?Wh{7ISn`&8o4%mlITUT~)q3aq#6Q(-?^KWW zt9{S7yzA`m2KCI<=Tx~}pRhg3Qlmb9duO{M@e{bUw#RBp+WYDIx2F@0`s3{l$yZHr zmQ-%gf0m4H!7Q_)MRLwT+H_BWb8OV_OhL!ILrfK<@1}pjfHP7 z<*ohFm%437XM%oa2bSYs^jABWRsdVA582VFw;%sfH|)ryp07>uHKzFP(vkOfbY@CF z>!v$97d_~oWEv-nkujV+-I8ch`8B5*G6z{P1)6{toTNh#=($Y7f3(d)j45eTWP*@n8TFbLlHOp^+xzB z-}IBCUAO6XcXf8RCh7Nfbw&(-PR>6?B8aF+4S%fs$>zD9y_@q&&Fxv8dN|_VQJU`t zqop5o>vny>?#^iLT|0SqXa8MSWR-r>luqO3wTS!o()>>?J2!o!xMEvC9FR4(w?a=l zyr@FQ_HZ7BeAevYOqjrh$oSNR-*bs5+bnV6rrM%pnP|7FX^_0i?Q5m7@ zEwkmo3VBR3P!pQG;D_QhHZjOU!GG6D=AjBu?`$PTowp6IIH`)f@8_kc@fW@kzTSA3#Sy^2+N!C&R`CpEZkVRY~ zy=BRXnF-OFd%5MrGzns{mUp)0x<79{oo|!}M9f85n52jeNe*4yYmpPi2z>g*vDx%B z0b&YDx5?N_Xmff_XRg2f11 zjNG-gb#Q{}@J^=Gzbd#T{2r>BtG2cV-9OtD4DRjgm`{^>*DnqW6;75J9d}6cdlkrO zkSW=n^&{g0HF5^BQE1Cdo-B}^gnt-1mHux7ZE*L&=xKx)7jwhQ=Gc+;v-=}$1O9V4 zPi7914>?c)KZvNzJ%0pG8p5E% z<|mD#^TI7?(+bW;A&|(u`M8Npf{LC{fAS4c%XV}Of^{}%UeWid( zEdnygYb}0M(OkR^KP$80r$WNgfTRGCPX^wKQ0XOrnUd@acvpu>l0%*$Ya=MuCtd2J zP>U2YH@x|yljV!FGxzkh(!6 zkdze2-ipn7x=&$B&Mpi|0%>L>Y&SzAQW{Gqh~M^GHco1bU=o?&Lbrw@NpXKPfORgU zk=wH9frRClq+D_yP;3^t_=~G4g|!E_X2Cb*bW4rpUz#P#27*!$>%pqFT{8 zd1D1{WY&D~S7*;he$WBvWInSn(LUD}*&N=ChI}>L>0HW4m6Tkb7VXR~!!PNq$RQt2 zS5W77rb_e_>>;C9CSCW95c!amKXCS>!-3CC4}AA62Of+&R+JzSTV~6Sck_IQF1k>ljrzKf+59!sDaveG z7pmjh`?Nl_8KhIT1u2yc%EurMhj4&(=>bmm2S|@Y%GnCxSXg~rx)wFgVN6FnKo#!x z@LeTWsiAkV*<6G3$0QxI<(@#C03t(8tPP$SM^q7aLns%-g)FBZm=VIhc;%~uc@ynU zaz_hC2*gkqYti?Xe+AuuU_(E3_9sF`9PZ4M9eVf8Xy7Kcs%qek$loe|ZB&z4fm)R{ zMF?U+Cq|@nWQ{BdP8s=}6DJA_mJy0F!~SKUvat*h4KSp9@$*m(E#EL0R2m9m=ip{| zE2P!zSKr|sQ6RpYC9zY!ToL3hGgRT$luMUNYbxlHo2c&ud4JNujedHz zQ?~17sMuhcexqFb{_#(;9O(_df83{yWtJX&uv35Rdb4T3hQJ2X8_}=?gdE2382lNW z*ysz3yko+d!F)J8;tL6PpwnQuYKuN62HIeiBTI&{M!PE`uBMUEP)sgwo^GwZ7` z&L9iHdr_$I$ahdFx}3oJKN$*zGIeAY#6MG0RA8ly>X&DZ38d{EVL&IkJ>l#WiboA% z3PxjEc3+A@)40!u!pgfLR{kLpQmQQ$irJZuG^a8wJW5XOjbSsPE)rr;h=6T}@+9ZT zPlmx_xYtzrYIg#9%lBtqNGw??3V8xj@e?okWsUA0!LOP%EAX!@qO-geDG60G=OjMC zyu{Q&$J?)FglOS%Yk|1}nZ}}CO6*g1edG#9VNG-M<@AAsoDD-a9kKjW*29RscWGQ+ zGhCKSQq93otu5FGtM#-(SqQ@kwb&Fnbw55L2c5=Rkeiln^WV;`_0GU7#o{yvJ|5ov0Mhj9?9G~ ztUaOTxp>wYLq3BrVRVwDOv%uKAtjaJcD%*DmVxD(fdSNvEY}|yv1JFDYh`3;W;p)H z6gnS)R}g{ScoY4J(aZ&y}ow925BeLmy*Fovm&ySP502D2ieG4ftUt_qK!H3sBv2feK@js+N5t>rEfcHPPY%Ox1b_wZWo zmGV_~;4HRs_=>~mR;WZy4T~IrThj0+k$q3&Q!n|kb5e@1Z+158?6>S%6HiIht+dCC z*}`+hybs+D(lv7>zzkbBzxNgLB;fL%P#ZrjcR~XU zbVQ1HDYI9KA@dIbjtin+RBmrTKnr;yg@d!6?AXyS|A~x{ThDzzgRqr;s}>)`i4Yq% z95Rh30~+G@dS1N1cMt%_?;#p3yKpCe0UF@^NNQ))Z7Olh1nag`_xEY7JykBHcW%hD^O%$TAx3is-WsQe|V2E6j|b zxIS_K#4h;H5rHi(Pb2PXqYGTSG-a$H(ev3M#B)ba&0XYPFT--*r2Z6ZMX2h_S+48- z+l~bTZK@VziKocyT8zl>UD}Jdj<|8Ms+d9y&oJQdeT`orARhJ&Jv0vKVS|JFIO*iC z)6;f|mgx(QaDHRGzwk(W`~T2mk94LNHCdJ|TkJ{W>C|l>7fmAs`nn^X`tk4QGdKPT zrNBkNWVr9aBLuRQXnVd-d&e2IquEfh$WDvOHdUP1 zn}VjA#H6P(ib_ByMuEnau9u!Psyt(@gjWlKMJT4b{*fU8H7m$0dWu`DjjOF7_q*8QPJrnQhM!Z zqHiD5$zz<6{Mz#vM*6ir_84b7`>`V3ySS)!*iP)=G6|eww?Paqb6q(nF6%sB^u!GaiI?t+5QIuivh zsgExyD@#eVFIsL?+~u#uk$#M7ricu)>T7~X0=Oa1rwcy#oa+CaOeQmVnqMR6$FLHX zxjMoUK@P&HXhqy3a)^c&SoC~x=eP?wfKwltDvK&w_j9{0jZrBd$XOGy;oguFcG%e4 znDTP_BsQ6ZU%-?jhAI0LA7!v$(8(`}FiDmtSIBycKOvg%`ci$$ZaF*J>!Ri|TYc(J zo!^&uIeS}PGHIJ&M&ZZ=Gk*P3r-w+R7mjsatrrc>UrjiC1K74Xk`OBK>D`fUN_6jW zPK6RJQ@?gdQG)%X4}b5}>WLr5>h;f$cP_Lx=>Fr`xI14L{>-^kzj2&XzIo|~QXfbC zTO0f3ypb#tQPg26ZEzd$D#@|^9FZ2VLvt;re*}_fS%|i}CT`CH>v52pE4+%UPTgJ` zM$ZG#K_oylqi+?0PIJK~el5XcoOw+N@j@>SsKvObm3w7`qCmB8UdRo%-7Lv1UVM)i zX!`u^;;$_i8M9Q6W=;8Pn2Z+zBsDQQQ+7n?@ZrH!Qbx4?H=~pm_GuH5F-JDbW^`?$p^or<4dE1eORNBP|8{ z7AQzY#9ox1W-dFX8uG4W}GB(!K)#eGgzuoU!U<+EsIndW@wY))Be(tF~I#dZL-`iOY%q4 zac_kq+fk+=xl(DZuDy`^Dc4#uFGPtPa3Plv^es;3_T)ae#rmuZ*>k>Eue#8gNl@&j z3!P)OC^C1xv%$Kz{jiIi-`Lh&`msx#arJjGhNK+pqtmuKPB@O^-i1ixavF4yyCndN z$eFPb7UQ$Js)_yW1^U-b&i6$6tSc(jPc%7aNT7musdI$?YP7!lQZ|4y?+diL%vn<0 zuso~B1J~)c%N+g72L;`5{KL)0zt?#aY^2w0*NLLY$Zp9zK-(tDXu7$SMKz z82rEpl-^tP&6hj7#QH%Qff^exC#du)J@yJ`Istu0T;c3lD&9dOfx)?@qm^}6zb*m55x%?%%u(%lic7u(I+-OP}8)9NPIeT0n&uMHV%SOKY~ z?^x(a9PcL!of*^6DShw=2C-7M*?>M;6Q%T+zFa%=MTj;#Bc?u13nbJKk!`#$+v=pA z(+u5Zp(O6DSgK!WCR*cty{VbN#((JXS30xz+ty%Zl;5V$E3ZVjlLoZ!O6QPqK?W01 zJ7wAJ&W72u>B-@Y17S8z%k@vMawd!la(~ovx00OgXrY{$AaR*BF4xyw<@~bh3h0#` zBc!5#ejF5tKn0UG>amNQn$nwwdUJ%ld4@yM;oPR{T)fCBw;_q^7C9%|9qV=JV&}k7 zq|A|o2D?UjMM(%#l0^IDSKE4>TI?*EbThG%Q>hJ2bRDihAwjwfxvfN?cBh{tv~;(vtMx*0&~EU zzWc9EOA@M}KW*I0Jal*TnJvzk30bY1{>hXM(RclDi*sQ4!!KW8*nKko10h#o;xYG0 zJz*)~+h6I_-<+BH@ug0ly~feM{~+G#KigY=p41;-8Wve{jgv>LLF+Zn3hPOI%ra*h zZ!THptm32fTB4lS=<}{6jwt-q{^GS*E&MEMCB$Y;`_8S-J~k&x=H1}zh4!%W2Iq(N z!|i(bjm|;(XE!>vKgsn5;33{j6TzD6-O$CRTY`M(`vgJ%w@!K;F&-m{PU_ zt%NLE_%LtP58mt?X|=bHyv2!IxgKgC`bG3WU4N_dhXO5yDF&Uk>pO3Cb{hQT^IM%A z2S3@l&1oU3Ndhg+VyU?IlqXlSOVXCQ(t$Rvk>yw<8}f%R_Ud; zJF`h5_2KQ#Qj$nreutxZ_u&e{vs!fh-yJ!HcK{!H*$QXJ5$05!(Sm}=Rva8`$N31=Sj?{YrkblNv}!TVR}KR!t_cj!^~ zIlC1&vY!LyI86J#zWQ#0z5N&Zo4cK}!tZ~-$N4Vg^3QviCi8k``|Op@Z!P;AM?ZS6 zGZi!r-0O_<-yP`@kr`nPUnJdw@d`_v$?!aB2W_ z>;uk0gy!Ay04EjhTlk>!J2ZsfKZud^jMfiQqFvwhpi@1VD&BaI`14J=`XSJ3hB{6k z^AHh!&*;SuIkR?YCaGR}I8FR8!|9KiuFAP*Gaw=LB*rc4*-Pz5=2v&UE_;~1uh;uM z>>N{#j*S{OS@J|;OL$yI#@{~j{>0onb?3v*(Z{V%r~Aa*v+5&WUfVHe* zX{^_;ta3(F4JrtC$m%HvX>mkcZdisO!VCetVIcWEF<}>SMc{>uc}Nk?~8|Hgm)_s@&iPVR@a8lcmLW&nk%NX-_z% z_B$`?S&uo@_Qjv-pYqAs;jHr)odzdY+sPz)^Y38Clee&O z5)6{Rs8erv%o%IH@}Vw#-1)6^=e)dywlr*>B6V4cE?qoh`uIUL*jqtkT z31_6e>2v)ej}rmdAo>yg0O%lz<588XfztW7p1B$*v~tX9biBXl1*@IDLF;^`Ui&1& z`HTMi$#8|w3c6X~P{q(75n0@T+to^gIv z9&{odBJB4?-~Eg;wmfJy{myS)-c{-1XPw;1L7V9U(vksh2C`Xv@pqqfE{sdx=$U@` zSwzYE`pajX?MNX%{yFF1agE|pK}N6%5;b(9!pvS0p-5O^>s?K5~0ih+yd*uEy(&w^!%*&-V`>9eH4MURadN zuL^9UVkx@<8=-J5k_OSZ)_ISQNiU;noUf03*@!FuSNpXuJ3HCDeEC&Wjq}^1uMrT- zA;D?AJVt>%v6lly9ACSz*YQg4#eIO2LH?Kx7bThQdwZPcnl02A)Mk=q{GJwqhNdS+fIqa@y0RhoxO|MZpR04qHsmi z*E7~T$>C=k<6#~b|A|p00Q7b1S(f+e*VYpZI$!U)f$;PD^|>4H+HKUUHh`G>_4^w* zZ_ufK)CU{5U;n<(nNs67EDn=4a|~k`HZt^LdmOPKQ*3*weyPWrj={OPYt+UrJ>(t0 zKA?a9j&makD#yKx>YLyG(|1vvrWl&Sr-0i_Tnhz+QQfl&J{S*D7tn&J`!ps=Csyh! z{^8W@eF5VSHY@_m-ruP(ep)|%SPXw;)CN0rj%{3p(%AL5agOEhPB3(3iLTygbkzwP z(M0C!qc=Lw^D+Btb@z6a8-bB91{4W@Mz<4nl8Hp?uELo->D?Z{jq*$bUuPNCcYX;c=>TZD!>|j z%7;$b@LW<^qNU>si}PACXzJZRbViqPnq}795T=2jOI8=ERP1aj0Lmn1p$H+N_#0?6ydHA7iG0V0sXOI>iTdat4%f9hm`& zz5j|ahi(@G*>Xt_8X^LQ5wDLND$FI$#;)6A2mU!`Tzp@a8<$*Xad(}*{v)Sx+MA4= z=XiDaC^Zp?(j#Uc{MEvx-OB>d(o~MSKtJ%YQ=Vv`c@7lAx~>+~A@QT)l?HQ{e(cl~ zhjqly64a@!V^b=|t`Z>M0yA-T7v5&3>LWj-y|^&OAF_QbbCdrGdRc1=?7mPU8N61# z<0sD2{H2~9^CPEQ7Ti`=t~-BGI$EbrtjdWE=R}Jt(aBGpy8W1BbfH5$1adLgeb43w zGkzaI8#poDew$skGPAEteoc01U^b{KyfK7h{8*w5->fhB)ERl8PXYc*-y4ihhl|+X zL=;WT_mzr|u7<^l;!9n+=a$`Bd^2803({l%xw*i?6TpC0U~a9BYJlL`KfLtXi4 zFa+^_0#z9YSER}~eo!d6=WvrUDKnxoS)~vbJPZnoi%-!chp=3-Yp@w~FsUJigVUKp zPwZbIc8sfV@5qc)^f=%Ef~2bQw259z(*0BXrzi5k-h@Q{U?iSl8e}J$F6J2bq-<_b zne`?Cy+N$7NB#{!WBQrB$*hRonw|3>e#kbB@fZ7xU^+h~&Q01kn=0ZNb|gY6X2Rzn0HkcGiP7IS zkbebZ*bs2JH85OcRepasz!;#8Rmfd`vgcT*FBx#EwlnKBNbptX4|8(3sEa{4>Y1!i z@+a%(2Ar`!ODFi^z{+qkIQpFn`9vTmq11F2YGK*J7#=rkwUxP7vmI-fxrhBDPxeQ%Cx7m20 zE|(0^2meG~VTlk{@3`Pp1E(9<$1q#ZP`CRHv{oN^%aS$9k%xuDV`Vw7ShW@cW0P+C zCk~K;{|!byo)1RG|A#OVG#+4N_|`E3ci$RD>}_J?$KR4TUX#3UqyIyw2pSJiF>LFo z7`Am(M7N2Gor?4FJ@P7fZ>;AuEB2GFd3$7u9{r6|xaVu;5xTs`sUPq1C(ok*fq-5i zzrI>+esTIv-u3&x%u(2=cY!IHO>-w_Ed=CbqpU(cWi`rD^B%ttM32E%MJ)`MU?T@d zVpzVWWO}{!OG%stgU~@S``@`%|3w#r=^)kVpq>V%EmxKX>;2d)9uxFAtU9Rcw#PWL zYD3gRt#Y;PiQJEbFv2H=1&anH92}dN+iOd zvT3R~_Y%rl*=5;X4a?3oa1HMk-K9~=s&rY*QRzNqM6gOCmF_Sa$Ow%f)=#v&CR?O| zA=uBPbD|&pf9>24w$V8}XPbAfV1%Wbt#pK9cW znjwKOQLpITqp^`QO)nZ`e6puxTsF2x=$LzAW`qG{XAySOCHm56tTeTa@@drH@V`cV z1C0vmn|;)a#J*I3lxL&W_W zId_CrQm+rmi&Z*N^eJDbWG>Sv!&)=ajL$UZdhY^c)MVqw4ZO34%aaaKq!}gCd~0za z&61Y!(OMZFt%6yneYBRpBB^iB;$Q6FrITWHna=aMdFKORC3}n6V+bV`G1@-O9KGde z(p6;)0c*$-awfdH*Ql`Ope}ATqsm6fg7Pm_*$Z=GJ6So|YQ?sc`#w?bB~kW&Lb(qb z53o@*L>~g>-efZ$TDgz?YmA6;uRC+dkM`}1on}$(EB{0I2pSLYQMh${m~$>e<0Jkp z@Nq!ix5meO(eM9L^bs^3;GeQwDm!GvC#p?Id-rp6e%)Dz}g8^sBwGx4oq& zmpDV2gZoOHA$=FgvDgJ5|7R*p=XY?Z2Y%`vp=6D31=Vci3U~4 zBU;am$h=XZNRmeU^#qJ~sIi?RZ`tMv80&evC!IU@nmU6;Cc+)O8?s<|`3h)bd8R}! z;4h5$b zGxWC98DcSEZ-{}c?cJSYZ63iz>jL4ln|e4ykL^mg4a>+3Kz_M^uk%?6d0mRd%T2QP zERT}%?rlWPfyfe`B6!C5gOc+8C8`1x)*sV=je-2hKNcUW&)LZt%o3ZslT**08`smh zn{Be8r*k0NWNck-D3i3@JLZ_Ata(sSUJDYy)KA5Q8cBY@$P~Mq+Xma(@U+-)V zgKVzD$&l`0QQSr11r_H44-l;%>`V`eooN3S({uWB3{TTrhR{t@H&*q(nC@4|=$~OvBp`J$-co_vvl)dD^IkW9i8u;fp zr>|9|yYAtPE*X?8;3Ob*s2I*Q(gjueoIRXQRzF>{hqHIDm*5wLHIASvLmNwk6r&F3| zUEi>1Kj*g?vLlZl%q&cA_~*gSwKl=a4rA3%*PrsquZf|S1Y9=bg{5?`fTgIjKn1Z_ zqvM%|OZH{ zN9Mfb$G*nAutbf>*b5GzHdZ1>R~Dq(5F8j3J~Yfp`Uy!T)`58qB7{qVh-{(6E8C5Xit&v?$Z4Uvz4#c+L;ih&XoeiS`|qsMg4ug?v8XgClKI$PKoGTy7!l}9&1yV6Q(rL--u~XE52f`j$UKC2ZAC{@L-%uSzRFh-3luj;L?) zxGX*J#ycI!)_Xii;cETjc<{%!`rGl&9!i`OK-tWGC042rIoc_-zgPM%M?2;AuS#Eh zw6nML`^BT3F4h#i?r4ant-9?o&MtPfs}G`1eZnzLT%dUNG2of4dd4x%S=7A!7^hqE zKaWuFeyr17V1LT7&aVFpM8vHq5LtPw8OOF`L5|<*ArnBx-|BNF7>LwPaD4kJSI?N> zlS&%ZcZ@T0WraU6%$S|8`^%~(D;&N-dPy^nW}@BFQ- zD6@@2?Ww3xAH6m~;^r+KK{>i7Oa~O+JnJ-jN#TBVli9+#)qzA0)p>GA2U5Ih$!+ zp}U{M39&?e_QSu2$#=&oLD>pW|FYznaKTg2?i}naD%`24vosanzNEq(;z(epgGma7%GPi(Fwf( zn*LB?n>tMtJy+DMOst%n6h7$hrtYi%?i|_SBgwW_WPbRrF}~NyvFY!Sb|34+1x{HB z3(}rMGK)}59GI)0{_a$#!sh0A#s#LTJ1<~qd?HoVhgD5aSG6w4(c!{$j>fW}v4v&f z5EETnZZvaRh+eoQ*HC-ZLUTR7oc&RhkZjR~iP6n0ScB#`jT;2!fEbn!fn&rLuQq!w(mzjbW zUFJN=Ivj9$(22{PJ=q@*@NrhjvU^K^AEflY!WrD5S$e;;Z7|w7Z9|BicZDf?UnWNr zLTR!HmKl~7o!k`U=$=Z~(cC7;(bOiaV`9qGQJ=}Nxj4x2QE`~#%OFRW>U15|VUCuf zFvls?rjFaH*?gbs*Q&WAE!VqVX;A0+S8}=fRCl<_*`qi>(GZQ|hv5Z%+Et+S75dSu zpskna@2=u%@Tso7+8GmOgaCsICF6%zo9+C=)#P8IcbnoINwJwzOrviEp~O@ZI%=v( zTQHTizgn-GYRdgQ)j6IH9es^+B95h=y~ddfVfB}3AdHvv9n+lM3Yq{oQmw#Ue{_n2 z+uvzU0aKG-10$kI?_A@I?fp6X#`uLZS>g6%BV=<5rr`f5N;S9DfaI4qyjYXF3uNoe!ckmDPbwmHVo&Dr{;XR-}>mLmt%ya%E-vReQCt2U!f7<=j^=U+`OezZ@V5piCce)uc8V-4#J~Qu^A*7-p5a`C)Gm9D{omX$;W=k7%i7p*_wzC} z8}*Mb(9qQl`z>+Kw^FzNT;o;RJ`CW(bL0N@y6SZ3oVdR^2#Iv`*>Qg{DiGw9&{=W+ z(YYoh#r=1(m7W^+zuuZj zJ0_QDrMTl(cMRhs6E6gUSw$`#)vt zI*3UM>N=1Kx-rwu1LD{~O;2U2aR0bJB}kN>?HBjgXRF>f?mrk*y-(c#EnD^8alav` zdat;De|EO_jQj5eRm-~ABknK%1<9#kD#peATQbcS`fL;fmJs5(g@ahmpCeuvqzy!r z>_%BOg#!T52c`_t@`e2Y-BJ@$g!(Z@%cX|GzBK-#S!PmKADq&DW>&a_ddL0mz6{$* zMGLFq{(@&rqEy%`?!Uh|(}|vOzh!Nvv7Oi&FJ-Ff5%=fSWzxE{)ZRATl_rvLf91U< zO#o9F_g{F^)G47v++P^vq`g#Ox48ez|RTb!Rr8QExcE;3;z0o7^8(={w(qxx1?2hc}%$7QvfWz@1yw@WKiQ z-LK)uDnMV|4*A|MUvW2`SWu8&;L<2TQx(eyn?BlZ$6rJ{&^Via_=I~ulh$8do z;(hM)C=Kh%qy=(OdjHRzeM)3x0-h#E_(KJtVohT#o0t8sTH0er>|+~6E%?fJBjYdK6F?@Cysq9Ma5)24y^Z9F{rHMmp8 zs#aQ=P&|{SnL(?*fIG%}!O9M?Qr-(*oY&>m*1?{7;X%cBO5C9b7fY78Ww9&ugs01^VDkP+bf3tWC~# z>s~$c8%Xne_4D8G7ICk3zvZRi-iE!tb@D9UA`jT?B-=EU#QR5Vd>6}*con>CGjBrk z(LQ8bHR?^9*;eK=S|6O(wZDGnJEsE?|M~t;xelhOk;{G;}vdlgm%jFP9aU;pWJl5gqv&O!1$^?RPq_)h!YIbFV+zjr#7nk5xw zF>y&5W@F=&o#C(6-L^OfNS?FAe>mS8xA6S3T0gSI*}ca>b5^PdklH()@dH=S;eX2e(=Cc_27}W|8H8^r>F|gR>-hf74a^rqxy_z2rxyQ|kJ4kQ7ne@FJ6M`){GG zPYAr~5~{`u2_d$hq-~NTO#CYQ1%%eGFd=Yj5uuy!HMM|SiwV`wGa(=>IUimQbtJZR zP1--^nl#`nIoIQeaQshP1Rv1X|KyCYr~j;9`N_GXa<)na+BpbQicewKBA^%ntY}F2 zX!T#c6`m_v{qy-+zv$&#ogVhJKkI*P1)a{;9e;K@j|r9fPYLU2$?UQh|CfFPs$tCb7IT z;_aA3pGTQ=%zXXNpPeVHx%$3eoU5(54SjwEk+YJ=Z^M`b4zjM??)1hl?nB#I4-YoH zx!sB4?Nq<@J6DSZvpT4KlMBR17601aU83Y>ive9(T$6&Z<=r(ar7p7`oV88OVE5c; zsjGP_7yZjgiRd{P^yKV0r~+$pspwd9?^R+zv{2q@@z8=d$T7Cs1NQBewi?p*Ukd71 zY}K6(ePyfNtwjyJql)$Tpza$}DZ2YmOjTM~ELf4JPAFc4c?)S2iH1quBYKphcA?tI zj+zz}NLE(LID+{Lf60?E7fvjonTOwoa-~Ww@SZ$Cbp`Jk^VWa-0HsH{s?%R)T#0?0 zsL`f2YF!*%a(OL5(mP05WTW~ecv9s5b9AqF$ek8i$j!Y`Kjx~^gM;F*@@g&Wm3q8I z=r)HHI62kN7HOTBcP+H{WTRyUHuf8HpZS;Gp*!TO-qsqud%oJUo7|ejp${Uo9Z_%P z$trZg$I_1BHSF$uC9kvZ=c|J{EJu}PpmmC|i;YbR-j!VMO+BhW9f&!hngZ2}!+v3b zn$BlcT(#GS#+53`5uT7&0_pp8dQx1S8sr+??M;!nl4vm|M{n2U_I5d501MTDgF{O! zNe~+I=ZCgfl10Pqdf+VzseW^|oaL{*LqAxkJ}EJ0b)G%eJa0<-7V7_ViZGjVza2kV zANF}kXZPM~5LQ6>LRaqIt%G1jU95Hy$JHx~)hHf}3)-kTyhY#NMvdc%`0F<6X5QT| z_teg$J?b&|dcDn4ukvZrP7UZf&8}2_%9z&vo1gkC2A0j)3h#l&gFKi<*sUE9r^w$+ z0Nb>NJ4#fUo&QwYT%&%zRGG)g!U}n_#r7X!T|IVqwY9akrhSK$Fg+sPT;Y%G@HT0& z);Vv@o&|2f5+njfS^A}tcgxI6Wj$5!Kq*HK50=3z<7jv^gm{|_DwjveAm4a&1xY!g zeeUCAtU}@@=ts*`Ioz^0%G3e-W?myr7yq}{NX!tVUn67sxC4ugNJcIlfSg1&7}5d6 zg>=A)XQ zJYe*;&E_y)wv{aXShR8ujsA@U|s9s?y907J376(yV@1RR@j5$%k_jF zs!wieZ4WglJ+6mD4jZ}j|8`tq{{OGXHNWQ{j%!;_)iHM@U3;m{aDPYjQiHrZa-8RV z7V#G8OM9skAU!^kJhS!Bz0}blzdu!}B>qV+s8Vymh)d3~JLvwsRhvB)0{qZ!oLoL) z-t`bm2;g7>7Hd{$=z*#|BK?SX8dU)sHsQu^j@fQ1dPQro3`|O4Niu`>^5pQqdPA;2 z#)HDk-2v(8|0IyHjY1-jfe8xvkrl`o{jUNUS)><~IwBX@<%g0O@f!2S1uA)(=rV5Gs0Q2sFd?`@bEcEU}#P-B8u{pw%+B%>{C2wBwKOYbR5A zGKcl~#_%*o3Pb~wv%?_KWGA7E++?}f94>5Su_MBIi0U(UQQcF6paqQ4iyp(H34Zgb z65~E4@;0!OU?8r=Sr2i+uVtVnk}x%8Oib|`k2ltETij$Pc0wgo(t8ZIKawyt8p9KO z=Sw`4{91_@^f)YKujjWv9h+n{DmK2sQZ+cJvqN(iQ%K>hHz#HrX5frvlyn~P+_cOX zrtWQ@H{6!nR-znmOBC1MCXss(g|I>~*RqO5LrvpS{6tsbF?g$P>Yx^N!gtZx!$4mXnp;|r?wgftsvzO8}R!l-0HXOF*|voSK?YjNSl5<;xQ_P5v> zz~P244bJu1pI3^f8!XOaGF}#Mi^u(b=lw^__GXnL{G5D97`;1&qrQNw|tWX+ip3(@KR&^A*xlP2V% z%aoBVWj_~9R(NY#&D>084jTbs7JGiwxBlk6(<+NtK+I&Qi6)Ke!}KU(r|-7P2UPsn z`IYUt7B*TNF-S*kBokYN&fHFWK598g4MRw zjD;!z8{_wkX(z#(MWUt-I*>^eM}UHZnt7QN^8|^V#wBX*XJNh>IRtCwvK|rVi}N%o z7HxPlX^F}sCld~g-UDRVc?=dJeGC+#1E)Z=8+4pwb}jh^T3eviup0@m+0e$b(NZ-@ z-c==PGe$!qQobGjgNTb``}=)UtQs80P@%@GNvYykA^+rw>IW@v3uwd|*rH}dF*f6- zB5ZIvCI4-MHLL=2EnV#Fm&cR2f_Y0)=YzfIYpWgw}60IFWjgNL1RvS|fzy<7Sj9@)^4L`e?pC9sc;V0y$)UN0~-}TN! z?q=*L8*iL}gCR^civTaDDaP*QwwDik1vv*8tXATprowVucIa>wT#`_M~fIRDTuQ94JRpd2`q@F+og;*y^ z4lzB}Ke8t9sVjcLSYECRg7Uy-VTTBd?Ipj zV*)YQ>C%e=>=E-7^ozw{JJALxh2drigDq~F4*ZSqYY*Y->aWf8MvH!G0SKvHVh{LZ+9U*B?zmkRtq z=6jz?%FH~>WeGNzjt6^lXRDRGK^gBelQc7(RA+EV4M6BY1~Ph|n+$Wp3~WxvNM!Il zK?WAKKbMy)zrd5hW35~Ar8uSp_Vq|!RD*c^`RUC3uFl8%n#sE$-6#(mraxp0`?DyW zw76A&$QbtLxpao5IaN!y=dWh`()ePBXU^Aa(V*&JB02uQFwHJY7Y%o1SV+n&bxc;a zg;eAJf>XTo1~q_d{Da1r&l?wV*~9p>l#y0wf1tJe-#613G~>S(`8Vw}Wk&sig|%o| zJ_D^aVTItsJQlr&^D5As5xztZ|Ervi1|ANMlag} z^pX-!lG5eq#Ly{-0TeNfIWb1U)nA&85&W?@Iy`Fj6T2orV!|S%s`SjaUhcu1#1j>A zg2d$3CvU2$m~bZ8c?P)#)K0ByTr{#V+JSH({vG^{aok|_DLfunT-mYFD#uSitnx-{ z{ERYM*PIQpcsw47#d&(L$4Z-q6C+4%0awEoEWEM%aMeSgS@1X@n!`*$y@#PXFgv2E zCS|6Q1%lWuZ+Nr^nZkvHkoaI_{V;M4JjAgO_-A@dSEJc(CY6j7S8jr0WbEe3iIW$V z>9X3P%hH`X6#!+X0++NE^3$9Ya}?{kEvJAWS%EqZT+{2!!}>m}QaUJze4tg$g2(PE zR~U{zI$C9Kiv_5DYbquPPwsU~#W1(+8zfN$c=*ljiP1>@h003{l$(&4I+IKs5OOx< z-m#+Di&8+rNaai+c-O2ngBVPW4Rj?n(5jW*nO@c8B{^_#(uymSp#HFJc9&_+f6)9` zsMs6Z(9$yV@Kt6O$78krmRB(SsYW4kKJduRA4l3Ml_L<>#7=y(a%w|N)Z9<*=F>x|3L!J0PA<7caEPvFvpDlbAzbzX4Oz|5!G9A6h{0yzVv>1dic2Wkx zY&H?_^fNJAO$^MaOiYbRFKwBfM3P0+V$m_a0ms33E^21(faD0GkYUREou7K0z@xQPQvOERJSVKv!gR~&qPq+jbQUlB(CjxhG z?;F|k+_ntq%U!zCF&DyW&JZb)05uro5S%FNt7N|SZTchvGX$BE5%Emu@Fu}>6+sJe zdnJPw2kLSGH1lbQ34BK|N;J|yDT!3RxB0)X%wR3BRZtlWnWUhkj87(7HgqJ0O}Q}0 zCKqri=>1?5%Z5u#6f`4nrCB^hvvN%Aza2o041hZ?%TX!|s`k3dy5`-?RQR2MP!_K2 zLuS1uud+wF+GqfmXt0fi9VnRA;69-@QxgCa)HsG(ftGkHlnO2q-&VoBSr#q^jfI+a z%$aBSX`c5T83$U8*aYTTnVvlt6@=J7vdk=&Jii%#3x1Wz%(BacCm!Nk)w7<9Nl?t{= z?xtWdkqPD@FQR}(U^%OtnJmFx8S z%qH}g6YT}y2ENZRf5JLj3ZC{GN$P~VYgm)!zKMBq!@7VZ>W!nWoyhVLStv&9##aI* zwv}sy z1Yn&7mD3I3f3jd;@RG2_ofsaZ!c9w;g#-!6DOi}uO1DW-*mQHDlI*5RSS8^Ckl}1( z^pY}Pmi}^jTMG+61NItuOtHZj%LGVkNuHCNJTpv=Hc2u{^V{URKGU^vDPc89+;bGg z`K$~Hb)WPCTtpuo!PCUc5Vj%}f#F{R?)lkla22JiQn;QH1>=NuQ(#6Tz!7e){~S=0 zeFzWO`^nrIWo}c3!^@B0UIJIp*ad#+S9$a1_La0Me8l|{Rs}Z3LbKPLCe*{d1px|U zBsddq`@&b;a33;dcB}l}kHW$*W`;#JYI`OGN|9y^&KAM+rP5V6jlT52SKiN=obuo% zIfPvl90D+LjY01Z+1>pk^&oKH7SBioA%el?^JTZepz!aL$_iRlCg%keq?8h(2urlL^{0 zj3}{}%xVfsCYjcH%E~-16i~pm@PFD)*NZY;Pfdm@L*6m{X2>-b52ES00@MZpNMYC+ z{&dCU;^=F7HAnRGOKqVPp7_%6x7SmRU){ zeH4VZSO&y*sZ}?HIFAWPQ7`VB_fGKHsi9k6QP>ysvHZroAX5-{5h#l(NB4y4KCf9(Kr0+|K1RY1QL zhVI+fp#p$pPG*Mv)`n_|2FX)TC z+bj*kw#}~O6o$v4aRh+1uFq|DaC+^9vD29-N%Mw42`!iP7v-nzugPG)Dtn&GwNcPB z_aj*@nKdEJQhJT-aJhG{F_nRfEk7n^QpZJ7f;B}TGksQYWso8*$m|B_fD1aMp}qt; zc)#S-or}k43IFhJWrv_WLmy&jh)P^4Ps%b1khp7zF7sd^_#$PG(F9%r(hitUKPRBZ6+iKn46{D!B8W~PBQGw>c%}oBqf;F z9cTsKD=EH=yMZ`YmOgN%IU@9BI^QC5CzoI8v0OwABc8u7+dW~;4YZo^j2m3?)`hfH^i(5u|Fv3LWpa40Kt9t`FZ92^!e}tyg{)!RP9*r7Ok6h^HXi9y#4n6r~Qis)1pwSzC5 z0L~TR>;Q7}xKJBSrd70LVxq~SaAJ6O=@R5kC5Yo5%LOEw>=u?MD&dRv8%F|Cft8Xx zGg_JS!B)Oi*&giFK2f>1Or02Zl`Wt&A4U3Z9q{)6j8E)m*GmSx~CcJq_ngE`DgbPsG}P(zv7 zs+o+;pWGj$?y|5)5rNr6fI=1GM_fpAb3Yaq47gLVtT+W^VS`YeM5?`HZ_gcx=Z}Qh zAH35U`8{;>9Tq#b{sno<{i9Y!&bLN}4at@7p=gPqhRlx}Y}>+0#q7|E7#zYyk!&9^ z$837VDrSh47CBheljP8p=S0XpV`I%?8zwQJz^kSpU-PD9HX&Y~sYM!hrc{ z&IuVVn+15tOOoFdpvg4BCKwwQ8J%IMB91OkTy%M!yI(9dHbB0h*>(o;@nGC;lGiPb zg{?#Z(@@!iTy11N0Ck&y)GUx4C+==Zv=VL7A%joTIXp9VdEb;AVMq`fi<=Mf|0yx$ z3bm$8=fRQkCf_^Sq^Q+Sld%W{fDMPi!^xAH3(ZBsFvd&=q{-q$+pv2eb&1Gw(Pqm2 zfcPF}&t#@}>2P!EP7w?$mye;LJ0>D>i}1uL1!BYUI~F*okh3A;UG2z?x`2)c6fOK1 zne2cUXp-U01YU3=!sOx2MTx%%nZI-mVbLP1${e#?bZsu%T&t%DeZk2Z@xIHW8FqUh zAcEljQK~{1$#N$S+^^L7$K^>e_iQJulZvZJfOp%L#f zITWj){Xr)3M-2YRk~EPK?+KDI1)nSc9Q?>JI+P_Igm)}0nFAIDW4&U6I)6;Y_>gIV zfl}O5hVdZ_NI|!UdN$2zAh>o{0S92l|~8)nzzxKKvx4R^QXNouu~5ZxrYt zSofaZcoM?Mb9C2}RRxJ7PgbK5*}m{(wG(bJ?>JfYD12PVn;NJ9$-Uxab=rUrL?{}! z$Aw9o;|!e3iv_({!)8P9%Ue%;aHA#B|4g5F3gTZI?!W#Nl^NHJQyJF={nV*y48z-U zD#N>7Pdkm<^j&)XX{sN>k?)+Q1|l_>ce;Mu8LBJd&m$0E zNi+@90G)BB^jklhDR}n3D^r$RrnA-)RS^a6ov6+)ZrzIBX_6X2pN^ZPI->1x;Uv{# z#~cq#QaxM6O@B&%JW2I!6<7C^E2ZnK~f||Q1`aug8JMqA=VWF8 zjGa3J-=2e1yVirxRoBqk)90!^nau6ys-wHShK(>o;2Np?1v>!YZ|AX6pVs%Br-l}@ zx}qZEkN^kLxee#3`~G+~@0zSG#XDlf`KqGq&L~C}<*pSIWaM!KL+g3>>w`$_5+t(g zP22UkZPz{3F!g+OYP9|5h=j;pXEg1~4JPVs(7*j1S>1K|pbONcZu+HKH(h|F?gnjL zs0J1VkGFK!o#jVzWM>XI#Xjy+8Y6HOyT+3$y-t-tBs~i&eL-+qiBr z&O9{ogfGO-a|D?P$zXf4^`#f9;rM5L;$qdSDO?OL03`lO2DC2S_ zSpJ=>jaCa}J4j#H#3Y#qQ^-`yzwS{y9~Fs6f=R{cSVZ)+qCl3^&5yK7^(57>96CyB z3);`vQ%#rcD5-8-xPu(ZmOrmKNY=Gis8aW?>v-O3>#f(15X!IlolX@}RNwSlrw*qy z{6sK6Q_~2kDLg!==sBs#&7_W%R8Cf7l7i&hnn;$qf+6fH$!v{4xS4Lq2;0!`-aHby z2Jd?h#`TGnW+)WY!-UGAeGkCV*N)qjt3iNqXR7ZAPJY3xQvZe}y zOpoin)v8n3y9G%Eqb4JgmXBpPlL!IuxIVU8_3CeYt|g0$^X)wO$BOVANF$;``ZikN zS?#BMxZV2EYPEmq(~j3fr$k-I7Ahv6Lll!%ZLU-khK3zsj`EEbk)(w}Eb=k{ljtp1 zik@YBuZJb?x>9xO3Uh;lx_1=0TE3=AS7tLG!o|Bmzk4OyW3~SAN;R1yjHcG=DP$*a_4ls0=^4HkJv zkG~qU{DGc#wd%tTeCuje>dZtE1b5p1xLS2dY8k1>gT}mdVP9))nN2zwfJ^t8qK@kx zf&eLadKO^LKo|w?!80(J@fM~Znxc9#n$=Ud+pX3mQ#o1R(Ys7lLpt6SPInV;K89yZ zn;e(#>*}ehqoAtWrgH49)E%d(oAS28&-+j>m@GHwEEf?wkTFsJT$&S{g@btTtq{E64jGpBiQYDib7X_wy+$1d9W97ZS<+ZA;-w&>I zBEQub-EfSw7mQIZPcp`~*Qq@_Hqe3ok?A<*j?qQH4>#B>`?Ig(j19($yizdMT429Q ze|()P7ij!^o$68&3;s8Ogf&qNgDn;OA2VE3Ea7AN*i4csJ9#pdv z>;==53-ZcNm!5k)lm3Z*_IkCq{e!Ec(^<5u^#J*-(??H-aBJ4*PB9DPvFWNp@~jN< z{4AeOb&ng=@kI~8rZsOEW|*}%sNS8!VG2h~7W`u2HW=2b?EiOeP~FD8$gCn3mCn&< za)8pJ8`3DK^7-e!%&#)WS&LuCIde&or&nzBOSYLXhN~Y(#b;@Hm=FWF41dX() zn{QNI?Aw(7{YFTloAtn()Y)w7J8m-DdgV<@EK7H(Reid!=2+vsqg&0!WU|zf~`(Rb^#&v1bj1&q3yk3K{6-P!z~E)k4UA zqmP^c_4K@+G6Oo{dHwwiRoDK`Xl1^f_b#}ZKcMf&^_@4XvYxlf>wkgwI}8p(F}QL^ z4!mG0kr{U7ruLft@MhIB`B|Z`JBKFod12v=W{}|s$(=4huO13L-EUFd?d=76-&<7g zT>_c6{P4CNi3c5+j|g%BFo=4bD|o&pC@g`;r6X~$%zd|j7+2^uw?K{Gs?|(&Ii%xM zWQ$TJKcGPXTk=zwmmumgl>`xBJ2_n>P>?7Tk&K9UI}D?6Y&7OQEvzMK@tX4=KHQ8D zK?}r_BD8K=;6a&6qd|p`fhR#(D1@_h&sk8tZ|XnIQoUm`HeEeS?M=jMv(U0!slT12 zs=%QgXRH1Gv?+k6MXfTw^bo9#1c>>|WlR?^A1p|67O#0E z%cKFAMYC03_ZI*Mh4+;Ha<&@N_Zwt#(-)~Q-|IP1u95XH(_N|Sw9+@Qf zTmQ|-0ToHxNT1KqmutZOh<-?`(cZV%RUT+PX9qLwzo>uG3~jAW)TzNEJ~yji4r1`^ zP8t>Nte0M20;kFTPV$uK`pvR3k$en$d|jQYD1Rzns@`bJ!wlZqqe9;12RHvEb!?Wk zdUGA>U#oTdTh%14{Ha@2Z}*hR3C~09{Bb7;0!TcB(S<|@ajp>@;ifCM{H_57ERSCO zWf{^UO(9ey6t%HlaeX4KpeD*qF(48X%0;&w(7BtnR9okD>4gnodD^Ap$Y^xKgYTr` z4n_HPeykuK;h#Jws^Ss1u&}_2n^ys8f;TmxqZ{FSHqJ&_D<*GMX>ajugfAe3Ffq%F zvH-lg2ogAPn7@Ah0YdMaj&e#;%l<-k_}{W@)OKOgs40ynW}Hx9^Q&{=|$l^gUxJdFE}9&{p~ZC&ojEi=!5l{V%JPm`9bneTe@+X&FW(IZ-3?|CX_^gtlb z?B*y>(qW|rCd2IKB&!*B%3dtlgeT4Y6sQP~mFZ$IhKFt#gUck$paaI>QYj$BpX3(A zkZ1gGy$9(x&=QZPOc_ZW-}J9aB;7zE0N0W&W+5?(5#ID{wZILH`J1VPmBzB-ncR4W z=kIjeD3?IC$}*hK7LNH-g1Dj}uHM9LGg@lN;vf$7okaLk4y-UI%07vpA`!jVVkHTn zhY?E@ri_kHtbb$?wwwf8q(isT zt=CEGLcZJC*6p#935_2GCi4S?Xv`u(@FN`&Y<>14 z9ZIF)y-#Vj&^oS-0!@1MXT{;mbsvcrP2bYjF%>v!l_N!jgrzx+nX?S<~v#3SY&iOB9#%r zJ<^&;w@7OYSSX?ha+Ccd5d)|YLvj~|UqM~>VPGo|ZV7>HIujt{wUUX8j(8S_?bt<2`>Xrxkrc;|5W#$<`xUcK2jwJ}z1u}nw}7R%&2 zJ=SXFO51f5VGZU0wn#WGqB4c-R6SY3BVeBVWYx-vir7(1m?N?Xkv_`wCz0MxJ%awo zYiT;qT+^q;(x)Qn%feW&n-;a|(^9Ef-doLXN~KRi=~H$J8YNQ`nOGXM{#gk8CawWs z97$YDbSv=J!(dH=-wdxs+Ayd4xDOy7s$)iUUdlGh2sg`SwNf%8F?orQQEbi`X;pfp z{eqFoWjLMZZZlG()x(i)4M)1I)kx7f0N==cwiWb)fg0%7qE?dW5O4VMo5y67JEZ8K zph46@GT}uuZ&B9m<(d0fdy*81bvac0R}3R`iuz~)gn>VU)_Qg;Jlim z6S-8=DI{~jHFW288O+8&)!9%e%_3$_B`vBlkDdwRLcqt9WuWt0U|@+Y$(JKCDI%>w zhvM+KNDv63`GPonf&AoTfVCn#5*R4?Oa~tbR1x+NlTtEBB+r%!aO7z@cmp*ZH2TfR z!eHhC-i$fZ8POKAwyPnBC?<0ylE=YM(VN&NLwlW0Y*qpOK>(RuAhuNG3Lpg#Y0j+T z6Vj{rlwcLthGdrHxtFT2GXoi7*2huzQuuvaXlL>XBW{tZTx# zr2&Guew+2lq)!%OYO-{Ye+!N-SYt$q03_2Uk#cd7z9Ye$>HGL}-zNlpZw~t|c?4mi z*3&}gi}+b(H-}wa->R#drS8p?YmLSoF^6OPXF1;e6qjW<7K|aeYmqB9*9o%^YFH<^ z*NG_-4zClKLa3H?D=Sv|9<1N3^bT2VAUh{mwj@D+^LB0S6M3fTG|eM72)VzkUSp`c z%-ymmCcL^}yUF4e8nrRlYfWbBa=Y_V55OK=9>gN_2_yp?4;0Chi|38HPU|@e-t}P9 z(Df|pKQxW{j}VZXTe2%V07Ak5-}W9fLt7|tPZ{JtU&P%R;R0i55_}hCe8^;6DjCh5 zTO8(luGIuJ3dkEN()zG4NW;ugAlHx=-x~{ubk*`ZA=6`S?+57v?=AL{Yd3^e3lSCe zmxlqaf-nc@4c0GGdnbBO-% zi;}+NocuXv2Y-p{rB7Qu<$KPPRu}tcT#kKNV!{KSvARqA3tyH5@y}T0sf=T{H&y)J zJljdxS8#Q25_u~wk#ZS8l!|Q)HEtVq2Jag5dkn!wnt9*1y=ewt^pErd_<`7#F$yMh z1$TplAS)rKr8>E&SDJ^&m{)^cGefAR%Y-5o%umGYLMt5ga`TWsg_&~FC%N{~pKF!r zAs9?*=3tOkkotooB|YIiM^>dbg&q&I_Dx2(`y;q8%sxTTdlfc&^4JvgL(ZTNmXfVlYKSa+%9J?``adyKd$_VFa%;8K7Lm)(_I!+$w}8yo=1Oa3BCttjn7%gh`d`M7cgTUhR)=eM`G!~ z&L5rvh-Gbae2*G_qigg5Cwjj3`!Wv)R%x2f5djv|VZ>8LDI z)6uY>!b?Z!V}+`u#s8F91|{HzkCE;SJP`IDCyaComWIz~G3=Eeew^ zV{o~PiZb7_53e(RHWKje05v^IDDR6x@It^&X2n&ZjO{(o+8bz9B~uRlgagHjhTGLY z(#JzToRlMyCV>ZqN@@{il%f?R3&ph|)y9$Ol+Q4XSu_7TwgW6EQ)zhpFt22Oy_Q00 zfX6)=k}WALKDP+s7=d7THNUDj`aU zs>()*p>cW}9g8$7c64nT74jYdRkk7qFHFnXKI}`oLXATq%G>e1=_i$fOpS^7q)HZr z+$Kc(hOr7CnGG#W*UETtNRSmqVB*?i^{w})&MBE78H)7ngd!`X!P~t?K5+Enqa3?U3hV5&ZvjX4EU{eF&B2HSi#05#E7q02j>EPVj5!IFEO?R^$ z$mw!r0ck8OnSvsOV%gYj69wskxC0}4=X+I$RBrc$g<+1Agjfwh`{9CQ%-jc%DUhMY zB$l@wrZA5O4ys0Q*0jLtm3jTIA)f(!-~R^kqH58u%F;{AW0fh!J<1BePP{zvB72<+ zc8BE^#{9nqJc|f95CG5k1jX#f%vrMgV|s0QtndJk65LudsE!sg( zn6L6vZ9xnozF)+SE#^sVVxnz&Tp|T8rdfzYAPQ4Z_Ch#QU@B^)ma!PV)iIIl{FG1$16=r)G2A}i|52<7A?RmQTj_7dx z#+>M)Ft9!odB>g6$MxRL(ZLc^+rt?WTWwp8{%%ODeM-~;<-#P-L*73M2_aiRQ%0ww z(A&reee{bMv$>f;^cfNYB9a9$JSxR00HL(eBIg!Ma(dT7z5Bc^@teCDiCZnBEiNjW z5K`VIc#IIWXawl>yX6I)2x{3XwN#t-yiXwh!{hKiuFptuddKm>E`#Vy@T-=^mFHDM zkkU{_F&%v+maqud$dj^kfyZU}faAhm3ml=T9Vn@x_5Y)1Z<)wZiWKqW@JP> zq7b*GDUgA-jLamH)1C6ZxIHr!sq_1Q2EXOZ8zQ=j>?I#N8iKaE)6Ed53B zDSF0)h6bNwgU?mL=YinU^o-iG>-=neg1tm7i(LkJgufLej|V@ib}xNh79Rh}Z934T zl7Z`K&#Eu1*Bj1x4iyLMaedqKs!PQak`>%&%v{Lw5xpY87Tyzj_4DW-JfYjXpgNHc zD%I1DAYvMo5;76oRkYsY1$AEgK+eO#Z-kI)LP*y&y!wI~Vh>zQk3*9XT%9w4!k$tS zx3$Jh6yc?J>Z%vj?ua9u@gi&eHeLTB5{|d%S6@_r=F{~h^<@0_yoVu$Sd!jt`jeN) zO#I7gO1Iy6X$zvo*jccgWBvk`uPpdm^ouX6hXDA_uc(fC>N3?a{rjn$-(TkZ?nuTU z&!BYHOEOU%^+P$oKg#)C^r~`GYjX$HlnLMhDHwE1CW>%#CQxYBjOd}>l>@Z;OhB9# z8=!5?6d}Ah6ToqjNk71^$&?|YIuj6C6jMQ6CW>%vCQyj`4>2gwG#?p2jU z(9V91nYdL~y~eh^T~B%qt&v-F?Q80!GJ{&+w+iqD(%=%*Y+XIQQB~+3jj9Y8q0x<| zlH(gqB~!79P|#@bBg<-zZj#hn8oqA?;%#;qyDonNBlNH7UEVsI}jH`D-o(=2_% z8|uT8)yzx)M^$y1 z0Dh{nffRnKvLRemWdof4*#M5J=45KXPgQ*;fS0OynE+0z=4S%DD=f$aaHX;^6Tp+o zq9AY-1ILrf;!Khg_n*rI5HMPr31IhWStcNMN;3f)KU|prEY+q5^mEJA8hibQhMQL? z+mau%SE`t0FZ-fl&RfWn7OwvSZYp}94Dw;Ux`|8UlrQwQCMIUy7rNgnbp{95tW}_y z#~WT;r4ACm9&O%6p!f-W;@fIGpJ(1yuLq$ndhQxDqMp!Q-%%5S&tU!VJF2fTsHxJ@ zt5(CuDSA&8>z21wiLQ88wMiG<_g!^F@OwY}SSgR+v@=fc`kv}q%zHk%&_>`;kkP5{ zAyba|_^E5vT$WL<_th~7yJ zLylCBPkkmj*sJsF_3@vm4!bVgA%1Zt9N>+x_|i;tP`t5TKl};GM6>i;pJ2-WHl4Q) z0oRxH@O2zH%k**U)WGtz@Ixan5KZ!2(S*UrS45scZ~j7c)vv5m?U8VOf1Rpo*R(_Z zYwLCUX7=VwdQ`I--6<^v!IO%4s%zx0poLp?Z8NCjR{eG})cEgtI{v8|pK2?2^=9wSd?r<#!i5JNG9Ollk>&uHJM3wive*9B)IL+mKhIRBXyFvcEdz*|L z4TOcHX`;B0l+ygspQ+m@wdFH)Ea>de&ygs-O>6nQq(Au_g}>W$r}db}zfBKY4=0l; zU$6FQBTwSN!~`eK_v_WbK!Xc5=!?4V1~uxBAac zWxC%NOw=-caPT?r3)Nj2{jH>>zx_gW(@%e)ib-!I0hGA$3+`@>dcc>eV%M?ItCqYW zB#hPefYKGhz+6#o&AFoVjtU)|xuWQmU#d^+1#xrf(4V%bD)b8qzEYh#8P9il_So#d z(BTOYklp<&Rnhm#FL0BW$9)agda=OIUmo;<4{aa#CN#RilJ=gS`jzS_E?FM_3dM$x zp4YE^rB0S`zps&ECVbe}Y@KWMwO^|m(R*nB4|O+kzISd^gY*~wP+ii$hi>G6xJw_j zQ5_mY?NzWWAa)`MWt;3>HmQ<)LjeKTdOdQJ+BcXkf^+rtoAC3pQXl+{!Vmd3sxT_$ z(xKuY<>ayqhgTU6uMqi;caNj1=H{J=l3nWVyb=1C0m#fx(}#Vhdg?pBRaXb`BlV#J zWASwSjhjJjVgB7}iVJzMkphd#??DnkxaxyMV$hDSi1GkrCU3J&JX>l!#uRmui?Okw zoK~~V3*IGKxlUM&)|B5s5_kNS0W46R}b8-O7#uDs>AK;SL%y?OlPXo2mGXlaBWKcq1XXP z;Fkw<-(Oj;_v!t9RaMU2HN4mk%(iCVU86t!HQlqv>_?{Rx3)1VzGHq%M`tU)RJYlV z%E=8=b>Hpe;(P1%Y_68=s@}RuU;DcnGwA2f=yDr;MC2LCci6Yw0I^wZnAuz9S}6Rn z8iWn6lfjjnF3l=*C)huKHfy3=-t~^~MMZRE;8E{TWY*m-AfNTIdpFM2sx2hZ=d9Nc zTJG8M?PQ~T6VAt(wtI*Ad_7Gpiv+{ol;<99f4*K{7|rIYb=<4;elfS7(;!pVOFwG4 zXQY33wcT(gO0-nxY?S;JATTVgz8oR#nPW$)JIyRY&&zQ7&8uD-Frt;mEL-Y;-> zu?e;*a>e)4@FI7zDAGUB25$6A`X6oF7Uf0fD?KHudnwY0wIIU!q{%3o)9SR3|C2pUhng&={i(BAJCA`P<=n}V_tKgqY z+&%H3G^Ye36mRGkO6dO+vpcwlfe#Pw;GT@4%TpcP-qvHfxr2L3`^UbW=J};w==Fw& z5)k&rcI~_A3rgLylISN0-lxw|?xaS_6&4FucKCjk#JQ+#!Nu?juBWt{^m?nKt8qPhMO}1 zxe4<=52_SmLyzq2_JDFYzO#E&m#5M*X>MAJ5nIgOe;f7c&hE)=HfL^JG}3Zc!=7D$ zISO~zbae+=i_t!W+q1H(+b#V&uUq!_=$zkw&H4Rc&hJk%zlWu(I3$s&qS)|8pgvGb z^K0w%Oms`q+14c7j-6(OM2~Cu{{GU4u$P#_yL4V9Yb5O8Zk4WFkpEoCHUI=SR%ZMD zZjkhCrCVxO*K3u`mKvLM+xC6hsSG7o777>_$9ehYyD%Qe-T6zAoRZrlipcu1q`Nzy zeNNsdp3xhU?#|HmX_^FG^Y?X+?(Uv;UA>;z-4%*`UUzpu8=v|R0E4$j$E}0(r`_Ey z)^qyz?x3OP^{5`;u$p>(Y7e&VbNYrJ#6GEC@8OQf&k&TlU?)((lX}=r?lI(b^|olls=a?nF8f>*pTU+fbgBR=Q?OkW<933(0?2K)ldNUC-z%`Z2%H=z2bTJQK19 zM@B@l#;}Tz9}RlvCnWp7X5c*{%plb8GdkAa?P~-u{l$!47D5zXz`OT%``9ekN&Vg3 z*eehAce~0QzCmV`cYf^e_9_S_&*bhkz>wkrm>qrqd(4schoR&N;SzGzSRQXe`9 z^>Uo!UONbhFs_Xc4F(oZ=rw~`4iD;WgWcD5N?(bpgp3kd-QYe{BnI3AF>m2wwhVE1 z;!G?a>K-Yq)>DVN{a6n-%jX5XWT-obX`ZkP2#&*o-7w7U-DPW*jS9cU2zb@X_JEh}J*|Hm<__o~(y2M@+~?)^W4+I}gcXV` zyy2Z5GTiMTEuA*pO*)his}45~ce{%n5hGFtf9FH}{BU3Ck(j^JfFr>h0}+@WsA!ugbf=40yN9W{^OfSaSsb~S@L zd{+$12_SZLyJB==&aQ6fV3ybBwA8pOsPHviFcR&x+x5ti?$Ll{=1As=k_{u>UL`qP zVy;ml+wFa%2aR$&_WVO8bWV0~ycOha#cMV^WrlwIDDcQz;Z11mC=BwwuYZ-#&ARJo z_c|u)fzeFibNb8C?%r&c0b|?)+j7(7SrhlsvP}&)>-sV7Ui5j@7-SMSasoAeXA1EmM`*Sj+Vvm8D5u8J;t^jPpP?hqorm@rL5 zCQXLhV!ZP*gjxD*lG$nY<~1EY>exNqZiv8;WRT?n(e_ zhjlSlZ;a#%y3=yhi>0|bVY3QWIcYC;<8AuJz1-eXp&%=;@6x^8U7fWYdN>;WeJ@re zr+Mk#pwQ)d-Mg{w`n*x?+Up08bc^-Pd%L|#SMVrjgd0Pej%vhh<6C>X-OE=>nmkDx zjlKtdwe6C?UstT6dPffx)qPvf7$0q)Qbx86c^PqNfeJAW zA_cDnTJnrWiMYx5B=6}b@SD#Ww1pG|l1S`9QCwB#*w5B**&o$#kyz@k7O5J(Jjy89 zy-+|=9_eQ79BE-y9_QBb z-YDgoj5;>dlc1KoP#s%rUm{|lr^P!J4`zlaEwwa5Ud6n*`9aCG8AWb+N0cQX?_BSr z;i?atW`T_iNWpOr?&tPRq1u-2f%kY=L$j%Ym!fo0c{?+bFci(^_ZPp;N=aAX{)ob>ox(N`L7+?aC)fBS0 z10#+)io!VKPXjvQIF2(qR)Yb(1oAdP?oWoZ?) zb)x=1-*fMKwRCkS!I}9FV_tRDz4zVaobOrgx#zYnP&^cid~rR>t?%;>n|IA0y_DrZ zSg3*eVq~bJdK2pIM3J7BCh3e$dLcNN(i+3z7*^SEu=%Cn#i36|@G72k}OeY4ZPxsO|lQd};` z;I)2R8aTd{yO8n;xwKxAL~8>qZlI2!Qd2mBBXnJ+Fo(P1dgzA!^Yn9 zMaEwZXgRDrEv<`GNQb>bp`rv;8K!QsQiiD`rBYReMy!xlMI9R~>*OXn8FxN(xR(yI z`&-85gs-*8F3%P^Pjty#5kyElO-MOCxJrj`k5O0Y#d5=K;c##zE?k2*oNhi#PVGjC)9MSQ(5#`k8Im)vz z+QpgQuaSK_hbL4fj@_0Ed33>O+?Q)5CslHil2@r@i*9=^7S`c_Ac|BjQ%@ybbeF8V zrd|zpuPJ;%h10nYidxWpx(5~=`)B^I<8O@Cz~GTiHA}#fiKP6ZYjX=URZ}?$Hr1KT z_4AF6&27>MsFIANNcX}}c#d}ICFO$&cln~DR*Wg!n{7R?3E{j~)!>qv0u81i7yEhX zl5vBLQ(A`~q`URCa)ERc5yqr*=k{x}d%G2+q;;e1^X9#la!A?Sm0W6Oo*~vo@g898 zl`<<#mOu@rwUentdZzKf26N|FWtxDf66I=x`{JqDeTu`P$6+HjiE(4)kWEHNxykC6 zWAE>qc?+|;sYUEaY!AaLy(CfiM(pQ#fzds)2dz<1+j3@mLtQEb&k*JZYmrzaFmzzm zukkerUt8V#UWbah#ktpI_bX07ED|ZgZSEO{KkUu=3P+ZPMxCcyl=d~56>gVdNo(|Q z)Ou(vw2hyAt3PdIPejsbEY#3X*@8cJ=w9+S+1|taTkhljPZj~&Uvq7(j9Zm>u2{t} zf^KtuAP>0<|0cVjIIf&1Djn+C8ckN$+o5`253o0B-82a@RZ&o=7Z;i;Eykctr6-qI z`yOo7TFqm6)Ey2CCEKzrW3G}^C@Ln#w4NXKuhkM^?iAgYas$M+)c}mOWC00JyY(1H)da4 z9Gb?ew**QZGAc9ka6=6u++aVOHL(-4w$8A=DNiI4Frp)A4^ugwh^bBKswIQBt_q5oLAY5W7&imvl;hm zkh`^oo-P!%ZWgsNN$5143K|s~WaAD>zMlN1?8@S=prMM_SP#+?6qV9c0*`k{4xIm8 zwk?!~ve8*3`(w)e$D6YU zxx22-cDM)LoZVM*5@sE6q3LRw78)bsQ{cmnR-NK=;KP`Dp$hTI_53Z`Vey1NC0aMh zP9fHjLxYLNlNmtsnVWadza7u-gYJgYgvA0DmfmJUb7)IIu5k@>eK+$6IdaH4(SM8_ zjUVM<8WQ*OG`v8-7w)_jMWsnvjhWg=Ta=%=M6nq{;3vA$eYdXTti?Plyh-{LPO+b( z4A=`PIN2o%pe3nGV4fHJGSJ~ObmoFEv3xIhLu|BBHUPostsF&J~90~Kc)j$R^- zB_2?s$&`(&TAQgJoW_>a13fesxnCrC+%(P{GgziJJ$I{n-CMH3(Wko(J8x-kLq+i|ByX&Ujdy+7qEe-@)?db+EfK}^k_t+#Ai;=?q4jW+SV)zqH`NWr?HNy#ypq^ zKm+z`jfT59Eu~n{fXF=8NphHs5pT889`}YPo|q{-F`I)3+`yp}({@)%CKz9IuE~9a z58PcYc_2Z=8%%UboAW)B!UBxA51o-+xRk3DH0ZTKaEg+}P(tMJ@#0Q*%Nf~us|NK} z(Ad+7xwv9rwmD*8z#d|-vx3!J>))}f@t`aGop?-$4t)yr1aRsR!}UD2Ry@XVvB0|3 zsY+B-&&C3e4N1Wm>RD6x>NBuHkFkvxi(?f$HU>qFsO$)3?|Mc&)@p^P@H&adVk<)h zhfSO4TvK>ZjdokPL3~=RRpo>eg>lL~r*aYHlyJffoH{dY7=YRvig^}VFZ{R4ri(IL zJkw&hR5NWAgwMvb(+n4orF?pfXUiri@Sb^tX5M(qDxOy@zRjt-_V0m+l94p=_u0KW#(M-2^#LM!T+3Nq z`<}t^CRkbIBMR1&Rl-R-6cb{FE=21=7vgT@N?k~*5QJ)HsrfPJLWAAz!)ImZR$z-n zNIA9$cMMyOUSbUFF=dOmD!e6d^N2h6?b-cbQ^FMEaiFRaQ}p(LDM@{ayMQTV*^&hn z`}N&*Z_n<%gb_=;c@+hUSz9R<-7|0Zj)49mOghaMAY4J72PUoQ2%yc8z@&+Ad*Tk1 zm<67njO9m|-k9@7pardmaG-GRos^s}b*EUN?^JV2m5Cp73MPI~c4jg0jZ;c}G)|Gb zBJ?uQ;ei4{4^ZlD4P(8m)9$QyWDjai$gta8^49E98S8y+;vGl_44~SUx=sls0!l(4 zQ015~YxSA@Ge*_KKcOoLN`FJbl8DyJQ6P-${nUKw0`h%t3+Eoel z&%F~p_me;{Tao1ob(?hU^pI-FNiHIlS^^tmba?PLy280^4yNW>;6EH*s9f}PD$* z(STGCXr z*V$y5T8NA{7s9g4QJm!Udh3h(vvsTC66KSTrxpQZZUwX`UG-;fO{t5vmrsI4URl^F zZ-cZcs0AF-OtKtPqJKDXvI{m5_aV;8Z5o#@X7_WidUv*^_#lf`g8|LLnUw*Vu^BwP zywCVI5U|#=@W4hw13d$R-hLru;gD&_Iz0q?CEWTu6%N`Ld|Ix3sa!&!=Y5`XNQ)|y zclW)#b!}2}W_9zL3@8o9WI)+~0&_?NB;!grF)T=}vBmXX%s!T<$||{0YoNXo4$79+ z0e8>`v-7$HHDz3h@$K5yXXm<;-ji+as5F2&-S@xPIoA!oC);_Xq*Z6`L7Vw?NNF{4 zarZzo>6M8=z+ruo5`o|&!{o4aUfWF?3<{yV-1VO9i=>>_>7c$mdHra;P#}T^T^EiI zKL2J!bEST^R0&B$;<` ziqi*$a)WT(kG_Q&A)kU$34}eBDJ@xScuZju$`2_kvhb)%i{;Sr5~(=V$wiJkV%r2a zW#Ks$!=E6g8F6p@r);nJLJY3W(n-8Yo8q zVy$5$RiQ7rdlQZfl~iYkhF&(Nqz?4BrOSJX4@tfKu~{Xd$um!-QAt4%W$BW?K2-lTWep~ zJ@zlzrGL#!DWam@U!y?NFEeFaP0()n6$u&C1`}uIZ7f^bQO$+w+pOid+Gu;c`*oLF z_<`)g;x@`@Z-@Lf+HLb;DPyHN$v)j!yS9cbaB19rj-%fu+RGytRDk<>+gWJq$9TK; zW_GA2lT?EV`L{HTVrd-Dx0i2!$K#l!e^aWhda$?st5^-`LE&;Qy{jr*CyjKIlC;l-G85^ZM*t55(WEcyu4CB1$Q2 z(CYK_%mI~zJ>Dxi+@cR;7rb^uM<87Lv zsgjRMdpTZHZ1R%hQ3=~S#4i@AvZ>U$gq=3It3QEtG;k9j+Ucy7Qx#Zn>k~&JFa#oO1Ilu`js8_62FT9I7U36r7}BG37Ue zrtJ541_#^9XHYvvLhGqkh3c(qi@`d-z=ZPTl8>Bxyv^+bvM8aW9ZBp+6*lhVO0CIqV%?3Ia zgH60)RKbr=sI1>uDsF7^U{mgUs{ot9+bA3~!E&IH*(w!J>b!R!YvZKM_mP!ly9<*m zLd~cFG+{6aV8G<@m}-H>q%=HKkU|((QZELYDwE3tDs=|C_gzy61N4=M4QTNJDNNc2(4rR0_>XA>Px3iy~n(6%-ql#I&4 znEf1c$#`42L|1|3sr%Yulo{h!f;!cc9dWBXtv_5ovE(P{f@E}_#%aq_KW~GbY!Z(( zbQ?sNmoZOaC(Y2bqz7y>CAN`UBe2i&yU#wE)d>U9*d8#@l@h96T2I=TkWfg4a=u9D z?DHx+;HC9cV4!kNt6-qp|9_I!VL%Bpz#{RuW|h`qa7n$Gi7K~dtEBbqx4QQ8b|I~e z1=eZdJ;mtXgn}<_cT3LAF37DjrPL_X%ceh`MR+N$SJ`#|%n`HG`3XJgYfpY-0dxz3|p(1()`c1;DG zFD={NKFn(}g+J90{iZ6T*Vh+rv&8erN)tn|{ikT=+e3KVxJflL0%RYIdfB2KvWbd1 zx*8z&EqIYT8Cxx6{{-DJ0Y_EDYE8f~DN8ou1J1e<6NDBM1B57)CJU_V=t$$Y@V|t6 z@1$&xR=k=Vq`bTM?GU~*YCX0o*kq7|dpWQ^2!!M9tE z1@;kTBcZ>P+hXM^N592>Mu%yZ6v4tZ?*MbW;M#n%JNTSz->aB=u3-kFu!AkQ1*w(W zN-$!)qgf;5r7acQNFmsTrt68_zS5m`L3Y6^?UqzFAU4oolQ>Y!7Ny9rNEyyuG+N~^ zUXWd0+>l}JDK6qju01Mo=d`$Z(L0x6l*h%JrKeE;QN5QO_QpSu5uaePnt|^-2sjt* z7QGgaK2N!cMwwT-;v%#ltyRPqpM;sTfb1sgF(Ehg1I_*{kK7KahCC^m7Ahfe8L1b@ zc&4%2eDH7ec#6_c3R7hRvXeb=sDGnvjp_H@JK1C0G)QJHA&7rxdH)BSBy-jO_&>v^ zi|gH6FT%Z+FeoV(xdS*`m(6C=*_0-yu+AE_CM!=rt+O`S)&&Ipi~Svxyf;4UBh=v5 zNFDS!L9u#@oGfgp=p|xusD-D4Z1<;k)orw=0B_qkKe%rV{XydA!MN5r(BNi<>k)-K*fqNjGA# z2K+q(Q)SCFH#x=NsjQ-xh(10tnmtI?5`Bkd)VjB& zWc(yc-jMsQ{lxHP-_mOvvrN58+JM$X7o9BmvApF_3z35|+@C&?%^y?(`zGT{89T$M zsoo|+nr`G)ifV^J=1X1f#?x@ko5c)7R8evH!5j&~nT;O{+`wg-7&L$SJ_gOUl(`SM zu7v@QqL{?C&uFCsvbd(OZM%00WL9et$|?GPgV~vyU zR$2MJ*VL3Zaocux&!yG&$=>ZFU1g!8UvfvU&mLH;FxyFvQNe){?3-pF;_w?+XB{t^ z$+y_1{z&8}iLmE7v+Yr#=rVgoH{`f55i+|aB8p!0&-eY=Wqw0r)P}{Y_#Tmq&h{;^ zV?}yvTzJ&wF3T?2??Rn0F=Kg0IsH6cNk3ea=}!4%w#S`y8HrZD(BO|fqU^irR;mt7_?J*JUW>8nDhY1mdKECVV zSm(x!O!_(-4PEH&yF7b%S!iLOBl_5rP@@km{E~aQBca50cdpAW&z#%sZgQx0!lcT{ z3la#xDLnO=b8>}M7Tdsa>x7*amPc>b870|vvv;*g?kuczh>f!c1-7*;78E68m^Vw4 z0i>tDGFbT7n-7>YqHAO@@iE5t`=wfSnfwDA{*kDT~5RnfHpAxqNcv%XgPe@+vPgN!DkejrL3E?AuE;Q(3b%Co zQ$18F5ZueZo6Ei-ESPUNOkitZ4ey5(Azm4UR|j~w%-852n4usB9skl>VBiM*O;j5=WpH|8x5l0PdP=H9zB8C#F6$>OUtC$xW z9O;y?mqj1xw|h&=#V0=xb(>J)oXY-mC6=E_i$M-cdLRkE3IJmHcDgGr;b4!qMxca` z`P>C20mIHqwRL5XrYxf#KyAj!0Lq^iHz$)>*ks?VE&FEJzS$WiW#~ZW`G^|^n{|+$ zY@@dNOs#3e8&tfenK|BEt`U1HbFA6A6(4RE>hiwjQi=5$eN$_NqS72bRH(&8GDWD^ zMCe1cTKsr(xms+KwIk_nP>Uy;%hci>&E6)|8*O`e8&*T*hVy9IH^=xLXD;}Q3)$ju z`{E$Rr+4~#8+b=)yC(6@>~Pzz$R4n`&}-_L#Js%%$r zduBkVF_e_nhamMg&w|)L{wCoNbEhG8#R06Eh#eomsy=pHOdy-7Hps3M!Jg-q@iJm7KcuIPq_RVLU=M)X&z;tUjdE=k)@`BT zq<5n15Q9cAm|lnqpU>LyJby$|NBCMQj_|dVM;tS`6-W3=yH+;cG&sq&=B>0WJ$=#D z*?~30?aEu{5zOx#w|fQ~1lOgN38A9Yr};hT3uh5JvV!v9UZA-r-1DwN;a>kUxI!aU&&76&u~ z72>~^>X1x^rJi6i?5t0sH`rfBg5(ze99jBM91i_lcBNFSGe4J|-{v{j$N3Dq8^4k5 zN81ry*ez84=@q&MF~y0~g( zSCjkw=d+z6j@E0kD;J?v4%8vj1q}vB8N(d;i8*+C+BMnLhs?@a|MeTpl6|VC(A|Aa zw)e~zs)JVpdU!?EltF-|g3He{;!e6Y+jpctQ5&n=N}N_V>-^Zk+Q2r$*JcmYq-?)7 zyYelGstu39ngsI_r>#>X7_^;sT%<6;Q?VYCJhiQVLC+eUACXerZ#l5IyJRPr2=2$qor5Z<=5>Hk0nvre>sQNJLSRYvKmx@WWu(*2gwyMVBF81D zp%e(Q4Y#ESZyLqWw0m@ZTnd4mTEK=YjaehcEGQpj93;C}JTK*=)#%c(uVgO^2x4Ax zi_W!lZfCcZRVq!1>;D4T1wtILiigv7WreG!BT(^PX1b_KjTcl;Qc?$sAMEjQ6PzBC z&zs-7iEjqw4~z~r6RLp+}~;wds| z)x?7;BBGE=l8pSWhv9k)mMsjM6$r&B{{u`ZwjAx@Fr`*nbnuwUE`tuZfvuv%cu#?g zx+o=j_}EY6-r#_z7Pg!qa1xgj#0>O6soCj4iq=fS2=hCj{5i>Rl6`-^o@q=;McWK! zF=LlVS)F7t{Rm?cql7J{>8C8F*|a_GTaRRWOKbVu4cXp~Ax<$%hcw^A`X1MDYxV$$ z+pWHl+Ydh5<4&0%sm-}Jk`sBm`@$BM^4oqXHQFj1z(c4dv}^6-<+=2-L7!%bDHNj* zH0Si{DC3D&2R18=_@x1>OG~rlE=%D1i4k(Ve!Is#_e6G~ z8ym^)yLu~3;zM={qkM!jbC#e%nK%MLm4JsQ5xfZ=)c@@HpdQ7Rg3^GsOu zk*~v0N+0Dl3Ilb{ImDqi6)gHomEFtA-hwMhS#itDRWNGt^8XU2PG?lDyFZ(?Nwig3 z_6p#RmwiUy*0S4RcvcTaD_E>MyNy*gHOU5*M7i18SAkC$S+evQYJyd@>Lk=22AI_G zj2aV1qo)q{)+e&P2gayt;gY&>0EL6hfSk^t5{z3x9m7vR9m5Xl=yii%&bEqqMI!qu zrIKjkt^m;60#S@r5`nwt%d>KKm@tu5!Y?=dn9w_a#7qf=jKh<)-3Cl2qrO`un@y36 zSvnEQC@@t^EAP@Yp-iz|fbc9`4AY%gACsQOaa4S(87+1A9f*w3<5I;6>DWv|V&ZHW z6PKS+NpC=pz{Dh$2`AG^1v18~MlVY|{m_%f)7c#)2LHC(e+PR4i`;`>C)M?K_W~s6 z4@Ti0pK)Q_hB=Gmd`e|exwsp27RmWZJAPC{a-x3jhUBEwE*7np5V_m+>PS6mET61M<3etVh3DRN-BSQC+O4WjtN_#3u zjR&`y#OuSMh=@%1#04P#0wGP4yN zTUICGSUdGAM}nXQ*QzuXzZzOZ+A^OkClt84g1w)^Gl5H}6p$BH{3 zU-G40q`#`%tAkR+fmTl0ODWMVrR;s}hTZ1QpgFhV$JzXR2%^j(W4?Fq!JCo?Z}qZu zY6fquHHRXp=CBOWR+1*Qhj*Ln`=$ZgO}C*#-0Lj_X$h4ULYY2r(a(Uh6}9yNc@|Ld zr)&5Xsvf0grPCtY5DCSE*99s=OZ+xn0%myGlq zDkrJV?35Y0mM6o9OjlxnrDvR`Ywc=sF~?`_V=~KCT@CIFY4RTXwLbfM13h#*sana> zGJrX6F7vfo2=fl0y{92rkqVX``L?N74}F`vkhZ(srq1tNiMDz~ZR#|}-6eHWYWJx# zyer)iyqMdzvo3V&-Ww#N6dHQEJv{dUZJyhu4K^vTU08t76M( zcg*T;b|2;M2^+i)$0i|H4d!*Zp72#L3(Cj3)kLmYF~T z?FO(@YF3)40QT^@FcT=Ne-Z1D@aYAzPTd_P#eQaIr()>@?7>rJp6{Ps&3=4)Q0!%{ zl{{n#`2S92mQ$wML+0B`!2jCMQVzGb9B0KK0TR!dHhyc0)e6TMrEKky;DxEdq0< z^v8Y_ci~;xH-QO1yK7cANiDhf-&=A2cd>j@#kOINeornLN+!b^`Yt7KftGnZ6ZeSL zt!&St%=1-@6NhTgB3`(eU0<_wEt$`_hud2Geo)!ERd@DXg#}zibGA>dJ8k2@ojz9X zYd(E@4dlIg%%tX0dPV$cGNp@G?O-$e>IC3U` zB8tbXSX5gN3S9}4+M6tUJgLeMm1sZ*@KIY7p?66oPuWN{a~k{I7@O2;4aicU=tBCM zlss*YS3$$C`9VU%f4`g4`_tQ)-Z`S-8}gBN(u>kW&F0WAr{vwIvv(Mdq&-96o z4=9_zKAvb{KI+#|24LxK@jpir%$a zG5ZuVGT*b=51kAX*^{rHeM!26%=rYB?_CJJO)Bl->~jFACYev0{Y57t%nf8M8x0;D zOGN|q_-Ii{2L4zp?Tmee+%cI!Q`E3=xm&T!DE4kxzx!RRvHK0-4+mpStRx9mxa%u8 zYVdYzS|@V@nwVkR!ZcQ4$iw)?6XE%QN@plNx6G}h5(%9@tP;3&i}^WKMJhH=eyw7q zy_19r*xRcu`74NkQoA66dZi+oVXR?6yNwDMY~t#ZH9y0MlR64zP@ULKTUMuH4vI`d zQb}by>(gd6w>&$}IRT$Tw}Gvq8+lRrWBiO%p_tIrggCGul>tjr}lfb5wyOQ=xPSFw$L z=_8(Mbi_4|#u$YR8!Q@w8p+)Lr%L**fOcOpDN{jU_o-a1ZFI3(P2IA>Wkac5OVgQ# z5!SFPA8}ZlqRH2;A6q-Hq@Cs z%Fn8-k{!(}!xinQR4n_@_hwjjgyjsXX}M-fsTZ*9VHjcUz~*9UI0;Y)vRRsnJ)khA zq@v=ZAp2T%lI8XzjF+2MZMiFPqNe-F0|Tci`Hesw(wLP9exkDC{`s>J!ABKnQ;YX?*r~RCQceVvdI3Zbh7r~h z&j40sJe*`fzKo(&bVRhSQ5(yX;EW36YOQJ4}E2)|c&(lW`31u}C^6(>#$%7JqnMmMFN{kuk zp{0m`8AO6R_%R}VI1Uv@`dDanvY&LlR3l^8HPKiC&Vz0bSk%G zUOYar9OxUay(;|EZUV4zg{|>VUR(kM1 z$iRPD4F3MN13maJ`=0{;;cww6ke#_p@c+C@Dy}O*$1?C|UgWRZsxn!*EQ?8$X5zC% zQQi&kr_?UiyOvoKh7p#nbtdqa(OV7vVO&YoWOxMTA2N*@+QMc$9**W-_{`KkXa<}xm*#-L+d8bQHaBBVNXE^%9R_yDa$>!6a zw1BmoI=4LaQ*QI4<>jfz+!;^T?$ZsWKH;h2c-!vxiDPXxVY}I=$- zL0xV;X;xXd(Vn-Jt5kj%NyS(;#@?KyQrU(_7*SHEoS$OzdKPbYOb*pSu;%SHd%8cE z(udgD-;2?kjFyck)&=E=`*n;sID%9j2D2wC&n#)xKPNj{mrdCjTmF@wr7}hD;>6GB+{yMu9s66`Dd+bU3 zl5#?uJ3cIJIC50(L;Ie}u;~GamVT{pqdcb)6>C}aS0n##E@tR@GQTl%+QFc(ybP++ zND5;~Drpt8knw3ETYO(9S;#QDq>f)mYTEu4yOX9(akT)RcfT>4$-DQAWgDv3wZkHM zW?9#6_rOoH?e65?WScXbRC)7n{7Ds@uE(&vxBh02mv`(-ER38bIv^>|ZSW_`%9rILv>=z5s-C?0`dDJ`$OGu6u*#*$Qm{wkKY ztWL7LVRT6yzr56(7&VvJqOB`qD^^+7f-4Zxu`5&um{8v~1PN`IY9PS5 z(&jIe)L-4q5?XwuC3%@uQa_@yq?cfJH|$bU|6YYF(7kl;{jS}kjQ0g_e;ABbOuQ~K zAd!)$^~62Ox6>qb7+F|qXm{p133-G8CUrb{_$?8x9+r#!jl3^qn{J!g9DRe`tKiis5DQT&@-Jq8J*Dbe0i~&u)>*yKBd??KsTpAv5x; ztVu_EWhS?|OVZK8xk@HjtEPk8dTQ2rQSsRvSx0=RKFLm>&Z!Nuch%bo0VO+8zLvGv zMV6q_QqC!P;eg#++El1fo#ZQUn#hfGCW`5=%iU@DcbBI&VWtqgP%H3{GgA^_Y#P=9$svd|7=#jX?lQejcx500^AbSRwofwbeR3o2HFZ&SHU_T6%r{QY#t=gci;lJyd~%!J zXX>I;+MmuHnIbE#hm~jDpX#Ee?LLJLx`|}~aR=2$y~Vqv4JnTT5gU9gj9DJ75&L&r zv?g~CIuQ4GkPMI&0H;K3EVp6q7$R(3l_n^T^bh&D#;L|_0fzY#%Ct)H!bH!SrKygV z!l%U6b8;_AwX9F4(?z$nK3bB#%ROEneW>Z7FQ1#s?d*2{uK@=J30E53YhKhf$9=9L zIzIiZd#WKiAankeuCFnATle`_vUd5fTS2ZY+I1Hgz(jA(O);5!~tcgLW&UdoWXR}{x1U2e&WA0s1bVT|dw<(G?bIjwk z=IFrm*W86XGGA|Sh0iVObK9FEMNU7}934FW?#5P>fD{)g76$JeO0pFdrtWYnTcXw3 zd*fz5)Drz$`abu&mS}I9UDO&qmi~cDw?+G=zwGvFi(em^iuAwX1Fa0UEqAOZZcg@!G+$Hy>``shY zrz7_-U2xl+yT+x2!LD)$n=xt;zp(<)ijnqyOu%r|qu>U-IWK{>evGwBG-D`*-Xw zZduX`H;vd|8UNSsK5c)o)cWmOcU*ULXl85HeXu)Poqo#Q(jCpqd^daZkGiA3O*cRE zDTb?Y1aos-ZQg&xw^t$FhsaON*SigV&`8)jkFPam* zabEZ_yuUTypS1DEIkZ99`E#R}rho6=GB;Y;v2KUG@q@sIVoBW9b0Meo*4{t%g&#vx zN*`}!j-MATRhcvAMf=dsC+9`CbQiM6rVIIe)0sD%f9-(uf$XbqK4X5=nod9J-n}6D zFpp;zMC0kl-E9k_!_&XNd{MM=|I77qjl(y*J{P+BA(At4dCw3j;+1H$a8(%J`xZrS z>&?Cqu!O#nD?c=QOyp7Bi6VX5B@LqlF93CvoKR)cUlNOe21*;SJ`Gu zajW-=zQRwx-77jWz5M%`eB+ArSp!XEINndMdB;FY4s2@96GoDvpWR2K-jrVoIJV>- zz4O-Gf-o^kKkH~J^gLeRQr9j8ku0Eey)4zg@ofmT*?ImeGkPK)Z-;eQqxRUVm*PfFZ_<5Qn@*{mp)ibdChPD0A z+TXqQ_YpVvq}3|8w*NWO`Y;0$?4MDgTl&`YAE0ONn*LQhNe@9I{ljVQu_%nAOz_9C ze0+d>LLcBcNPeMHd9LL-ss_GA1E=x)8VQKFIJIyc&$saW4cC-jtpfK@;IsO$g%2n5 z97Fe^Rc5}Sa5b+zlIJGtYyY?Po!q7M?=AL!M}J($dsg#&l;+;b^J_c{D#Z7nQ-Me6 z*hYQ0oe%%YbN!q({h!wt6TI)GJh$;YrSK3uwIZGB->RR^q4vM={4TG*#=fB00p{-kSFa-7%vJI~u`Vw1kOof04Bxr66*`eKy#{ii;l z?e=RL*U_UveKE-|r}NyxYv0Fnf&ypgd;W&^_<|2#(1)kHPh8V~jy`SWt54_y`Gxzh z*M}?kaIQXVX|aj?u0C&Qwa>TdGuIVqg1@KFL+;%NMvLoO^9ygjBCmN_<{6zdf$EA5i6qA^?!B9!3VhyuZRw9 zdeQO&57>XdW$x=MqW<0jQzTNWZE)>P)IS zHU%?}cGC+Akcsg2Y@8oU-@rHYFjH8{=TQ=xKSoDdT=&Xoe(|HA7_T8GWJWhEu&kLi zxyw)}!~F*r^Ls<#+l*sWGqX*0DI+CmePNW6cUVX6;vDYrIPPX^=pt+ATQoGLhQ@tE zMQdp+Y3U(rscF`hE=;;Kp_a^4wq_=1=1%L-)7DJGtj%1YW?o!Sk0#ZQZgeCWJ)b6% zc6M4jbv4>?w;mE5S$qNX1F`KU^^|DQ))czBt727Cq#~#wE>>{_aXbV9@;G|>8Z>dZpjNw8*n;Sa9oFn)O49eEg>o99aQ zy7$pj$xcq_Nscjn{Am}mF`{wd!#YO9J#4w-WfY$H-nMi2ds${oJ;-30H*&KzGo>Bp zaZd5;GJ(+!8r$^s3qh2N_0_T5fr32~K7;2@g#wtmE@!TxZY zRz-!qr!xKZt$z}LFU#!E=Rf+-L@6{kAR) zj_*uLdR{ZIuqyXk6&S7w_pu4&G1zBNoHU3y9&0kMtU)C)7NrTR2f2Po-TmsuyTAv# z$h!${y0-r#X}KbVJ4LNHv9}ZxOHrGPLfLYR7yIJwc!I$#(NujvEatn>5G&{tBQWUl z)Lp%Jr_7TEXe>{CznAbDp4)r-dEN5V-M#(Iya;X>XywU@6qTeHd#uoL_7SN`F*z$C zt{3+k(*p<3^EdZ*rz!Glebjw)N2GqGk7UK;<1bYq=r}w0T!5?jS3#j%K@l{XjXi!S zS|Xh%8rB4^5NB)Zz~$V7OfI*pgmNsTqJv(N-%B+{`^YJhd&=E?RJ15P>Yh0&I<)vG zeH#?1;P{l>$a5nr=cR-o^0knpH^v80#+^Oe$kLWAitNau#a$7 z*$~11?U#vg7+Y2rCf}KRz?Kx@;jTV9+IMaRADeE~B&XY--97J&4w^sKMz^E|@)Y|` zgulnz+&Q0W?jg}XN)FdA8n(wU^K{z`Fk{U(TJuKSy13EPn=bQYEL^cEN*d?-M9-kl z&)(G0rM9@QGPFf)wKdrmm}TcoZMd_JiT3I!TsMi2@Wr_wLP#s{;2rpqXle1ma6=-A z@oRyT4aDy7Qi6Jxtd@>i8mM?^u$^@C?fGr#z;MQ@x^GbvP%lHfDR-#}l9Bd-q9Vv< zXx6rS^ja9=`u5}7{*V?tL6hzIPWi2|b*xps+Snd|p9|H&T+G?pJ+&sKm%u(prnj~O zYgQ0>z=i98Q2Ux#+4(vl8@y$GOHI;QScOyd~>%@Uuf(H?~Nd^B0xDNSpwz5c4I|=a)FAkB809F z)M>CHC@&%TFbHDZk_u6((UkK@KG9v|4FSqc=j@{MFnKwYdt54s8qJDiAFs}lt4NAQ zPbH|YO+dX2)4jy1#PrP_9@I<7hG)`{ZNA%nY}8&H?eluX1o&2Ng$Um^%bJxqR@bm) z1HNqyasoK(?q54IzO5C$Ny{n6H^HT*Dd1Z=>&|Ft>2dFQX*9n|O>r-cj+{S+>@ZA| z3=yVx~0Ab4tlzEmzZWsS;61lt!X_?+9+gPq;^sag!?wAv!g+)^X2kIA~&vBD# zo)2=1Bi&opSd*ZIUG^?&nhA*8f(g zeV6!YODjwKTfQ>Oqu>44w&q-sbtca%c@L?^(|m9Tf#C5vgT>VB)w&Xg-Rsx=7U{Bv z%TOZUk6=vDT@A_%dX|G04f^`gq+)Hma)dUGQtxieTr=Th! z^tytaK;ti!cOQCrw4``n&R$A6nE)8of=$b+Dy7=XkAXTZJ)6aOKGZ=9z7yp%0L5Fk zgAov;ZyjpCCK%b$N0vK3Ea~_4-o4O`gICsgowuU}g!{Yz64! z4*VMCzK#tnm6?=VhIfo&XieB>cxn!z3*;Y|LveZOb-|XTL4EEgWu#jt2eSks8HB^`(-&9Cc3rCuk}>Utgx2CU8$Fw>FLF<{yuY{ z1y=|@S#%pW_mxpUTNg*X5+8{-$*~q}k<8>^+H;F5U+U?@CVGgQH~cDk#d<6CtE>^O(DZbWv|_`R?y^TK~Yrd0})qAT`9->i9bSyrGsb8gA+5w`G|tf-u_HV!~(>Wxb-g&I_Xr(}dB8 z>wHbL^bNZ$gCq-PlR@UH5+84BSyi{C#l7UjXm0VlLF=fHLkdU1yWr(eqmc!ZJ44$h zJ*@4*Xc!b;rXYaET4uLQis|4&!xPbReA{n>#xmLxYb@i|B=fgbP%f@|h$d@mEM_B` z`e5Q#meu20A8K#IH#DI&ng~nCcjGPn_3m>gMSEd_e($7cQIjsj1s&ZlPKsW8&=6A~ zJA}(HX`wLG4BBnsb3r%Hi9}Uec;_YUsC)OxQGVV!LSxp~+#$I3{vl z`f9nePlWtg>Ad`*8&oqZyY)N9@gthE4B-o2_1 zUAk?__P{Il;sC_{cEKc0x-(fV#enDaw$@Zjif=uUx zbU2ki!1f7P1C=`vtPj=S%ua*VUXl8jBU9Jf$K|Q_ncr|@`0JnTug{0S{?-1vPJh9l zy>RNdTk^W-*y2=abf*+1{opioo3izk*WSf7_5{PNHI!+u)~Hvf<53R34)c^G*r31V zf$-=phk=)(jYL==k~{N9NR>`cxv!oMCxT$!&B%EPfN`(>tG`5tpk5yJH-2VHemElgRV?Iyzg+WIY42N z9a{Qbzt&(}QpK7m)-l`}+c19M^*getp}X-)W4yJ(bP~tCRsf~~CztPUC~dk{#EuG@T$0@WdbDwPqEXzF9%e6fGJ=n+9Upz1F zBG%bc>HO(hz8&lfrt+ufhnSqyD)+@VMvcXxK6}T0bsPHn zf5FREq^|Aj2h^6QuIuYpub!fkAq-*SKdUA)CP)xs%3(b(w_w^a$NMn$$ z-6WJ?wTkeZmnx3b>>>#;&-xTd#^h(nfU}9#)(jY+UHngyrd!nBY%HbyVRJ_}L z)SwR9yFLbz}eD4A;MY7zGE!<1^u+^$t(E`|X>f zWlc|#`$`^+=iZE-G^A$1fpxMSWv8smuE{-_HN{`s766R5wHSMSK6fGyCMI#mm{$^q z2r=_ZZJ$Eg%4gWL{bicazTBAu^-EKNVpSO+K(a@E{KnknbwPu3>rRW>Gw0O14W~sf zOK)AGkdD9TU?tR#uijAq?5VovBwY$W6JI zzcspX?<{2}2jhVpMeJsK{m z!}4)DGeqcXR#WfSSj=tRhWvl7J7&ExJu?BBUr>t>l)7j6sHQq|zlb$98ZTd&eYN7{ z;aI;a)qciHBX)J#QtqB#w=HpZo)I0s?~bN5`I8uw4TZVSpJbmV7(0@@34fWcPu=dW zzNWjIEhtT_d5$WB&2rC5pJsU!W2PZ9P#iGKf;1vi`)m<{vrf`(?Gq$k30XZ=R~{+? zK}&bhd%u)~l{xniF~>tPKTj*^7H`@T;x(w(3*xYfCr}bxHEmML&J3@0nYTrU>_2^v z)%evg2-@oNPNXe;{VgPtsGsYey{);){rlUZgZ3iymjfpL774wgFu@eQEZ?|*sS)&H z`M9ySMR`CKAopG~qD(`CF^&C# z1rx~aY1?j&wgmfO#O?U|Xt|OJ8+4h!u#?TC@b`$KaJr624ND>eZ5wzN)+Ovq0!5t%(^>d1sp6@@xIXv!j{SOMGD(_RB~Q7`gB`Ez&wL$s`T zr#d!7-5%RK|PlnD>X+1=@*)*2cJ<(a_Gud%Zu}0v>QK zhGqz}vG<`1bjW{D7Tsd5HoVaITj8C!J-%gnikcuqXovSA&^^TkPHFH{bnzKQWO%Qi zb_qS2$)lMKUhQoP)&h2yr?z$*^UL3fa0Gn3D~=G9+;)TSO+`h=TG@@AzAS!OUsgdT z7FM~C4~nn{k(bKA4i}}<0Lz212Ik z3xxbzOD`QLf2%+2P{K=C9nx?||6_Dq(^Yl&7+Qbhu6}>Cz>QtiQeOwsxb!>Oa?srCqG!F()A*8Zp4YAc6fA$KI z=2)l_{?;D9P^AKO#D-qTYSB1h7)SYmDeKJQnC!8A9!X;s3#za<%po-BBg*42*Vz}u zVMfvxSVHWcZ?1{@7HHAz8YI1@97Be>+}J-v`<`;CrUc$7fe2I;hgpa281)g9M6xm? zwXjgiGNvs7MLT-tUkix1^iNy5i!r|^D7li^k5N+Eh)2oM7$qlJ^oq#R9ri_8Wa)%` zQ6}CMS-Psuq$&p$=#xV00!Y@g@Y~#<&IBbDlvRn6A}y)FE7H{{N#d7slpLd3>uWN= z3Y64OyFkf}_ZB_88A^gtwNR3-Sk$K{e{Iw?1~{^uP+CpC6!;6@0>br_5RR4MFM4y;B2W zLFKptLQKIdqQ__iaO6YcUeE04Tz<43?y$njm>pe9%)U{3L@>Ka1}`qaU?Sl>vzvsV zXyIBhySR)s9|i4ggGJ1)g&!=&+|C;M{gbtJ#&@?M8pt)LKmySgv?~BH_z4hAyrc;N zUe8XQhiD-Ug+@E)1JOG)qX_cCX97eEVn;jOKW>cXzr#>}82&ZX#|03y!c7t{;G2Rz zy)wp38mI}3llE&g9}b@Vipq4U)9ZiR42yfOs*911wiERTWFA;NgB~eeoq2aBAKN;> zwxTD%(%BHxXzfj$5NXWa#rR7atvWE$TlfwHfKcvTfLB9{8vI3_ayu^VSMx~Y@|X%) z<@Nb1(*#kgx%)K1w6Scp!nXxE!B|I!ppcJ{wlg6V(mQn~#J1BQ0|25$2f`Gc_zidS zd!iQ|e>ZVH0EPG$OGuhF^(7o} z)g?4*M%Kf*7tufV>D^b(j#fu(RU7qhaeq1+*XBr{@JRy>@TXW|Tnf7+eYFr)IBTR{iNx^(T|+>D(`8d;Mjv zGcT)&(O;D6|17DW+C}|R^`DojKVc@VTYXyBa;u+{`ob+~@9P$gdXz?0vZ7+ftr(Q(b5^UdaHnhg=V<<- z+4NkX(d6`R8as z(Lx4F7?5D50xJ}|s11n(Gi}>Qm^oYPL4R#aw~DgEsFB3kj&tsIxe35>t_>bOdE`P*Np$-3!AU+fqE(?`;w{W;BU}F=c!L2wR^f`7bOW(vL{A z(v-W+Sb(5Nn=4fs<+C?*jeTU%0WtYvsTT8q4|OwY zU(2tz-bj_)Z{%XMKoh9cpX1;q@vpzN=Ipp3W$;Ph$l~YVgvxkYZGl#e0B5FCkwj8V zbV_Ry^mO2Yx@1EFa!;gxBi0!!%WrpDaE)ANg?m!kFHPqU4rT&f5f$)TITgfIVxrrq zBzLWQ?gLTpUKN~N0xbbufb2)L=8NBQXzXW^R%KD{B^M&0yGqD`3S$herR_n$>PVKB zjH4th*-%dF?EjGqo*+Iqq9fhcuO? zF2)Hj`8K~7(>Y~bRgt)x=_DZs#_KcgruVfiaEE^|YF(*G{#~|EcQ%{m?U__sR2}%O`jmZCgi6w4G^beb#6@N4gRbV8xuTeu+VL$>16C zb7+ByAu<32HCRUWW=URw5ZGyPpwhwz+%JFB+~Yn}j9MCZpAlwUC!tAT(LXC^j_y1# z7^!k~WtA7%gG5bec`a&;VGG^e6}C)&XPEP+&FHC|u9w?%;k*OPfmb`8J7u$@kVQW` z#3LeB1Q;?<6TrY~-BWrtjNNUd{_02ArN|jQ%lBD}p2OU3Q^}(!S+B`;pE(DOtL8k$ z(`Xvko-_R+oCtj^t6D|6FLCeur=Hfg#fm8!+|m%e z!^g5MkESD1mUuu4B3l&*Va)=XudQG1_hs8#{G^?VR#H_}Gs;W2gPyAFPK4dCHpG6A z^^bYkaluETy^7N{1KS#t4Ir&a7Th0ZHK9t{gzAJhJd#vT&0f9S0DP=e|0hZPbnd@* z^YuS1Rew0Cp4vtAo>KQ8P3pU$^P;8h^b6bCi?&w;XVfy_P+KLsY~R#x%=uN2Ffi9N zPjd#!I5s5+rLOY?xGHCL+fYS%w5fAJku!%$v*yS9OJd&!g_r=%m^Qg$rA2!j zg)dz`=+k1$;Uz^0p9fdqS)n*WyYLMa(#?`h`Q{u4sR(%iAm)D*fG|h1v|zZG-XuDl zZEg~G6gD^cx%S3pWv5JsE9+Jw=i=Tach*UPqrb&I9AVY{Olrx~!;o_4rNfHXx3tQI z;3xafepg82mI-a+hlZY@;o#>|+?1B(YmEbRpy`IcMN3Xh2!j|hAMIi}Xk!nS;~d9K zcQVuQfNL$sWh&HLz8rOI6}w?9^4ZtJ02|R|d%U}|9*keB5qcHol~AX+cgb?_ovp{w z73&c?TB=vIAZ@A!fX92)Zt;cDaR;$)1k~0S`c4p<3Lj%D0VO>*7%6gfTCF;2U4BgS zLU;1kmim0fs+}SfkbPICm+xZH%(UKZ9(rJRc<2cgvP`ux4;2+JP`z2FvDs$btc=B! z>=|Q)g$zCQ3-Y&ZkJng9zO$9ASjf=%u#gm~UP#rdUdX;Or!&I}LhE6_A2@VzwAb9} zV$nP!KQYh98!NWVlsY9~mu=LKn&qrG6H(u z7B@4HcUQS+?xKNZiVlY^AZi>}**c7kn%_s(4Ps={dVWSw(EODW)Si5ESRu z6MWTo>FtyPa`Cun3fYO$-kEG%B5F%msPtuSpG%0p*sM^{k64@R8#Y@HXl<}}fK8ko3`0|>j0MWHXI#Ma zt=I5u`i4{@q*YkQ=P-Tig$MWD-ifruP7x|>#}OirE1NEgHCK~pj?~E;ogPZ ziThXCdX+8wG<9ZsLpuAqR4Tv@+}3SHZ_7S z?M3_duh*WE-ajPAB|C)tJ7{4PL-;KH?htV&)7lKldb}5G%KO5o%+ZNl$fUy_V@0i^ zw~+}+5e`t1?s=kuB;JGmUjhmaA42PGXDZ2%7)^ZwT6!pSH{y3in(Q!|>x7F2*|SQq zMtQA~7R9!ufKw4*?uI%cko(~$qg5l zjUvfl)r~y0(9Fwy_;Y`|e`-?r>8OErveiIhEgZvYcg4%1xv#U`bK6z!X5Ha`nc=Lv&7%bm z&@pYI>7DDkrNI79!C1&tD}bN(ttz`O#xnbWw#7TE3Su4Z+2*yYi{32~Z-gTvkz zVdukQeb#d$Lfv|awl34*JOh zW*^KLDeG?f`zZf6Qh#LMneSfN9AYIT&fI4EM4C}Ci9%G8cv`=sEq|NOh&EqKXFBeP zTkE3NovInv>B93OZDZ7fCXIz>QYS2cxmt4PNAW(s1;@`YCexy1o`*4wyX70ASG~#i zMcWyr?pS!Q?T6Qn4A!KnuNGn_qSsMmWpbleXjQQYaxWWcJKoLvRMe6laeIF%TGHmb zACK&mPeuLzcs^No#XwT%JzX{BKFWZ0Gm!Y9(tv(MD6k!VR)Uv6?`#U*o1AdZ{8fLwEJDD`rNuG z_A(ib9s^)Y@-jF}=lK%;)-JXFmPaeLn=}?Jy=2$Okx*B2?nxrptDGRyb#Zf1{kSyg zKa^U&lnG4esGRZ%6)z`kzrtmawVHHsauSb08^Nsyr$$&fpgx zNvF$%_j}AAM&_2-nms!kJNIAP_crsUiI|e1Ej;=I-JwukU??Bw^`p%m3rLZCDlJ*I z@ZZ0+u!Re)GVAD^RNigYmTgq_q8^OIuP6b(y15nKUWIZ=@VXa^Z`5O76oB zWyY_CrOq|cjwiqe#4JxuxX)Y}&5L4p?zi2yu8jIlHQ6i?VbRcjuL1Qj_?t*DS1<22 zqv(kD^5w%5p~-$swhOQN39jWM@K~5<`GBi%9LWoAlw?w;%5em#?#jNM-nzF=$=K0~ zW3+GsgO9-mfngS_IA5TQA;3NzTwsz;r0Dnbx3~wdiuP$A<=%l#@C|w(6Rd0hOmtw) z0IDC(y1zeeR%~U)RH2?6It)cK{HGhV})-g`A8i(jwZZ*w4tIZT3<`m+v$|?tI>xG94 z-d9-g&xN*EFh^Bnt-aR8e#1hZNN+5MU}|AEOzC0U)YFQy6$Td&Nnyv=&hs}Zi~Opc zGg%8{Fz1ZN3zpEU0VS{J$sCwL``!)et$t_ltvnDrQqZdLL-bw#?@_;d_xUXyuDFF5 zk$f@Ax_|v_)cUTl!(jmv@n(j$$NeJK-u5uB4J25f`&cYWn8&cafi_gRl>u@D{ox1h z>fY8`fh;B>438HVcd@Jnkn+p%7Di@^Dx|RZ1|Zlx(;!0Z1urz7K+?7(@d#gk3HwP= zLm;}&Np=VTDIi+~!Z>cp*#(o7G41$YeyVsVqa1z*CF|0uG>(cY+KD6e-FrUV-1kb8 z7By%nM0+YP%PchXovWZB-N$TL_wJ3+f_KbFE|QshMlGTBKrQ1jwHOX;alhOcz4Ewt z?+XoQDT)OT?R()8lUY_l_cl<8rJwwgZR|tBJwn#+!OusFs-4MX>C7#VjZ$@TiAAXG zOb&v#>kJXUa6QPWl3nlddlh= znMS`U2i2N+BE|c#Oy2)7?%sG!^g1v*s294qAdvFChS@bX^%NIw>)*P zr(J<}rf`MaS3aV6Ggz$<>e;}W7Q?IaCqRGwC$!!z-IY_-sEX9B{gRF#uAjSP+S@ZH z9!gJe<5V8?8!OemlaCGg6X`w{Ysl76H*_<%+T_#jYu81&^o!gb*F}rnQ`h09?I@(Y z$;xdegvMc12hXlztg@^Iy=mbt-iMa;NNo1~ZzE2=lGh;dTXpZyLtJYmx_|a4Blt@hjH*!OBOfACt zQoG(MlOr41cK4#|qw5YGs*4ZJa#fT1gL1NiTbI1oUW$Y_)Af?B+$U&D!KAG>L~~bs zl1s6Dg%vY#uqB4L5AZ+?x!2wh?VlcUAHE@aeRjYENPn+;_=f0nnN44J=iV3{ntsG> zxskhvo^n6Ck*iVqUF#RgZt+>S{}-c`Cl5eylPSE6g@*SRzHuiS9~p=WQ}~dEFj8>~ zrUzQ^4fmR!-i*^TJupWn$QH@9_F8hG^EjQy-ns&GNAc|l6?%EavNZ#J0^CI7o zi3FxP9|D={YIJe2o7x=hwJ!%W=*1O`Mu68`*ig5oe_zo*{DdLxFGjvn;N=4X=u~z4L$x9UheB`OzI3<9Wb-@%TM}hG#`p?2b_(E^7~>B zi;boiUo@bJXV&_)_I+X6nopp!nU^cS62#^1_-gc`h1Zdp?uLIo_9f^2wTL7(u-X1N zSx3z;ZRuV%uso$8q6S@MV5(T7eCY7TfR0&X05unW1uqP^lWvYya5r4>X09H($K7;u zbXmtepCZg{zxcYX2i)tnMDIyo-}KhHVe?|dhhUUq-w_xu0;m6uI2bFR;M&U1gBGo0OZ z)_iG7yNRxNxc2l}Sazno(~{mk_tG1)FYUv|f2k^-$ykiPq@mOk55muZh9$+Hxl3=% z?p2tJHK2q4b2h!R+ygge4{hm#5LEf8>$xd=L5 z-jsb=;}aZeX}){vCaB^4uH)wHM|i{Gl-sf|acghJLU_L$yp2c6etmQH#IF0OqiwfI zP+~EZk@!-*Yx^$WA3T!Hvgv#FSWOnvMv z*?pS7@f>!OUHR}Mw`5=4gcAttde+^#D!cPsc{-kh`~@g%6eZj=mG?0Dxq+WtwW)-E zxYDiJ0~zj3xANfD6Yh(*W)EY2G5!6P8!KB znDIQSKgci(p;q@MuTm3o0v_V7Xl>^;w zcV-XV%zh%`Dsg{8eJ*#OxidRwD)~&spSo3d@~Gh~SHC*Ds7*&+UJgF9MqQIGchqX0 zQ@h`NaJ9XB2zKx0o>`sks=xjYm$@t3wfi%06XDN#;>tHd)XE#Far50fP(->>o}%)l zRPt#dtp751*j+qj_t+ioO?PGgb=LK2(Ggz_kuyq<@Gmu?iobNfzAM{vOc*5S8gj1r zuj9}{tuIVWlqOJP;>R>>H9nTu@1MBaD^ZVY8AjuN*WJ9dcDdVhclOAZ@l?ZTVqqW2 zlvG)ogVX_OY?5wp&}5Q@iQC_IpSUNx^URchr}8+a(Ie+kox?r$-R!RCv@&i{vMOX| zUubiPT2LS~tu3uAF!aq!gA&^${|q7xS`3B!z$U^KKa+*#Y1Qt$HQATT-7ERaoBO1Z z{{r#VWW^?^otF>MyF^3Tv<}=HG!~^RaoQw84S+kz%Y|_%ZZAZdFURCUvx2~$EmIr0 zaHNf09s_-h=q__z2Ki79Mv0rqhx;KP-hCI+lVscx{6I)gtPI<|R>o1nELq>WVqYFQ z{K4;KKZFtsdY}d%ax|1$kpdr)Nq|nslfymWV$tRV$(*TIR`BK_JMXkWKjyr1z1U(rcYp}%-0PQO1hI3sT85BsK2|Tx{GrOG zy<4NFWD-TdvLsSw*xG}yHL6@1pS-Hl*x7RUwjcAS*BpLmfN~JYA18ACIP65H4-#zt z^sTR*{z*KBgDY>c`&Cc1&Fu8?AgPnSRyuYOk1TzWVZh#n0NcG3##u zW6H#n;Dv-I?#nKi>Z|DVKU?UmREy|X=%v5%vcq|Et6g@U=tG;eWH}+GIri;Z zRWr@YPke!!tUyL-WgWy=hT1t+xS}clZ|u&@t%!@f49G62F@u@|+$16~bS%`=F^kC(3=0 zujYciw9;lbRB<0ZQVU3eQoh`USE!1dq@qYSrrSiMxgK<<3cT)fvuQ`BBgxb z7Idw6pXu||aTvd-%7AAa_OGJiEXoRzz`sL9Y}NQaszJlN^N6x+EBxiu>l@!v-xrz}%qZk&X-$dfnL%X8UH?6HV_m0C-PPaXABFz;T&# z3NtEewm=W=+Z;V8em)yS?Zt(=C#}0=RBP?T;~XzokvKm&VcEZiuCk+aKnCRsY1S}g^ecZU(KXf^V0LV+|72u@%5syiBOaDTJeu(3gW7Ip$F(J^hTGI zaG~NCJf)6PZZWl4Uz5-Hnn%j5OcB<}V14uqK={pmQ!gmOlvLO03ss}I%~<=Sq5`7{N6;Wo{tz~Q^a-=E3T7U9lr)7J?kc9CnGBP0OaNpQNeR&SQ4>VxpdqS@#cDvgi z&Nf%>HryK~P|J%JVjhLFmh zLf?xNLrO7MDs01oY*bXQuhLXgoH8!N2L~dy)w7?b;$^W(8{q@Kp1+GVPs~C+rr%?G z(vgIRJd9}%YB%Kg6Ln|73Kou4VO1p1aUbMy*S!#n)lX58dG-BP*CSQZ%Y<~%s(|?- ztA+DC#8&|EWjr(|c~^n3#YgN}Hjpr}Sfrdq782mT@fm8!5A)a_;S_lF%~AUyi1%Xb z=(sGb8eD1d!aCGdaSh|oCi;Py%p#HBO9A!<(Yp-Fog%q9NHKC6K`%iCZ68ije)3LK z_8u<(^AFMZ;9L#6m7k6)fsHCcs(SXT4bOjNxgZq{v!6|fi7jqor>xn7Yw9`cuCOjv4v ziA~t$<~+V^;}iJWQ~*+TzQour>kYxv}`!dcoJ+aI)8Hxj~t1EuCy0QTnZ1yG@SWycBca;7{wN@QeVtx6~0gzfNw)_(4Snj_l(JUkGPeOWao5?8~C+* zWf#&PPb_6{g5{Ij2?yVO%K8o!d*L<9{p)qEK9Z-=Uf#6$(s-EtCLV?u)3}H#gfwBU zarso+`Y{uAwP;eIqhQutyn-F`_>PIurDeQz?~T-4cEshr?8SMi>-S;W{TR_TJA`oapm?S<+_L~jWVPA>7&^_52ZNr=BqP{rK)Z_ z20p9wuD|1Y?4$1GKVc)D>=@rfVa=PPp`BM3Jmp@Pftbir!Ts5nal}VVtd+Oil8YO5>40GH|0AQ-oeVze8S8;<0ipm#>Pm6``vaCG+Pn zmSzyi8EQR~fkoEEV5LPWbtR_dp=am_8_M(ZA1v=W7X@e+E{a$r?E{y z@ffgs{b)U$!2S5AIFP5g=YL90t}!8>af(cP+uyPO)fdl50ut-;r)$HKZK_d>CeED3 z$3Bpo9V{n=!$Jb*5$$GHGY^tDwZh7Io#qdc!r^TKzU`bclHO=yy9Q+c5epSkO3&1W z6v!zmE4pcw59lf0na-IU^6$ukLFJ#VU<(b&7L3`LbYMX>Ux|m;=d7SY#Er*$lj%38 zb9>TRr zyc#C|@!0j@WoCJj5wy3v)wy~+^nD%QsNYw|?92B%&ac4cwduJ`>4H@B`X|_#> zOWelt>K}i?(vnII#}e9Gny1@Pydy4hF7?8&zp;3W@#m3H08aa+DYC>JR4}uzEXJ1K z#M66Ar517~2mu786vA~|CdU)m%*ooauPB_s#cg|q-F2Ck<`oBF6^H@dK1^D_k97An z_VlVYnOV?Q@f(GGd;7`|hIfO@CEfg1s7AijcQnu4b$imp{)!)yRivc7x0_O>hf8=^ zyqU(bOIJ$m@5a}+6!$f1Szp(}OkH0={76{~q+1=FsiU2JS;910&%l5-pRZR`0ifletrq10*^?c?zs9V2m##uc4v*-sAa;A={+LU=qM8S9vnqT{+z+ zr>LAiYI6s&Y%YH)y0lQNNhr1}XB1EjeQlyy?fwSyAT2sz12V7y@uqhd@0^-{yGZOL zecw1q=i*&c^KVP-yHq$`Thjuc{OJa~NCkOla4^ zmT)V~rK1TM;vP4uW0fW^E}S-z%Iz+DCOgC3_B7bL*S@UxGnIo83ia z!d+Z&IUTEPY;l46WB{8a?cp{%QW3PoaDc%;GEGtz4rmjp46BW0TybY)I)W=@2my8} z0nLtP>=`PwQfJ6}CmsISNCJ=s>18tISnH@)2SbXuu9=|d;G=kx@LDfg3Q;nrn;Syx zy|MgV;mm$O+b|}?yh^S%jFDC^^%M@mPhI+0@G-;e!!N+Wo2#(SNYjdOX2@;)MYgjd zn;BH5{FvHSkp$M7OIji-2)pc9V4<30P&5&7t^CB;exh?G`P&N35<~mgj$6K2$XH#I z9ym!Y3X!ooRlwt9s=LSHF(hzv7;tNSPOnq@%8xs7_-nN2ShZnMcI;Z5d-56H#2Ur* z&{%w%ohGl0>F1~lv6=TKggKK{7Gi=U3EI>-kddzR}UCU%Ihni$8W2P=<`KHW4opt>>LunS$-nk>h6FYld`X|q3vnqdP=Qiwq z^KAC06TJ$4Pg(`Pp+5gcV^$225C+nn3^zj6P~nJK6zx)pKnQ(hNEB?%py)pJD}pkn z{*nambPcMBv4m3}gC!idZ31|6q~7RUm}5Ma;1?W=CEB}TiFqHr3L!-&F$kJY{03pk=Ienb>#2-gkk%87gNSD-?A5wb zNMo8Oc^Z}+}C;n3-l-V5TtM?^rPREGlp)-80i^R(7X3 z&1(SwsBG3px|krqAy+w%O!Xlx$+NkJ;#G+*sNUYhQSXhpjYVR|;bJPk#EJT9xBoyZ zos9 zkBD$NOm(vlxQd>K98~w8;&1f$j(+mFJzX!`iAVvQGo%(+C8yWs-ghrA>Irf|~>av%N+G_5yg%0Y}1F$&NS@inGhm$jq8J1w!-rv8?!$z zU3s}n64wW%6`Bgidqks7DmbqfK!p4Kns2~*#0&vAc%--h33* zxggchxvR6W_33!lVctWp;K;v{{xXePY7tk@m;GH6%NnFGCt1OI+jVLxno|6R90gvL z>sK(7efX5!y9?`0<1?>GXy+Slg6aEDeZ%MVps224GGO7=p@9u=e<`XtjP9O&j)$#| zeJ(q*YXFu~DCMQ&Ut=plOFF^5@44(tPVlxQ!Qk2i1}B8GsDdE<9l;LONo)(xD&D~h znH${Sp3BaC#rO$PGhZzV&$$`Oa}GlJ#Ny;14>1>fnr%5kQ@RzpW^5`c-9Nc+iZcNA z6Zob?qWW8zk)H$n#1*ro?7l!b>b$&ECQ}1Bh!L(pgj$W$kKbmn6?~Ncq5M@fZldui zc>L2%Ml{?AExxH1Dye1y*fi^CGy*IHO60-Ws== z&3F!1xJk6f3tMce7>7yfdp+g*_*Np{gY9kqo}$)?h?p~3A*j!$tb=TkuQ^0F0v-{HTgu(rTE2&*IEES zeCT1EM}?Pr0IwsiWK(OZ5xeFbz8T-LbJVarLhtIsOsfg-v5yChssYXslF$?KHE85- zg(S`)8sKd~%Dj{-xG*y$!pDFM>aORva?{aBx!p&fAFM|mZs#8md8Yfu1{5k}_m1b7 zwRdm!7P|NgPw-FiTgF#SXn8pcWF?deu@ic^PH3Gi+qfIQ`GwrrSRArm568P0WnbaAKjN_$SL zlbgY>0<9nR+0?Kb7a^%p zUjZwA5QkD%ejGXLJUWDNj5k4B!#Ik#DpWWh;AmB7f`pQb-=7luu;YV$m~#jA(acgr zBuFX<&5S-+v(;_>@lxo7faC=QqGRc^FvBweVKklzFm`WM?^@f-FJvq|PUg!EZSL{! z&F*ylkG4*25xo`|S$*;PtXuy~uCx8M`Bx}Br+{E(n#IO8cinSQN2h;JU`1_@jdxHQ zc;WNesd)n*i^50ra@U*9bsTk!$8;Ebbqb?#ax%;F6GT!?NH@(VBLT9Sa7`GysZAqI zgAPNK#*ETG(I_AU=(7v9xs*Tc6N*IF$pMOsJKo+7q(Q&4p|>nzS1KjEZ%o8`!;+ge6ONZsqfu_zh!q`02AVh zV1F&%Ylaka|G$DU2CEz8FPahg;%@(noeN+o6pqR-4zzOAWj=?{26vEGPv(0q0zu5= zbAeAc%X_e6L5T`U4TU54I9&enAWv=!J*i3oY)pUkR{$E|TY96DQ36<`$27AtF$|9d zR5}b%_#WksN&BsuJr;s{>SPRWEFX3>QpZtjRfhD2^3Ut|n-w2+OyNjvgcuX40p(4~ zq`W>$0F-?!^4eLv%Yb7QIgZpS(Po;;_o064jsR{B$F2N3Zh~(cX;~$6)RB^O_d|=b zH?*LK&5BPk;^}Q&HWmF5A$`Qv5n9KHTyfx7wL1R@kqA8O zq(XipTZ!XAmYI~N51+EYbeguiyiFnV9Z)E*QY%P;Dd#w4aUB$I(Ut}&BohNe zn;7XRlFAi8qB2I@gf{OBMIG=uF`+cwC-Vh54FQzZ)dC>+W%cmH2@YM>2q7!e$CzdqZ8^5~ahQ``n-B)a z2_%xDZcWO?<#2sPwxQ2`E7wy25@9c5P7UGRD-*X5^`MU(uw^+(DCpRcf{Vm(uP7M7 z>F{qYNmh#owLYTi9!w9#OpI!AO@(slaGeupJz}0`t!{*99Aa{X(`iA~j&QmU^oP$` zuEPRsHSQdjn^6fb9{dGl{$M)f>Vps+u*X>@hE~*_mXojf`1eC0U@|mDCc0?1+ofueA8FaIXxlCBKeev+uK*IhQG*X=T4miD^U&+z<XFD`tmK7Xia#yZzCHQJdB>w~98M>k*o z5ZN9L#V6gzr$+l{oEw=My)5%R_xGvM{ARZagq%x`-`v^1-p99e5=JImVY`^T* zH%Buv-*kUzjuvGG-JaQKkIZ%MjoE1L%#Ym1v(Z6JygC~l*m}Dn11@{|vlbap{E;h0 z(R-(>5Gl#5teG{C=xpVIlxcTG#9Dvj)didmPRd2| zGhc8Y%SE%>XCce#&1uerbxm1P9CA14>-*hC(0k|{4LL`Sfn2MW=tnKf?T77k z&#iH9>4?s6{M;Ha_a*M+og&9(-q9H?$lU6_*lEjJ+ezPz?y1h`tOoH9_u8)L(9BKL zfamLn-5p)*`~lb4&1rwz?WZ4qcc*r9zSWa!cSGIOEVSd>)!i^HYH>SHi<~znO^^1> z%ysi;kRUwG1U%uX7B&!Jc`9C&KJ4a>ogKZ3Udv}kZzEvsud}0_x-QO3Ylcoehl&V{3Z<6fC2r51 zqn=mx6@Oa@tj3_HkPSU7LP%gQA%Rm=`l$C1B2oBUyYvV&R^oB;v5+EObbDt?iy$in$6KgXbiEt$zuH$B=VGXVus-VZR~ zO)?{hu&cXiZq&8!MZbdl>G3kwnvhCn7pb(#m39youu^SBDlgtMI>p^OH;Q&M!YvxA>rIBw&jC_BdAAL6cMREzQ*dywi&Dh1? zac>sXT{umbW(-u}>-)QCPl_EF#_#Ihx@XiiWB3!mM@HtY%v7%NN3Mox5mZH2tLnjrB%Yc%YjA z*OzZcDOe&)Sh?N|6QwSN^noN)R+C5l(HZR8(dq;2E2Lc+y}OuXwg%+UH5K}hECTwhO`(q~?-%u)43i?n46s~DlCL{!c{ys@hT4fAFRSXctEspkBf{StWN>A zx&etl3+jG0O`IGkx;+j=H@V*dmXC{uZvc9KNY+JEc|eM14~*vI{wO7lkJPW{r8WJQ zf$V=u^@}n2(U(TMGJO3((fcc3iG4*(&6^4nKo_J@)f;#m%SeM^bIsF-(K_}pI4|zq z=nN!Q_xwRok+l>KMswQ~U%D}CahD$)O{+P9iKjL^h|*IBJa8Ki1~8YnmP5EcKXQj0 zf@t}9_x?koa(*2(x{&ZBcy_rphahE6CMW;rgWRwACe${D-w%0vbQ6Pw?6KUKP)=G{aYU=i+}Ib?Oh#fjc+|Xn$sK$gYJ`uCra!-K@((7 za75N_l`BJO@TzrF@rK6UK0efQ4oC*L8aBGA<>)-GG`+WMNHBImjXzjtLYZ{gcK`cC zi#q1Ws1KU9kZs{7dS!!(;loIh;~A(zp(3K8W7!T91l)bWmdyk13Bo zc%eJtuxM!`zW`46ts^2gtLA|60v0w){*^6sqx;R`Xf|(OgWmQb-h-kM#`XM3y{kYJ zpJZJUXLBVLCd)%kwsf+M7itNMJidhAwH!_@rTAz$+Bb%M3a)KXqQ`u1bm2%@pXHjm zRZ+A6718v5kUT2yOcDg~-_g5Et>^c0K^s-6Oci;ix%a;ubIy3sG=2L@j1&(9BSrXQ zqLV%DmC=kWMd3E_1WT#Mo%_nD*X!`h5o;R-5O?`2qXQ20uY7;JlUBm`gX)&)*D8;| zYAY*a-!arv?&m96SV0XCk!`JV9mkp`d(^SfKXh-4x?2C83tahCSO~r&wYArsct+$p zYd}gN88GT5cj|G7C7*FCj>Et+D6e2SGsq+8U-?xzyTdvq(DCW>S)gf zM4N8caC~%aN>Oa-Yvd*uP{1#7=baLDc5z-CTPUQ50NWx&+E6e=IzHOd^VH?oF2UyV z0ONX|dKpISWQ;>kKpuF+oqU3sK;C9!UK73VHRvv>!gGrY;Fc{~e5RkJEX)(A&pF_7py*Zc zO5Hq}S-r)k_!r|8GPfry{lIIZS9Fu>QT(fYw>JIVk6#<@Ur~iWlKGePiO#THZvOLe zL9ekobdOh=nQoS;$>nv-4LXn$qq~Iq606G(xAwKfYe*K))oTe?&y=u@q;|N0e~4b! z?lmpU&q*iu$A5_W=2BIWQKpey(ptPtkFJZ!ZfH?aI%EMBt1tdmaEHAP<64(H`E^E9 z7W`B6aY>fTFw*|rwvCQm1<7G=ie}G@#0QgCI1-^m2=e|nMKiINEd57RBok3QWpqW_^Ky@uTRi*Rgcurhml7ILmE%A7;*9JJ8}~t>2dhqIzB|7*)%i^ZMw6)53>U zl_Pwac|+8RLC?@)(Ho+D8SC^nM2jh;R*N0b5s%V$z9E(c6#}3eTE*K{X`nW}F**I{(2%} z_n2O9>-bQrhlD5fxa-Yk1K#-N=+x;5PZMLoIVVN4UoqK6(kN{Yr$E3H3AW}2OMKCs z-#^KWF@HKKI$1)}#B-kemT30Y0`I@x61|@7au}bed^p);48!ySt(1xAhRjQ%c`EwG zi(L2~+dvgls)i6PZbkw{C+vljQj)Z{kIPQdn>~2a8th``Fof+QsdR(LQ^;wFS zYqY3izOWf*qp0yPc9oW*bL-w3&Dc*FYV*Bod^dalt42p{DfJu;Fj#|hR2H;SI_hq2 z_Q}y+bG?olE2WLKO6ibD$B-$d@1pTeR9h9TNUAf=ibZT4>n$fo)2Ga$9H6KLDE;e` zqazQKyGg8N9YDPh))O2&bvZr_;yHcC9T7sH#}#p?8Ios2c#Pt?`H2Q_0)0{qL#$-9CPQWcl0vrk)wS z5|`QuXGZ%pQwV{LJr}uW&y1!wmWO5Jb}P=r-EyUS>P*CtP{$ZV&1s*nI?Ib8)7%Hn zLUQSH-#jaNqn9g&a`K{Zc#PrYij_HgM#`W3?n|P1f%GxpkmdM6aId?#*%`<=kp(dA3hLJQ~g`jWJ>+-2`HmJL+rxfk9UbyY4L!R)C3)>aZs zEnmj0YJDBCKWU?kc=(sfpH|D6v_vbyK{t(?PGwKi42FrR8xY+az~^+e+R5nJT>c8} zx@kvXY|^gAKlNaf8q4a+slM6$;anV4VMYFEk%RogT;F-ooEc#a$#+_t`~$yqZ#^&S zIVMb2|UqOsXn3nGrD!p`Cm&`(WYHA#SB?}CaK`!3Ov(4H?yv8H z8eDOH^tzqWg>!YYMnGG$FNr+UYv#+xE+!>Yr`EEDwP0d29MtXfhS;@f6nodZq6Mu& zZQphsHixI&x84=0yeq=WMco_tgVod(L6mpxSiM9%j9uZeCv+|5~gh@+|k` zcSncSe|44H>7S!_>*po^3_gC`-S^MY?B--szyD{hK&^^=J@#z4<6ooaG5g0vQk8-^P- zFx6ax15<2-S?;O#!U(%u`}?BT%%-qCn)h{68EM$YTgJcOPj>) z5ap}D95NYH#gbstU9|4CofO+86U&kXh%Tq& zFcF=!K`RTk^QjU_ZOl>4DNQcNNm!s1SEH6ZreuX0qFd4yU4Ce#g zEP3Z(%|5_8e7?4r&&pEVl>*(?2?YEcVFrcHmw)yUEMT(PDh2gkrdo5I0LpB@S(}v{b=n{K^bQskJ2x zD!s%ilOjV`;I^eQ9Bz+znJF&ypQ>8DQ{`YG>Uul5LWpFynD~SEc!PbcXfB}W7tgm8+8^QPY0@ubQ)$^XgqmLK26ag<2Bx<+VS)-v}z0w!eLXP@iq{l@rqep zj|iXf8E^E>m7n+p!s`A-|JdDn1-R21uY!+Qw+L%%t(4jJ@@hSe7q{NvTQ`)OENRMO zWjEDRo5dHlQz`y#cg>xJ={Y@vV!1&)>9+L3h_~&8i3&mdZ=hJHqN+1^;JYkmKOvUt zbT0aE)N|%$(S)sS2~Eb29#G>uoEqA!-VWvGu)Fy4x!v4*z8KAQCwwy3u`9;Jru_aK z{erq@+NeKQ1Ctt%;08SY!e~aEE>*sRb2zx1s_=KPg`56w0?|j-q(GF;`gTuW7`1L5 zT+o_r09Tl4tH3oU2A2Q@`Dt2uEJP|`(EmYb{o&J?yAKQqm4;TRhxk$s*zk6 z!7a==G2FWFcHt{M$bJ6vx!F~8%O>!1zx__Gt1)%sUR?D0=1*`y#W!#QfKjx2D*$%D zHUTWmxlI6jM+`7n8I(-)7782}fmGfPv_TiUA8)ihv@RPrOxBt`Vv)kb3N|vFe!97WC&wLGnbwqvLMf#v22z-w8=uwveY@hpTNti2isI2xnDAe3}r~C#rtRb*%FezI`o7&sHgkb zvS>EuIG5SIeR|OX&~3l+_=V&mci+dOrixzdw)b+w`>X3r`VbG(uHZpT*i;<0AZW{J zGDxbng;l5Ig?fUO`}yC-oj-hn?NgN*27KBrjx&bvPsfWTiUbs&P_P_@$OY5h=M`Si%Ai^iq$8>_DQOg)dGv28FJ-M zMvE)^NXdXK6>rLNQWmfWddGj{7sX#99JelA41u?AQ84t^`Gvhz1y0XJq$=Rf=`|HY z;X#l*@pCURq)MY8Nt-)x>Jej0-2xwF8^z*iH3JvWNNhxUEr0cH3s?(TzwWIUM>9!V z(dNMmf8P?B(lYq=(kjT@v&=<8R#daWX#eJh4@8M%S5{QbvPElXpdJU$EAqm@{;DP` z0okp;DB5E&!RtWshZe(snbnV_f=4~z02TPEF?@Atz2L&(*E5(2@r@}Mx)VMXO}7_V z>BbF}96l%vl^m{Y5|U}kpL!C>S_vmoN8?H?`1M*;?n5mTV0~FF@g`DKGV(2xHbazE zkPM_VDBq)Y7>nu;mq5LHxb)#E0W3x21sdKA>hc6YBknxyZof4*t1$^S9v+MYqvdhNestT67xwE(JIyfoR-v)Up%R$qq7 z69zM;r-F~PNWK-h!K-3Fg_=o86p?3777X$#iBi1Th@&N=?5U>9EO~^-vy--f4-(=_ zxm%eN+NmW;Cro4*;whBBR+%E+G}2$7Nlk@4g#}QuqVXs#Ob73=l|S@SN?h|Ot#a18 zD?gL#QZjn+qQbPy9pt1(-4&s&-8WN(#g2W>Z^*Y2Gv zfk&VKzKj4okkQa16E#%ngVOeu-&6hxEi2{eUJjg~3eH}flr7CN>BK1*3Q@bRTms9rTSRgAGdJU9S~Mcl<4VImi2B?Pky)7kygF1-4I^b zZgoq4oSo~IysI-;iK#Kh50PP|@?$#WsxJWRyycp&=BO(13H~544V6w%HBPv*Wz#5} z#DKn1xs8@(>$wjEI8#ib#tM6eZ)H}9t#FH%`j3gkvhtt-cLx!!##Ge-u$F&UU%o>U z8iPP3ALCX^3q0X7QK@iseSS~fCY2NDQaJ&oijB#Jw1+#nj#h_OSRYs@3vS?^2$k)1 zqXyA2yYFJ3I_ymO)2l(H#^RHlfIVhpZPaQLXvAddSX(?Yf!q z+DALAu8Vu%vr)EhlB#syO3YwewJlq%*Y<0+#q%0rTPjaHe!1gUyIOXr4eVDMVaN#Y zYjB_VQPfiTI*T&GGs&XASzR>PA&^Uk8UslW?`>F^4WweTv+g1VP^aku%TGj}kSZ5o zy3dL&Eb&@fV#vl8+EmlEeQrDZv#=K#%Pf5xQ;c8oN*-(#iin2B5*qI`RWp-^km;d= z9w)TxbhRYwXm>K8R z&l;zGy*06X@j4Rdr=Cp8cq(H&m?gQAexo4^R+Sjy$1waZxlYNYtrq?hw$oAcOzE?Y zWHh_tzS&v(T(yPMx#s_!)1dE#USSGhsgdH{&4pbo(qrm^x-THjlrswSA7cXw3EL_ugdiTXqkfnB0v0rAmkJJ* zuLqglm$sn2&n#%BlgY?9C>ctW*2Mf^$EK!cb742T1*WA*fUugWAZIrcq%=6Ig83gZ zGVT#q+R< z5IW1=ri$5M=R$}nzbK2_F1zj(7dezMTip>#$5H9d1Xdmv`C_zN>riKk|6kuJ{y*Ja z{>4azV{$z0=`(|;oM5q@?XN|1Aq4?Y)BQ*iK1`Kg^~wB ztcO;QR${-zSmRkSVm-d@quzaw5y-09kb-3`Osa%@m;bI7N-P!bbcaXU+=W*}9Thuu zDGQ$KCC{=MvayXVAzST=bdZMZVNF4Rtivnlm-dm2A%>}RI+2~ZA!C;rV{2C~f3-XD za$F&?M2YW^fCsq{O~2QL(W@qAf0!Jay^}#RHT9KOBZ$oz1ECjMUvJOYxr?rda%cWe zAvJ!s03A*X?IXTj-L7q8u0uDe$sKx4)arJ>60vgz0i^yvY$2FDV1M}Cl?9?0 z+t<#|HTKv;VVe8Qe?|J^`A+G@ScP}Z&7CqGZd>XA@4_#b0`rs;;-s)RBsQEz!9}$>oGbwHytlZYgyw z*^_rL>Rt{OwXmHM?^bAcYTZV=c?xQ`7ZXUCJiBjhsjc{UJw*AcrEoBe_AjOA*L*@= zvGi)7zGp>Ca^|2T~e5s8m3pf(w6)XWkevr{v z6AAN4(Jakn8XpkbN4};lM9Y|#UPhR`uP51vb)BZJdKRb0*4e`wwf&j%^*~y686|sD ztJ|&rYBcAy7%k5xfMy=^_RUiS@9GRmMS98rd}^_wrk4DP@N5I2yQGtnin2fLO6{;J z$!%DNbo*uWPz9RTe=XYWWkX6`C29#G3omXgevP=z#RaRbFp}Ycj+G>y_Lh=UUR;=t z7o9U~<2k^`-Hl%(*<^u6>Kjxr*UmnJ%3Ul7s_%fwTEmtUCq@lQ08J^@+Nii)$%|1&r6*q3kUz+^(89&T& z4S7lO>Bag~x+1A3DBkjUAo49{Fi>#eg1S0GGL;Fb=V1i@uqdcaWr7Ni|t%~j=8m5;V4)uZ|W}3J|qCA#9}ubwEGs-<~0IyqzAp^kanN zP{SOLM-ca<6DBobCl~ijNx@|j#}tWfaQNLb(xT?ZuOsg*iiMZUzCrZZW;r(ZC2(vG zP{_oUo>28lgxN4dU`v;b<=PbUj~zLr8sNxr`(G0+jveq9Ttlkp9qwD#$bOApHdd*$ z-J|)GFTVv9sv~inRHge^c5T*)qYaq*WQ=B6E9^s!Y>TD}$J^VGIk;A}96NK^-=(%c zig;L53zAv{j7DAM+Qg@UF~}Y`B|s84Z`G$kc&GU^(AcUzjZwGuTH*{x+@G(F_MdY{ zM|ieU&!09Z!`7>p1uV$n_I=V~rrf#Tj4qiz-g?MmMGg67YSQD@QO&A%k|rPJI(t`( z8|=#OGQmg|)%jr`pe3RLMQ$Yqa*df{AXuH`pBv$etz>d%NF9SD$ii-A@bDxHJLPW< zv2_KDt&?jSNkTp@nuMSND;Q?w&xtn&$NABjK zsIBrBaBLFo*#P!}aL`g<3J5)Z`Fy~v_V{zbkP|J}FJ2CMUD$N-HI^hV8!agBN@RiG zfK?HkT54svlo2eH4lC_K!LeVs6R(So%H-XbuZvFD=j)a73QBnP98}jY0s;||@-Byt zo;$PRfZ84Q)Udk|*LyvZ#h)_y26xK!V9d$aM<-5(3Y**&UHQI6BTJG#!emyzd?Ai9;XL44Bb$>4f$w46ithdk#9X@G?oJ6{)s$mMp4}vwh zbx%e!6_QcY?{j}9()L&1CL%*QNs>ax9S9Gkdn;DJCeO`l4uo|mM{2cTi3ls_lSNjb zYzVbS*yL}_2yFPPRezP^ULTNa6y$pJ4E+%*gZpxL8Y96nYLnLPEmwN2b`>m3znC0$ z27AFa+~wD-%3KF%gO||`1KX=GI zc1x~nGD8H!ZucqMP`2C%-b8T*a$EPyU&+syRFp6NHqLP+ax|Y*eGCWML9V_0?I3@s zTy9sS{6#gF4)$*7Y#be{79+?Q=5ZfEn7T==svdWuY%DyMP=dl#clnb!QpML4W>4l? zudWfn!>Cof%D@klkC8mwBRc16&=`0pS#Z+x-HfO%cqsQwo?r@{Mr8C#t>1UDrw)Is ztJvfEKarh^X(Wl6aHrf59lbvRu8D(?KodQXxeKBEipV!)wR@!~SPPJm7lIQVaX+~s znkFUkyg%lq9VRnepjrN2$S^&YP}KvFP$yDa5j%--&Hekn`Dtf~xP_{y{=L^w5|SXE zP!e*bE{iP%TV=cXBLk_P;t8Z^b4}StWGWv-WrU1PXk<6E3r`NMAC98e$q||>eKTFL z@yRXRp3U4Yqf8r&?oCD&e{SENJ_%LS+}P?Bt)}11maBK)lu#iVunz#}0CBmSR54oY z)yCj?qfnW7RJZH}T1Q;{gR6fK@gnHwZi;4A#!qF-eB360l_U?#jek6GYhM%0kP>F^ zy0XO7e?8J3mPE2sy4T0*dz*MGQM2p0#9kR1?Cp9D$qP6- zMjg=@C~wo4s4u4FEJ}4LzovPm zcCBEZpBvGZ1lkO7YZ$FDx%`X!&8nzrW@wbthrR8xTp+?o`D%5=TcYXB+W0)CaC_ep z&78uL-Ep@>y;HP_rTns!Z!3MxxV(P&XHeDjo3}*MUaQSuK+((f_36#bn_x4VNsaq} z3<{-jSN$VYMFG__pA6@w-5Tuz`0Vf0%5{G?bG^FIEIk&%B2kK|HRrLYIlZjBPQ1Rt z;*h(r5%sl~@>a-t;oG`UdFUNi2 zwrH1*&`#q?I|c#fxsltVX%$wLDkct!>bg%Q~k~Xs>vek=$JQc3tux zKuV4LjwiJyZ~e$<{se^Bz8w`I_HPvgsxSHe!DiV5m=JDl2m)!B^yr_jzf^+If*=SD zni&YSsMK(6@pBe^Cg~p}^&B`1g;FG4faD=*QGT4&VH}ekBtv_tA6(;*iuYOgtO`kw z9n0iqkKXv{Og5l5xit@%V55zfR;adqos zfulxAM|iSP*d?NM$G;b@p2+u%E1a_AJDsw}y-xXy+!2C>a{){Q#lVQ?B!X&ej$`;@Q zO>kC*`DGdm(IqCGE#P&oLS|;^0SP}DB(HTmI8gq=Gx&cGDc&FFZ7weGK$>YzcY^ZX zV}E$-k{k|i^?8&&+@7cmJ8Caa`Iq9_q$s#c(+ z?doXG;z@4j{RXvVO-uT`5ZQ+8e`~fhC4aAdL51byi@M^iu5wp2clHJu=w%(1&teKj zJ0GKsoFl-ylkX)rA#uN&X-OL}c;L*YuS^W-05R}#QbX3q{)i!qNJ&(bhWf+R8Dms; z-!riT$utMWInM^k|W=JF4AIu^oNPHdwKaUA83{n0JIJK86Yy~<7k z{?zPB_fe?e*LUN7{kOMgo88~;j%FT>1T9TL4E1?Ch{h%Pdw^U$V7x8cH^sQ7h(T$} zm=?CwN%Xg#QL$EmP#G*(_dlyGI?lKR~na*h{@1Z`j&2R^8ZL{1Gcu6rleCAAVjk*Wx-H(JpBnSSE9Ve5g>)4TRe>dlVn%T#=YL}^r(hHRhWZ@K*Mo%v|j_>AEUGp*P zcHdYVy`&wGd-*FNfNta3=*_X2%Ve<;FcspYVJMg}#UlW5IQTXV7pg(tut>WtVCHwT zA@Y}dGQ}({A}|MM!i08bE{9a|w=SJm`?>D^m~l(K80Y+}YdAfN`PtGrf9C(}IfsRA z`#FE+S~vdzf6fB4c_%%i+y3Y5&lOw<=WKGuXrLHV>_yk+K>W$2TNmy7TC>k%d$r&? zPxB=$iVroE@A;iYPkueH03TYcE!8}DKE{RUez*?OFc{c9V?lzdVVfo`z*!+T69^_$ z$w1!^*-i3&d|-@dlEk(oqRGVWwS%`wG^w4|ee1z!@3eT*nZ$6#ixu5LEIa>R-jnOKwzP$uwdgFM#Jig-=(&;7hjOq7*%j`q#2sc$GnAhW~~Zsxw(4!7$2QFa1-#h~gBV7JJaE0m_5TVtKwnKVo&3Re_O(x{$PhS&MF&cC>dvuFwV+& zoXLl|!w)m$u6;N<&U*$p7eo4x@E{q|ZZ8RD>w`>bf}W&9Uw}l+i%M%DJeSQ`;+QTb z4VprjuVIeZ*)W0$=Rd*YXL@8xXk`u!R6Ro~%asVYMbC!f_hLR|%u)U>WRvuhVah`B z4sKzi4U&428q66t2na0nC4|ouKWshx=5bEGt9v+Hc@8fAP+_{;>%XFGq2^HBQU4YF zLsTu^;)>tNbstkxtVK4CG^}`1El{d~RxC#5;VvmE2 zY$#(Ov&N+N!LGy=s7);I;-*Q|Ssw;TK}H8Ub`UQ`o+O&BG&2tw83DWmb+4iQXwcG3 ztL1JIYd7!tsE#a=`or~~=*hE5s>lXQ7Tka}L8!H{)4ZX3scaILG@Mt(zaMCx9A=Zy zvY_rcITr+njU8^$C^7z6K@1R~wZtCnaid(>Rz8Hw94D7sS6L}c=@a_FfNI^O7fK%W zZc-2)d7cZin50VCIQ;lF;-}nH;<@%Mb`bw<4>{&!uBw4(@7k>H2-FGDxz6>@ zEp}8q1;(J%FQHquDV1-IpT)#wY>Yz=F@uEp1AW6JLC8F7eCvzh@R!*h!h9?hAS&34 zuwZp*j0rZra?;Jd`TeM+`z9beo*%kz{wO-|l|)%k9iZ3Cy_^k~Y9ABtTNSpq57=PO z=6Ew{Ux=Qdo!ysRD9>d0p~PBcKXkX4kCf#o*pe!sBO9U;k5)Xsf%u+&b}v&_Tz;t^ z-}o|RkgBGE2toYC0AHvkQhCJg6y+4cbHF?sY{sAkb$9r8YpH2NGgU{rl+)tjout7% zofw`yxaMmVEASs3zF(%uGaEsT z)72A7e%R=ay%Z=V21F!k0{^-!#Q51O-dM?{3nQ!pc!umybDvCNrK;#_rK)5C1-Vj< zjV1##-&p2eaT{2hWq1ebY1Yo-1KAWb8?YGkRtED=cYAuHSkKaXa1_;hz$ zw78H^*Zt`+9E}XZ2CX$9H7X~bXwe1y+i!PITkYhrrXLspVI}9Z zI6Ew(_oI4bhQTBxjp0v6=qbyXK@pLgzQVBF)T@?!le}u_TloVRb^rc!)K&bi1S^Fe z<>l^g>!aQ4|1;}O$>loTA&*BhCyN2NRpy@sI|yk4z#W7%^CA$^!VW@e3_T&B+}fDV z{4iVE8EL>ggPSF#zj^Ba4pO@Qsfap+<2cn;lGV}w9$EcZEm?haq7zx{*Y+c;)7-sJ zN9_r1{r%I?%rUy-%nkqhCDV#$MzLfX%xhNl%7mDH|2%h9DV02FuY7xf$g8C5OlPgi zuHS<=Bg4|VD#I$}Xb&;&|B(=jMDP{KuJHQ7ZIfZ=p|J!Z7NIrlYQ%)t z!5SgfJ$PZEqk`PJLIy8>4oMguLco2b-ekq947>u!)k?6j^efrd;ts0ft{T@$ur*X# z`o^%G#3*2^Aix4>Tb@qF{-n64>0w&ScmbCH%BDJINhYgUE5`l`sPns$+itZnH0_DnZ= zaY4zn-HJ6b)s($;`WL((vDq!4nUt}r$ZGHoLRvLyxx-6o%QC89Z9wFW=<}Y9x+7_K z5+eKCU2m59RgySOEVn2CU&;>cE-k1kQXiKkHIbA=6wpNdOj5OesC!JKez^F9P`)nd z_fTy=S|*pKQv9(bbVU-TrE@cpfY57#J}|=1MEcg2xocilM+P@3J!AEtSf-ASBmp{` zlEqI82w88#NB|l6`P5 z%;1Q)ZR>;q#xitVb0)~poKBOQ|Ldq2ZPGS56pT!@heGHqG}5#O%pIvi=~vVVmW>5< zAL^RmP^=&N2EEm^FQt_TqA-og)e=BzzGbk} z9Eebh;*aDrFPMpL0&Qihm6fSMS=nFycfYfMh%AbLIxa4L$A+U(hoYm;Pbn|9pylq+ zjVa~j-!?{hlB)#51nM5>V7?!mEsXybMQAfXO+Y?T-&|=~ItcrQ4DWVMV1e4{x6y%z z4r4W}u|RbM3slF%7O1LCnI-Sy$`50I{?zBAh_jM8Xbgj28!SS&d#v1{$%x+SECz2S z!nulsG5NaD%S}74GWg}#q6WnqR@fI+2ET$YhKy+#IT~zF82qqk5o0c>yQy8)i}HxI z7)q>EBT0ih+N%uS4oK7cxx8(e3(l3Ez(^Ihu4V967j~%ATdA;eGZx^*$RhjQ0p3bA zY-QQKK~Ff!bk$Ep7*yz73|^$nG<^(H7CRu}p%W*9tQZ)nh9%x$q*`f$K$(hRFn9|t z5dHJ0gKe2cqmTtN5W`8mmC6cy10z8m$_STVBVNJJ4T9JPU=_GMrjan#8ek(PsT=7{ zP*(NZPLHbJ6_GF9Y(KMFi`@%;zO@wrx7HhdY442&>_O%dd)HDna;VGY2^ci^>d1`q zuP~#!Ussj$)5c6&*tOvNr{%}9ezT#`j28_9>t7S%*#y`TZ<7~64t}B7vZcC+ppc9o zB8euoQOyMVz5CVQqMb4=qK-4(?yvL4mNc(lC-D+sFQ|K{mBfsRcs;awEU&*v)KrYn z9$66iGiYOSdh8kE6_5A>?@*4qQZ_gHEryV?t(l=jaxVvr1Xr~-txuNlv(|)#shja| zi!W+7y3R?aj7J+eEX&k?NlXbGL%4n}mZBptWQ~?ef&} zS_LuM`iqa69Aok{K$JYKj2I9etPFcp>E4ZZM4d;z&8Nupn{4X4dqY=$YppfvZ89nr zZ2jyhW!+ohZvG?bwF3qbghoxo$~-7LF>e0EmSZ{vm??Z4Kua+37T#eiLbMDWydcG6 zIZ&zfLI=92%sk#6kKp3`er%JDHQaEq_*f(nhXfNobZ`Ar)N{rfaC!qaW?wo=FPL(* zs*<1jsflVhhlpFtaT_s~AOkSBiazVJ>5$z994nE!5|_(`#Rlvi{}Vc_m14%wPx62l zMX3v+--5?P86uy*h@-S`qR37QoSPluba~t$ zWU{KfAL>5p-nWTjwcZoPr5$$^NzdT;#<;2rfoG;&A-oO2hk|XsRVnFmrG)yxoSHdc z;u`{Sho<=~sxdi=JM{*XLcNKxO=|`eqf_1OU&(b=43(gI2WM8d3ai4wisI{n!D6{u zoWK~PXWgUHjJ#70uXGw6)-ifI6Lzf4Q@5J)w0}1p53%$TAN4Y@KGX;m#8dt(G3lX7 zn0V#8OGYG?AtiPgDHw2Ve=)Ix#LF)Cn!iNTCNr7dY$ntH%Q!#8o?TWs{}7jAIA-5z z_tXeqh+BzE@$Ybz^KVEP`!gggC4dHMLABqQH29rFgY?O1`dCzeF7mq(+T;jgRlr|+ zTK_-8`NeR>`3D2z9nbk;1)JskkwBerezo2V=hvlzpY!iWKNi|3VU1zx(A2(ahazdHi~n z0m_R~HkG4eh?{FTCioVIKD$@0=Zxi`k$7NWwisi^F$ofHjAfmz={EGohVk-C^#Jfo0=oDZ z=oy^ZUVt#&KNe-ioya&*MyixIrTSsViZYdSy@j`1{In#MOJ5{OWqG?{iKo=qm9$!i zWo5?RP!dZ3;CQj*^$2vh8>}A0XB%?U$9NlVYRDbv4raTESsMR80Fh6)9-bcO!|H6}Ak>gO;x0#U$@f(U2m-&1)`Feu|D3zCw5kmf_ z<_9sm5tA$=F?j#N#O4Qsu%?GQ+P_|PAL5oyV19^O*9swf82l`92+hRKuWD@wK{CT} zSr;bqaf;X}f`!8DlFvyI0j_qxp~ILnEILnPD^f46PN2vmnIFEQ_SDZZ|AK`;%LML4 zD{){`U(Eb42sQTRhvx&_by+v@`{d>aXh9eYk-&ry;1Uc97Y1n#XG42$1?pnzOjS}g zAq4n&^TS0+>t$)-a4?oMKw^_LHuw*MYzUyu#?Wrwg=oB>VJFOn-;k0tlHFlrXfwOP zfaFz_1NIWz80_hpI&vKn8$)=2W*slh`2XlL?B+D*dXdc%e?udImq9@>!>Yfb$VVwr zWQD985Vi$%S1UeoA|u3z#ah-l99GL#XHE=n$n)B*g|%_^#CCaI%stVZo0VDaGFT&! z+02T4aC25)9fTgZ%|kv!H6!LPZ8c% z#kJU=VBJ^<1_jv@3^*G)DSQxCGG-y(18wFwAgnEHU?H*NZmYMic=^=!ZC1(k@uZbn zpg(PZGqawqnwW&PtTQovAn4@;Y!HUM35ttVdu#&T3S~L}*g?x8HwlSP|i_ zW5SBU^tOlr`&Uv}WSYX{8n#YerYYas%WY{^CKA_Q0X^%>JjlW&CaM;UyHIvJ19*Q| z?<>(oY=%gVfrg{s<-J^T&D59gsVlwGa_tso%-9FS@LlnQ5(965PxRA37hp|!s;q+u%%q17HU8?!$tv?pQ=2iMv92Fc=;O=Ck9ApV=AYHsioZzj9W@h_z%YuW?!h3VRsseDyV4N>y}g%KR;Li;!f*0e z3XtW8>!kg{*V+TL%vm|r-{B#*TT5>D1B5(q)-gW9S9*)n`kH$LB?$LueW&|qOK#VF z2K@0;2VKy^w2Ir7Op>nQxgZoKKZu(@#pc~B?v@=eK8aL2jXUW7dJPry( zc5D9<^+r`0ES9;}*Pgfqx|Rk3PKV%xb#UY*!Hg!C2Ut{Ur5OStde)T1T%+05+2?~*dc%Uwxp zFLzZ!)hKiOAR7TFMlp(C_2~q+PfE%P*(O!d%(&f?=oON?*6SzOVXhu=Kw(PC?W6j6 zlDnv%r|@=VHiU5Ss;dT?$~PdA?u6_hLGFqjHo{Lix{>mLrHnu3bwLD@@khi%WZzT7 zjlf2yC{{Vfk+U;SY#%;c(^{9#q%?Q=?}{%C# zJz7U%>=Dd^n}<5V`Q4vrSBh2Xw_^??HF7HJaxiWUj3?! z&#@TJ^hzilq9w16Sxc2krM#SPspDR)#J0x$kMp^;l>LxY@3UiByHemZqTF$o0>|$w zGrD?KMv=dUD2HT457N(Kx#IbSnH7QB9;SjIvJB{E$b|HCP~-B57Z{~XaWN$|@(;jC zwb$xQS{@+4^;O@Y^@%EA2e0zYYZ{J7 zb06VST&IT9Xm_zNj7RGmef|0;∈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

`tH? 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�`aWnE{JQYls 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

SMjKKZNhR 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

  • )A4%+DA4zXe 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 Q{>I>2I Date: Sat, 4 Mar 2023 14:41:17 -0700 Subject: [PATCH 042/294] std.os.abort: take advantage of `@trap` --- lib/std/os.zig | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 3a3433d819..f2bd8bda97 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -575,22 +575,12 @@ pub fn abort() noreturn { raise(SIG.KILL) catch {}; exit(127); // Pid 1 might not be signalled in some containers. } - if (builtin.os.tag == .uefi) { - exit(0); // TODO choose appropriate exit code + switch (builtin.os.tag) { + .uefi, .wasi, .cuda => @trap(), + else => system.abort(), } - if (builtin.os.tag == .wasi) { - exit(1); - } - if (builtin.os.tag == .cuda) { - // TODO: introduce `@trap` instead of abusing https://github.com/ziglang/zig/issues/2291 - @"llvm.trap"(); - } - - system.abort(); } -extern fn @"llvm.trap"() noreturn; - pub const RaiseError = UnexpectedError; pub fn raise(sig: u8) RaiseError!void { From c29c4c6f70ccde441ff8e40e94c2848fef1f86da Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 23:40:54 -0500 Subject: [PATCH 043/294] tools: add lldb pretty printer for std.MultiArrayList.Slice --- lib/std/multi_array_list.zig | 15 +++++++++++-- tools/lldb_pretty_printers.py | 41 +++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig index 6965205b1e..f97e42cc89 100644 --- a/lib/std/multi_array_list.zig +++ b/lib/std/multi_array_list.zig @@ -68,6 +68,15 @@ pub fn MultiArrayList(comptime S: type) type { other.deinit(gpa); self.* = undefined; } + + /// This function is used in the debugger pretty formatters in tools/ to fetch the + /// child field order and entry type to facilitate fancy debug printing for this type. + fn dbHelper(self: *Slice, child: *S, field: *Field, entry: *Entry) void { + _ = self; + _ = child; + _ = field; + _ = entry; + } }; const Self = @This(); @@ -463,16 +472,18 @@ pub fn MultiArrayList(comptime S: type) type { } }); }; /// This function is used in the debugger pretty formatters in tools/ to fetch the - /// child type to facilitate fancy debug printing for this type. - fn dbHelper(self: *Self, child: *S, entry: *Entry) void { + /// child field order and entry type to facilitate fancy debug printing for this type. + fn dbHelper(self: *Self, child: *S, field: *Field, entry: *Entry) void { _ = self; _ = child; + _ = field; _ = entry; } comptime { if (builtin.mode == .Debug) { _ = dbHelper; + _ = Slice.dbHelper; } } }; diff --git a/tools/lldb_pretty_printers.py b/tools/lldb_pretty_printers.py index 3013fbb43e..a1db03b33a 100644 --- a/tools/lldb_pretty_printers.py +++ b/tools/lldb_pretty_printers.py @@ -201,7 +201,7 @@ class std_MultiArrayList_SynthProvider: value_type = self.value.type for helper in self.value.target.FindFunctions('%s.dbHelper' % value_type.name, lldb.eFunctionNameTypeFull): - ptr_self_type, ptr_child_type, ptr_entry_type = helper.function.type.GetFunctionArgumentTypes() + ptr_self_type, ptr_child_type, ptr_field_type, ptr_entry_type = helper.function.type.GetFunctionArgumentTypes() if ptr_self_type.GetPointeeType() == value_type: break else: return @@ -221,12 +221,44 @@ class std_MultiArrayList_SynthProvider: offset = 0 data = lldb.SBData() for field in self.entry_type.fields: - ptr_field_type = field.type - field_size = ptr_field_type.GetPointeeType().size - data.Append(self.bytes.CreateChildAtOffset(field.name, offset + index * field_size, ptr_field_type).address_of.data) + field_type = field.type.GetPointeeType() + field_size = field_type.size + data.Append(self.bytes.CreateChildAtOffset(field.name, offset + index * field_size, field_type).address_of.data) offset += self.capacity * field_size return self.bytes.CreateValueFromData('[%d]' % index, data, self.entry_type) except: return None +class std_MultiArrayList_Slice_SynthProvider: + def __init__(self, value, _=None): self.value = value + def update(self): + try: + self.len = 0 + + value_type = self.value.type + for helper in self.value.target.FindFunctions('%s.dbHelper' % value_type.name, lldb.eFunctionNameTypeFull): + ptr_self_type, ptr_child_type, ptr_field_type, ptr_entry_type = helper.function.type.GetFunctionArgumentTypes() + if ptr_self_type.GetPointeeType() == value_type: break + else: return + + self.fields = {member.name: index for index, member in enumerate(ptr_field_type.GetPointeeType().enum_members)} + self.entry_type = ptr_entry_type.GetPointeeType() + self.ptrs = self.value.GetChildMemberWithName('ptrs') + self.len = self.value.GetChildMemberWithName('len').unsigned + self.capacity = self.value.GetChildMemberWithName('capacity').unsigned + except: pass + def has_children(self): return True + def num_children(self): return self.len + def get_child_index(self, name): + try: return int(name.removeprefix('[').removesuffix(']')) + except: return -1 + def get_child_at_index(self, index): + try: + if index < 0 or index >= self.len: return None + data = lldb.SBData() + for field in self.entry_type.fields: + field_type = field.type.GetPointeeType() + data.Append(self.ptrs.child[self.fields[field.name.removesuffix('_ptr')]].CreateChildAtOffset(field.name, index * field_type.size, field_type).address_of.data) + return self.ptrs.CreateValueFromData('[%d]' % index, data, self.entry_type) + except: return None class std_HashMapUnmanaged_SynthProvider: def __init__(self, value, _=None): self.value = value @@ -556,6 +588,7 @@ def __lldb_init_module(debugger, _=None): add(debugger, category='zig.std', type='mem.Allocator', summary='${var.ptr}') add(debugger, category='zig.std', regex=True, type='^segmented_list\\.SegmentedList\\(.*\\)$', identifier='std_SegmentedList', synth=True, expand=True, summary='len=${var.len}') add(debugger, category='zig.std', regex=True, type='^multi_array_list\\.MultiArrayList\\(.*\\)$', identifier='std_MultiArrayList', synth=True, expand=True, summary='len=${var.len} capacity=${var.capacity}') + add(debugger, category='zig.std', regex=True, type='^multi_array_list\\.MultiArrayList\\(.*\\)\\.Slice$', identifier='std_MultiArrayList_Slice', synth=True, expand=True, summary='len=${var.len} capacity=${var.capacity}') add(debugger, category='zig.std', regex=True, type=MultiArrayList_Entry('.*'), identifier='std_Entry', synth=True, inline_children=True, summary=True) add(debugger, category='zig.std', regex=True, type='^hash_map\\.HashMapUnmanaged\\(.*\\)$', identifier='std_HashMapUnmanaged', synth=True, expand=True, summary=True) add(debugger, category='zig.std', regex=True, type='^hash_map\\.HashMapUnmanaged\\(.*\\)\\.Entry$', identifier = 'std_Entry', synth=True, inline_children=True, summary=True) From 27701596060c7a8018f9a9add21952a6d7f83d96 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 5 Mar 2023 02:58:15 -0500 Subject: [PATCH 044/294] std: reenable vectorized code with the C backend --- lib/std/crypto/blake3.zig | 2 +- lib/std/crypto/gimli.zig | 2 +- lib/std/target.zig | 12 ++---------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/std/crypto/blake3.zig b/lib/std/crypto/blake3.zig index 5b8e21d922..36d717387f 100644 --- a/lib/std/crypto/blake3.zig +++ b/lib/std/crypto/blake3.zig @@ -200,7 +200,7 @@ const CompressGeneric = struct { } }; -const compress = if (builtin.cpu.arch == .x86_64 and builtin.zig_backend != .stage2_c) +const compress = if (builtin.cpu.arch == .x86_64) CompressVectorized.compress else CompressGeneric.compress; diff --git a/lib/std/crypto/gimli.zig b/lib/std/crypto/gimli.zig index 9443b97be7..0189f4c359 100644 --- a/lib/std/crypto/gimli.zig +++ b/lib/std/crypto/gimli.zig @@ -152,7 +152,7 @@ pub const State = struct { self.endianSwap(); } - pub const permute = if (builtin.cpu.arch == .x86_64 and builtin.zig_backend != .stage2_c) impl: { + pub const permute = if (builtin.cpu.arch == .x86_64) impl: { break :impl permute_vectorized; } else if (builtin.mode == .ReleaseSmall) impl: { break :impl permute_small; diff --git a/lib/std/target.zig b/lib/std/target.zig index 9e3a8d62f4..52f41a0563 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -719,11 +719,7 @@ pub const Target = struct { /// Adds the specified feature set but not its dependencies. pub fn addFeatureSet(set: *Set, other_set: Set) void { - if (builtin.zig_backend == .stage2_c) { - for (&set.ints, 0..) |*int, i| int.* |= other_set.ints[i]; - } else { - set.ints = @as(@Vector(usize_count, usize), set.ints) | @as(@Vector(usize_count, usize), other_set.ints); - } + set.ints = @as(@Vector(usize_count, usize), set.ints) | @as(@Vector(usize_count, usize), other_set.ints); } /// Removes the specified feature but not its dependents. @@ -735,11 +731,7 @@ pub const Target = struct { /// Removes the specified feature but not its dependents. pub fn removeFeatureSet(set: *Set, other_set: Set) void { - if (builtin.zig_backend == .stage2_c) { - for (&set.ints, 0..) |*int, i| int.* &= ~other_set.ints[i]; - } else { - set.ints = @as(@Vector(usize_count, usize), set.ints) & ~@as(@Vector(usize_count, usize), other_set.ints); - } + set.ints = @as(@Vector(usize_count, usize), set.ints) & ~@as(@Vector(usize_count, usize), other_set.ints); } pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void { From 6ab04b59417eb9c7fff81ea26424c2340dc72097 Mon Sep 17 00:00:00 2001 From: jim price Date: Sun, 5 Mar 2023 16:39:56 -0800 Subject: [PATCH 045/294] std.os: Allow write functions to return INVAL errors In Linux when interacting with the virtual file system when writing in invalid value to a file the OS will return errno 22 (INVAL). Instead of triggering an unreachable, this change now returns a newly introduced error.InvalidArgument. --- lib/std/http/Client.zig | 1 + lib/std/os.zig | 9 +++++---- src/link.zig | 1 + src/main.zig | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 7cf512d65f..7f62b2d597 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -658,6 +658,7 @@ pub const Request = struct { MissingEndCertificateMarker, InvalidPadding, EndOfStream, + InvalidArgument, }; pub fn read(req: *Request, buffer: []u8) ReadError!usize { diff --git a/lib/std/os.zig b/lib/std/os.zig index f2bd8bda97..821c544cc8 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1027,6 +1027,7 @@ pub const WriteError = error{ InputOutput, NoSpaceLeft, DeviceBusy, + InvalidArgument, /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to write to it. @@ -1113,7 +1114,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { switch (errno(rc)) { .SUCCESS => return @intCast(usize, rc), .INTR => continue, - .INVAL => unreachable, + .INVAL => return error.InvalidArgument, .FAULT => unreachable, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // can be a race condition. @@ -1183,7 +1184,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { switch (errno(rc)) { .SUCCESS => return @intCast(usize, rc), .INTR => continue, - .INVAL => unreachable, + .INVAL => return error.InvalidArgument, .FAULT => unreachable, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // Can be a race condition. @@ -1278,7 +1279,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { switch (errno(rc)) { .SUCCESS => return @intCast(usize, rc), .INTR => continue, - .INVAL => unreachable, + .INVAL => return error.InvalidArgument, .FAULT => unreachable, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // Can be a race condition. @@ -1368,7 +1369,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz switch (errno(rc)) { .SUCCESS => return @intCast(usize, rc), .INTR => continue, - .INVAL => unreachable, + .INVAL => return error.InvalidArgument, .FAULT => unreachable, .AGAIN => return error.WouldBlock, .BADF => return error.NotOpenForWriting, // Can be a race condition. diff --git a/src/link.zig b/src/link.zig index 24cc0a3861..1ecbeadd7f 100644 --- a/src/link.zig +++ b/src/link.zig @@ -461,6 +461,7 @@ pub const File = struct { LockViolation, NetNameDeleted, DeviceBusy, + InvalidArgument, }; /// Called from within the CodeGen to lower a local variable instantion as an unnamed diff --git a/src/main.zig b/src/main.zig index fb02628c61..b134b7183e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4622,6 +4622,7 @@ const FmtError = error{ ConnectionResetByPeer, LockViolation, NetNameDeleted, + InvalidArgument, } || fs.File.OpenError; fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { From ccf00ccdf71fae7ecd25d9b1bf11dc7eb52d5d1d Mon Sep 17 00:00:00 2001 From: Eric Milliken <11590808+mllken@users.noreply.github.com> Date: Mon, 6 Mar 2023 22:28:11 +0000 Subject: [PATCH 046/294] crypto.25519.field: de-inline mul for small builds (#14775) --- lib/std/crypto/25519/field.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/std/crypto/25519/field.zig b/lib/std/crypto/25519/field.zig index 66a50bee70..1885f9286e 100644 --- a/lib/std/crypto/25519/field.zig +++ b/lib/std/crypto/25519/field.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const crypto = std.crypto; const readIntLittle = std.mem.readIntLittle; const writeIntLittle = std.mem.writeIntLittle; @@ -6,6 +7,12 @@ const writeIntLittle = std.mem.writeIntLittle; const NonCanonicalError = crypto.errors.NonCanonicalError; const NotSquareError = crypto.errors.NotSquareError; +// Inline conditionally, when it can result in large code generation. +const bloaty_inline = switch (builtin.mode) { + .ReleaseSafe, .ReleaseFast => .Inline, + .Debug, .ReleaseSmall => .Unspecified, +}; + pub const Fe = struct { limbs: [5]u64, @@ -264,7 +271,7 @@ pub const Fe = struct { } /// Multiply two field elements - pub inline fn mul(a: Fe, b: Fe) Fe { + pub fn mul(a: Fe, b: Fe) callconv(bloaty_inline) Fe { var ax: [5]u128 = undefined; var bx: [5]u128 = undefined; var a19: [5]u128 = undefined; From 70cbe5ac7c96c107985e0246dacefe04ea283761 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sun, 5 Mar 2023 17:00:09 +0100 Subject: [PATCH 047/294] AstGen: remove unnecessary `pub`s I think it helps when you know that something is entirely self-contained, which AstGen is. The only function public is `generate`. --- src/AstGen.zig | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 20f4fb6df3..f91845a1fd 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -205,7 +205,7 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { }; } -pub fn deinit(astgen: *AstGen, gpa: Allocator) void { +fn deinit(astgen: *AstGen, gpa: Allocator) void { astgen.instructions.deinit(gpa); astgen.extra.deinit(gpa); astgen.string_table.deinit(gpa); @@ -216,7 +216,7 @@ pub fn deinit(astgen: *AstGen, gpa: Allocator) void { astgen.ref_table.deinit(gpa); } -pub const ResultInfo = struct { +const ResultInfo = struct { /// The semantics requested for the result location rl: Loc, @@ -245,7 +245,7 @@ pub const ResultInfo = struct { } } - pub const Loc = union(enum) { + const Loc = union(enum) { /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the /// expression should be generated. The result instruction from the expression must /// be ignored. @@ -277,11 +277,11 @@ pub const ResultInfo = struct { src_node: ?Ast.Node.Index = null, }; - pub const Strategy = struct { + const Strategy = struct { elide_store_to_block_ptr_instructions: bool, tag: Tag, - pub const Tag = enum { + const Tag = enum { /// Both branches will use break_void; result location is used to communicate the /// result instruction. break_void, @@ -331,7 +331,7 @@ pub const ResultInfo = struct { } }; - pub const Context = enum { + const Context = enum { /// The expression is the operand to a return expression. @"return", /// The expression is the input to an error-handling operator (if-else, try, or catch). @@ -349,11 +349,11 @@ pub const ResultInfo = struct { }; }; -pub const align_ri: ResultInfo = .{ .rl = .{ .ty = .u29_type } }; -pub const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } }; -pub const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } }; -pub const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } }; -pub const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .type_type } }; +const align_ri: ResultInfo = .{ .rl = .{ .ty = .u29_type } }; +const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } }; +const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } }; +const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } }; +const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .type_type } }; fn typeExpr(gz: *GenZir, scope: *Scope, type_node: Ast.Node.Index) InnerError!Zir.Inst.Ref { const prev_force_comptime = gz.force_comptime; @@ -3507,7 +3507,7 @@ const WipMembers = struct { /// (4 for src_hash + line + name + value + doc_comment + align + link_section + address_space ) const max_decl_size = 11; - pub fn init(gpa: Allocator, payload: *ArrayListUnmanaged(u32), decl_count: u32, field_count: u32, comptime bits_per_field: u32, comptime max_field_size: u32) Allocator.Error!Self { + fn init(gpa: Allocator, payload: *ArrayListUnmanaged(u32), decl_count: u32, field_count: u32, comptime bits_per_field: u32, comptime max_field_size: u32) Allocator.Error!Self { const payload_top = @intCast(u32, payload.items.len); const decls_start = payload_top + (decl_count + decls_per_u32 - 1) / decls_per_u32; const field_bits_start = decls_start + decl_count * max_decl_size; @@ -3528,7 +3528,7 @@ const WipMembers = struct { }; } - pub fn nextDecl(self: *Self, is_pub: bool, is_export: bool, has_align: bool, has_section_or_addrspace: bool) void { + fn nextDecl(self: *Self, is_pub: bool, is_export: bool, has_align: bool, has_section_or_addrspace: bool) void { const index = self.payload_top + self.decl_index / decls_per_u32; assert(index < self.decls_start); const bit_bag: u32 = if (self.decl_index % decls_per_u32 == 0) 0 else self.payload.items[index]; @@ -3540,7 +3540,7 @@ const WipMembers = struct { self.decl_index += 1; } - pub fn nextField(self: *Self, comptime bits_per_field: u32, bits: [bits_per_field]bool) void { + fn nextField(self: *Self, comptime bits_per_field: u32, bits: [bits_per_field]bool) void { const fields_per_u32 = 32 / bits_per_field; const index = self.field_bits_start + self.field_index / fields_per_u32; assert(index < self.fields_start); @@ -3554,25 +3554,25 @@ const WipMembers = struct { self.field_index += 1; } - pub fn appendToDecl(self: *Self, data: u32) void { + fn appendToDecl(self: *Self, data: u32) void { assert(self.decls_end < self.field_bits_start); self.payload.items[self.decls_end] = data; self.decls_end += 1; } - pub fn appendToDeclSlice(self: *Self, data: []const u32) void { + fn appendToDeclSlice(self: *Self, data: []const u32) void { assert(self.decls_end + data.len <= self.field_bits_start); mem.copy(u32, self.payload.items[self.decls_end..], data); self.decls_end += @intCast(u32, data.len); } - pub fn appendToField(self: *Self, data: u32) void { + fn appendToField(self: *Self, data: u32) void { assert(self.fields_end < self.payload.items.len); self.payload.items[self.fields_end] = data; self.fields_end += 1; } - pub fn finishBits(self: *Self, comptime bits_per_field: u32) void { + fn finishBits(self: *Self, comptime bits_per_field: u32) void { const empty_decl_slots = decls_per_u32 - (self.decl_index % decls_per_u32); if (self.decl_index > 0 and empty_decl_slots < decls_per_u32) { const index = self.payload_top + self.decl_index / decls_per_u32; @@ -3588,15 +3588,15 @@ const WipMembers = struct { } } - pub fn declsSlice(self: *Self) []u32 { + fn declsSlice(self: *Self) []u32 { return self.payload.items[self.payload_top..self.decls_end]; } - pub fn fieldsSlice(self: *Self) []u32 { + fn fieldsSlice(self: *Self) []u32 { return self.payload.items[self.field_bits_start..self.fields_end]; } - pub fn deinit(self: *Self) void { + fn deinit(self: *Self) void { self.payload.items.len = self.payload_top; } }; @@ -10803,7 +10803,7 @@ const Scope = struct { /// ref of the capture for decls in this namespace captures: std.AutoArrayHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}, - pub fn deinit(self: *Namespace, gpa: Allocator) void { + fn deinit(self: *Namespace, gpa: Allocator) void { self.decls.deinit(gpa); self.captures.deinit(gpa); self.* = undefined; From 6218e4004608000ba2e42e07ed1bd56745626820 Mon Sep 17 00:00:00 2001 From: r00ster91 Date: Sun, 5 Mar 2023 17:37:56 +0100 Subject: [PATCH 048/294] Zir: fix outdated comment --- src/Zir.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Zir.zig b/src/Zir.zig index b8ea2ea295..65e2f21cc9 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2045,8 +2045,7 @@ pub const Inst = struct { /// A reference to a TypedValue or ZIR instruction. /// - /// If the Ref has a tag in this enum, it refers to a TypedValue which may be - /// retrieved with Ref.toTypedValue(). + /// If the Ref has a tag in this enum, it refers to a TypedValue. /// /// If the value of a Ref does not have a tag, it refers to a ZIR instruction. /// From c1d16a2b80e258a126ed496dab09a8a7c26f8468 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 7 Mar 2023 02:11:49 -0500 Subject: [PATCH 049/294] compiler_rt: fix rare case in udivei4 Unsigned integers are never less than zero, and so zig helpfully deleted the entire case. :D Closes #14816 --- lib/compiler_rt/udivmodei4.zig | 12 ++++++------ test/behavior/int_div.zig | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/compiler_rt/udivmodei4.zig b/lib/compiler_rt/udivmodei4.zig index de2427b79f..38a9b66b78 100644 --- a/lib/compiler_rt/udivmodei4.zig +++ b/lib/compiler_rt/udivmodei4.zig @@ -79,16 +79,16 @@ fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void { } break; } - var carry: u64 = 0; + var carry: i64 = 0; i = 0; while (i <= n) : (i += 1) { const p = qhat * limb(&vn, i); const t = limb(&un, i + j) - carry - @truncate(u32, p); - limb_set(&un, i + j, @truncate(u32, t)); - carry = @intCast(u64, p >> 32) - @intCast(u64, t >> 32); + limb_set(&un, i + j, @truncate(u32, @bitCast(u64, t))); + carry = @intCast(i64, p >> 32) - @intCast(i64, t >> 32); } - const t = limb(&un, j + n + 1) - carry; - limb_set(&un, j + n + 1, @truncate(u32, t)); + const t = limb(&un, j + n + 1) -% carry; + limb_set(&un, j + n + 1, @truncate(u32, @bitCast(u64, t))); if (q) |q_| limb_set(q_, j, @truncate(u32, qhat)); if (t < 0) { if (q) |q_| limb_set(q_, j, limb(q_, j) - 1); @@ -99,7 +99,7 @@ fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void { limb_set(&un, i + j, @truncate(u32, t2)); carry2 = t2 >> 32; } - limb_set(un, j + n + 1, @truncate(u32, limb(&un, j + n + 1) + carry2)); + limb_set(&un, j + n + 1, @truncate(u32, limb(&un, j + n + 1) + carry2)); } if (j == 0) break; } diff --git a/test/behavior/int_div.zig b/test/behavior/int_div.zig index 6ae794d377..c8b600ba03 100644 --- a/test/behavior/int_div.zig +++ b/test/behavior/int_div.zig @@ -91,3 +91,23 @@ fn mod(comptime T: type, a: T, b: T) T { fn rem(comptime T: type, a: T, b: T) T { return @rem(a, b); } + +test "large integer division" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + { + var numerator: u256 = 99999999999999999997315645440; + var divisor: u256 = 10000000000000000000000000000; + try expect(numerator / divisor == 9); + } + { + var numerator: u256 = 99999999999999999999000000000000000000000; + var divisor: u256 = 10000000000000000000000000000000000000000; + try expect(numerator / divisor == 9); + } +} From 77d06012c2465f7c4ac22cb4834a2535c4de6cea Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 7 Mar 2023 02:59:41 -0500 Subject: [PATCH 050/294] CBE: implement unsigned big int div and mod --- lib/zig.h | 38 +++++++++++++++++++++++++++++++++ src/codegen/c.zig | 45 ++++++++++++++++++++++----------------- test/behavior/int_div.zig | 1 - 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/lib/zig.h b/lib/zig.h index 65fb21f99a..10b5f546e0 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -2384,6 +2384,44 @@ static inline void zig_subw_big(void *res, const void *lhs, const void *rhs, boo (void)zig_subo_big(res, lhs, rhs, is_signed, bits); } +zig_extern void __udivei4(uint32_t *res, const uint32_t *lhs, const uint32_t *rhs, uintptr_t bits); +static inline void zig_div_trunc_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + __udivei4(res, lhs, rhs, bits); + return; + } + + zig_trap(); +} + +static inline void zig_div_floor_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + zig_div_trunc_big(res, lhs, rhs, is_signed, bits); + return; + } + + zig_trap(); +} + +zig_extern void __umodei4(uint32_t *res, const uint32_t *lhs, const uint32_t *rhs, uintptr_t bits); +static inline void zig_rem_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + __umodei4(res, lhs, rhs, bits); + return; + } + + zig_trap(); +} + +static inline void zig_mod_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + zig_rem_big(res, lhs, rhs, is_signed, bits); + return; + } + + zig_trap(); +} + static inline uint16_t zig_clz_big(const void *val, bool is_signed, uint16_t bits) { const uint8_t *val_bytes = val; uint16_t byte_offset = 0; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3d059adc15..519b2b45d5 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1885,25 +1885,28 @@ pub const DeclGen = struct { } fn renderBuiltinInfo(dg: *DeclGen, writer: anytype, ty: Type, info: BuiltinInfo) !void { + const cty = try dg.typeToCType(ty, .complete); + const is_big = cty.tag() == .array; + switch (info) { - .none => {}, - .bits => { - const target = dg.module.getTarget(); - const int_info = if (ty.isAbiInt()) ty.intInfo(target) else std.builtin.Type.Int{ - .signedness = .unsigned, - .bits = @intCast(u16, ty.bitSize(target)), - }; - - const cty = try dg.typeToCType(ty, .complete); - if (cty.tag() == .array) try writer.print(", {}", .{int_info.signedness == .signed}); - - var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = int_info.bits }; - try writer.print(", {}", .{try dg.fmtIntLiteral(switch (cty.tag()) { - else => Type.u8, - .array => Type.u16, - }, Value.initPayload(&bits_pl.base), .FunctionArgument)}); - }, + .none => if (!is_big) return, + .bits => {}, } + + const target = dg.module.getTarget(); + const int_info = if (ty.isAbiInt()) ty.intInfo(target) else std.builtin.Type.Int{ + .signedness = .unsigned, + .bits = @intCast(u16, ty.bitSize(target)), + }; + + if (is_big) try writer.print(", {}", .{int_info.signedness == .signed}); + + var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = int_info.bits }; + try writer.print(", {}", .{try dg.fmtIntLiteral( + if (is_big) Type.u16 else Type.u8, + Value.initPayload(&bits_pl.base), + .FunctionArgument, + )}); } fn fmtIntLiteral( @@ -6099,13 +6102,16 @@ fn airBinBuiltinCall( return .none; } + const operand_ty = f.air.typeOf(bin_op.lhs); + const operand_cty = try f.typeToCType(operand_ty, .complete); + const is_big = operand_cty.tag() == .array; + const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); - try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); + if (!is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); const inst_ty = f.air.typeOfIndex(inst); const inst_scalar_ty = inst_ty.scalarType(); - const operand_ty = f.air.typeOf(bin_op.lhs); const scalar_ty = operand_ty.scalarType(); const inst_scalar_cty = try f.typeToCType(inst_scalar_ty, .complete); @@ -6113,6 +6119,7 @@ fn airBinBuiltinCall( const writer = f.object.writer(); const local = try f.allocLocal(inst, inst_ty); + if (is_big) try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs }); const v = try Vectorizer.start(f, inst, writer, operand_ty); if (!ref_ret) { try f.writeCValue(writer, local, .Other); diff --git a/test/behavior/int_div.zig b/test/behavior/int_div.zig index c8b600ba03..954f6be220 100644 --- a/test/behavior/int_div.zig +++ b/test/behavior/int_div.zig @@ -95,7 +95,6 @@ fn rem(comptime T: type, a: T, b: T) T { test "large integer division" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; From 36d47dd1991f0ccd7a9673075624f09500cc415e Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Tue, 7 Mar 2023 10:04:45 +0100 Subject: [PATCH 051/294] std.crypto.hash.sha3: add TurboSHAKE (#14824) --- lib/std/crypto/benchmark.zig | 2 ++ lib/std/crypto/keccak_p.zig | 4 ++-- lib/std/crypto/sha3.zig | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index e6e0e1fc39..47ca24aa66 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -27,6 +27,8 @@ const hashes = [_]Crypto{ Crypto{ .ty = crypto.hash.sha3.Sha3_512, .name = "sha3-512" }, Crypto{ .ty = crypto.hash.sha3.Shake128, .name = "shake-128" }, Crypto{ .ty = crypto.hash.sha3.Shake256, .name = "shake-256" }, + Crypto{ .ty = crypto.hash.sha3.TurboShake128(null), .name = "turboshake-128" }, + Crypto{ .ty = crypto.hash.sha3.TurboShake256(null), .name = "turboshake-256" }, Crypto{ .ty = crypto.hash.Gimli, .name = "gimli-hash" }, Crypto{ .ty = crypto.hash.blake2.Blake2s256, .name = "blake2s" }, Crypto{ .ty = crypto.hash.blake2.Blake2b512, .name = "blake2b" }, diff --git a/lib/std/crypto/keccak_p.zig b/lib/std/crypto/keccak_p.zig index 10367caffd..cef3b0cce4 100644 --- a/lib/std/crypto/keccak_p.zig +++ b/lib/std/crypto/keccak_p.zig @@ -175,12 +175,12 @@ pub fn KeccakF(comptime f: u11) type { /// Apply a (possibly) reduced-round permutation to the state. pub fn permuteR(self: *Self, comptime rounds: u5) void { var i = RC.len - rounds; - while (i < rounds - rounds % 3) : (i += 3) { + while (i < RC.len - RC.len % 3) : (i += 3) { self.round(RC[i]); self.round(RC[i + 1]); self.round(RC[i + 2]); } - while (i < rounds) : (i += 1) { + while (i < RC.len) : (i += 1) { self.round(RC[i]); } } diff --git a/lib/std/crypto/sha3.zig b/lib/std/crypto/sha3.zig index 985027f3ee..6fc4977f0b 100644 --- a/lib/std/crypto/sha3.zig +++ b/lib/std/crypto/sha3.zig @@ -18,6 +18,20 @@ pub const Keccak_512 = @compileError("Deprecated: use `Keccak512` instead"); pub const Shake128 = Shake(128); pub const Shake256 = Shake(256); +/// TurboSHAKE128 is a XOF (a secure hash function with a variable output length), with a 128 bit security level. +/// It is based on the same permutation as SHA3 and SHAKE128, but which much higher performance. +/// The delimiter is 0x01 by default, but can be changed for context-separation. +pub fn TurboShake128(comptime delim: ?u8) type { + return TurboShake(128, delim); +} + +/// TurboSHAKE256 is a XOF (a secure hash function with a variable output length), with a 256 bit security level. +/// It is based on the same permutation as SHA3 and SHAKE256, but which much higher performance. +/// The delimiter is 0x01 by default, but can be changed for context-separation. +pub fn TurboShake256(comptime delim: ?u8) type { + return TurboShake(256, delim); +} + /// A generic Keccak hash function. pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, comptime rounds: u5) type { comptime assert(output_bits > 0 and output_bits * 2 < f and output_bits % 8 == 0); // invalid output length @@ -76,9 +90,18 @@ pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime delim: u8, co /// The SHAKE extendable output hash function. pub fn Shake(comptime security_level: u11) type { + return ShakeLike(security_level, 0x1f, 24); +} + +/// The TurboSHAKE extendable output hash function. +/// https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/ +pub fn TurboShake(comptime security_level: u11, comptime delim: ?u8) type { + return ShakeLike(security_level, delim orelse 0x01, 12); +} + +fn ShakeLike(comptime security_level: u11, comptime delim: u8, comptime rounds: u5) type { const f = 1600; - const rounds = 24; - const State = KeccakState(f, security_level * 2, 0x1f, rounds); + const State = KeccakState(f, security_level * 2, delim, rounds); return struct { const Self = @This(); @@ -348,3 +371,9 @@ test "SHAKE-256 single" { Shake256.hash("hello123", &out, .{}); try htest.assertEqual("ade612ba265f92de4a37", &out); } + +test "TurboSHAKE-128" { + var out: [32]u8 = undefined; + TurboShake(128, 0x06).hash("\xff", &out, .{}); + try htest.assertEqual("8ec9c66465ed0d4a6c35d13506718d687a25cb05c74cca1e42501abd83874a67", &out); +} From e33dfc300e144dcf3323f96b38d8c67163259bb1 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 7 Mar 2023 12:25:22 -0500 Subject: [PATCH 052/294] zig.h: implement zig_breakpoint and zig_trap for more targets --- lib/zig.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/zig.h b/lib/zig.h index 10b5f546e0..59c3ddd695 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -190,10 +190,17 @@ typedef char bool; #if zig_has_builtin(trap) #define zig_trap() __builtin_trap() +#elif _MSC_VER && (_M_IX86 || _M_X64) +#define zig_trap() __ud2() +#elif _MSC_VER +#define zig_trap() __fastfail(0) #elif defined(__i386__) || defined(__x86_64__) #define zig_trap() __asm__ volatile("ud2"); +#elif defined(__arm__) || defined(__aarch64__) +#define zig_breakpoint() __asm__ volatile("udf #0"); #else -#define zig_trap() raise(SIGTRAP) +#include +#define zig_trap() abort() #endif #if zig_has_builtin(debugtrap) @@ -202,8 +209,17 @@ typedef char bool; #define zig_breakpoint() __debugbreak() #elif defined(__i386__) || defined(__x86_64__) #define zig_breakpoint() __asm__ volatile("int $0x03"); +#elif defined(__arm__) +#define zig_breakpoint() __asm__ volatile("bkpt #0"); +#elif defined(__aarch64__) +#define zig_breakpoint() __asm__ volatile("brk #0"); #else +#include +#if defined(SIGTRAP) #define zig_breakpoint() raise(SIGTRAP) +#else +#define zig_breakpoint() zig_breakpoint_unavailable +#endif #endif #if zig_has_builtin(return_address) || defined(zig_gnuc) From 8da6b393fb6f34ba8cbe3efaf1dac4d459c05d5b Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 7 Mar 2023 12:19:38 +0100 Subject: [PATCH 053/294] std.fmt: add bytesToHex() to encode bytes as hex digits We already had `hexToBytes()`, but not the reverse operation (at least not without using formatters). --- lib/std/fmt.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 0da25fde78..8167a2b252 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -2555,6 +2555,21 @@ test "bytes.hex" { try expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{fmtSliceHexLower(bytes_with_zeros)}); } +/// Encodes a sequence of bytes as hexadecimal digits. +/// Returns an array containing the encoded bytes. +pub fn bytesToHex(input: anytype, case: Case) [input.len * 2]u8 { + if (input.len == 0) return [_]u8{}; + comptime assert(@TypeOf(input[0]) == u8); // elements to encode must be unsigned bytes + + const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef"; + var result: [input.len * 2]u8 = undefined; + for (input, 0..) |b, i| { + result[i * 2 + 0] = charset[b >> 4]; + result[i * 2 + 1] = charset[b & 15]; + } + return result; +} + /// Decodes the sequence of bytes represented by the specified string of /// hexadecimal characters. /// Returns a slice of the output buffer containing the decoded bytes. @@ -2575,6 +2590,13 @@ pub fn hexToBytes(out: []u8, input: []const u8) ![]u8 { return out[0 .. in_i / 2]; } +test "bytesToHex" { + const input = "input slice"; + const encoded = bytesToHex(input, .lower); + var decoded: [input.len]u8 = undefined; + try std.testing.expectEqualSlices(u8, input, try hexToBytes(&decoded, &encoded)); +} + test "hexToBytes" { var buf: [32]u8 = undefined; try expectFmt("90" ** 32, "{s}", .{fmtSliceHexUpper(try hexToBytes(&buf, "90" ** 32))}); From bbba701a41443cdca454e65e58e5dda2f4d3188a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan?= Date: Tue, 7 Mar 2023 14:59:12 +0100 Subject: [PATCH 054/294] std.os.windows.advapi32: Add RegCloseKey --- lib/std/os/windows/advapi32.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/os/windows/advapi32.zig b/lib/std/os/windows/advapi32.zig index 67234a26e0..bace7ce850 100644 --- a/lib/std/os/windows/advapi32.zig +++ b/lib/std/os/windows/advapi32.zig @@ -27,6 +27,8 @@ pub extern "advapi32" fn RegQueryValueExW( lpcbData: ?*DWORD, ) callconv(WINAPI) LSTATUS; +pub extern "advapi32" fn RegCloseKey(hKey: HKEY) callconv(WINAPI) LSTATUS; + // RtlGenRandom is known as SystemFunction036 under advapi32 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */ pub extern "advapi32" fn SystemFunction036(output: [*]u8, length: ULONG) callconv(WINAPI) BOOL; From e3cf9d165081533524927cebf8a92ac6fee097f2 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Mon, 6 Mar 2023 17:22:23 -0500 Subject: [PATCH 055/294] Module: rewrite zir caching logic Multiple processes can sit waiting for the exclusive lock at the same time, so we want to recheck whether it needs to be updated whenever we get an exclusive lock. This also fixes a race condition between one process truncating the cache file and another process reading it without atomic locking. --- src/Module.zig | 123 ++++++++++++++++++++++++------------------------- stage1/wasi.c | 2 - 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 7ea69a0a2e..f3f1aa44e2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3538,44 +3538,61 @@ pub fn astGenFile(mod: *Module, file: *File) !void { const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache; const zir_dir = cache_directory.handle; - var cache_file: ?std.fs.File = null; - defer if (cache_file) |f| f.close(); - // Determine whether we need to reload the file from disk and redo parsing and AstGen. - switch (file.status) { - .never_loaded, .retryable_failure => cached: { + var lock: std.fs.File.Lock = switch (file.status) { + .never_loaded, .retryable_failure => lock: { // First, load the cached ZIR code, if any. log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{ file.sub_file_path, want_local_cache, &digest, }); - // We ask for a lock in order to coordinate with other zig processes. - // If another process is already working on this file, we will get the cached - // version. Likewise if we're working on AstGen and another process asks for - // the cached file, they'll get it. - cache_file = zir_dir.openFile(&digest, .{ .lock = .Shared }) catch |err| switch (err) { - error.PathAlreadyExists => unreachable, // opening for reading - error.NoSpaceLeft => unreachable, // opening for reading - error.NotDir => unreachable, // no dir components - error.InvalidUtf8 => unreachable, // it's a hex encoded name - error.BadPathName => unreachable, // it's a hex encoded name - error.NameTooLong => unreachable, // it's a fixed size name - error.PipeBusy => unreachable, // it's not a pipe - error.WouldBlock => unreachable, // not asking for non-blocking I/O + break :lock .Shared; + }, + .parse_failure, .astgen_failure, .success_zir => lock: { + const unchanged_metadata = + stat.size == file.stat.size and + stat.mtime == file.stat.mtime and + stat.inode == file.stat.inode; - error.SymLinkLoop, - error.FileNotFound, - error.Unexpected, - => break :cached, + if (unchanged_metadata) { + log.debug("unmodified metadata of file: {s}", .{file.sub_file_path}); + return; + } - else => |e| return e, // Retryable errors are handled at callsite. - }; + log.debug("metadata changed: {s}", .{file.sub_file_path}); + break :lock .Exclusive; + }, + }; + + // We ask for a lock in order to coordinate with other zig processes. + // If another process is already working on this file, we will get the cached + // version. Likewise if we're working on AstGen and another process asks for + // the cached file, they'll get it. + const cache_file = zir_dir.createFile(&digest, .{ + .read = true, + .truncate = false, + .lock = lock, + }) catch |err| switch (err) { + error.NotDir => unreachable, // no dir components + error.InvalidUtf8 => unreachable, // it's a hex encoded name + error.BadPathName => unreachable, // it's a hex encoded name + error.NameTooLong => unreachable, // it's a fixed size name + error.PipeBusy => unreachable, // it's not a pipe + error.WouldBlock => unreachable, // not asking for non-blocking I/O + error.FileNotFound => unreachable, // no dir components + + else => |e| return e, // Retryable errors are handled at callsite. + }; + defer cache_file.close(); + + while (true) { + update: { // First we read the header to determine the lengths of arrays. - const header = cache_file.?.reader().readStruct(Zir.Header) catch |err| switch (err) { + const header = cache_file.reader().readStruct(Zir.Header) catch |err| switch (err) { // This can happen if Zig bails out of this function between creating // the cached file and writing it. - error.EndOfStream => break :cached, + error.EndOfStream => break :update, else => |e| return e, }; const unchanged_metadata = @@ -3585,7 +3602,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { if (!unchanged_metadata) { log.debug("AstGen cache stale: {s}", .{file.sub_file_path}); - break :cached; + break :update; } log.debug("AstGen cache hit: {s} instructions_len={d}", .{ file.sub_file_path, header.instructions_len, @@ -3637,13 +3654,13 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .iov_len = header.extra_len * 4, }, }; - const amt_read = try cache_file.?.readvAll(&iovecs); + const amt_read = try cache_file.readvAll(&iovecs); const amt_expected = zir.instructions.len * 9 + zir.string_bytes.len + zir.extra.len * 4; if (amt_read != amt_expected) { log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path}); - break :cached; + break :update; } if (data_has_safety_tag) { const tags = zir.instructions.items(.tag); @@ -3679,42 +3696,22 @@ pub fn astGenFile(mod: *Module, file: *File) !void { return error.AnalysisFail; } return; - }, - .parse_failure, .astgen_failure, .success_zir => { - const unchanged_metadata = - stat.size == file.stat.size and - stat.mtime == file.stat.mtime and - stat.inode == file.stat.inode; + } - if (unchanged_metadata) { - log.debug("unmodified metadata of file: {s}", .{file.sub_file_path}); - return; - } - - log.debug("metadata changed: {s}", .{file.sub_file_path}); - }, + // If we already have the exclusive lock then it is our job to update. + if (builtin.os.tag == .wasi or lock == .Exclusive) break; + // Otherwise, unlock to give someone a chance to get the exclusive lock + // and then upgrade to an exclusive lock. + cache_file.unlock(); + lock = .Exclusive; + try cache_file.lock(lock); } - if (cache_file) |f| { - f.close(); - cache_file = null; - } - cache_file = zir_dir.createFile(&digest, .{ .lock = .Exclusive }) catch |err| switch (err) { - error.NotDir => unreachable, // no dir components - error.InvalidUtf8 => unreachable, // it's a hex encoded name - error.BadPathName => unreachable, // it's a hex encoded name - error.NameTooLong => unreachable, // it's a fixed size name - error.PipeBusy => unreachable, // it's not a pipe - error.WouldBlock => unreachable, // not asking for non-blocking I/O - error.FileNotFound => unreachable, // no dir components - else => |e| { - const pkg_path = file.pkg.root_src_directory.path orelse "."; - const cache_path = cache_directory.path orelse "."; - log.warn("unable to save cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{ - pkg_path, file.sub_file_path, cache_path, &digest, @errorName(e), - }); - return; - }, + // The cache is definitely stale so delete the contents to avoid an underwrite later. + cache_file.setEndPos(0) catch |err| switch (err) { + error.FileTooBig => unreachable, // 0 is not too big + + else => |e| return e, }; mod.lockAndClearFileCompileError(file); @@ -3871,7 +3868,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .iov_len = file.zir.extra.len * 4, }, }; - cache_file.?.writevAll(&iovecs) catch |err| { + cache_file.writevAll(&iovecs) catch |err| { const pkg_path = file.pkg.root_src_directory.path orelse "."; const cache_path = cache_directory.path orelse "."; log.warn("unable to write cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{ diff --git a/stage1/wasi.c b/stage1/wasi.c index 911ce6e520..6c4ac48a50 100644 --- a/stage1/wasi.c +++ b/stage1/wasi.c @@ -497,8 +497,6 @@ uint32_t wasi_snapshot_preview1_fd_read(uint32_t fd, uint32_t iovs, uint32_t iov size_t read_size = 0; if (fds[fd].stream != NULL) read_size = fread(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream); - else - panic("unimplemented"); size += read_size; if (read_size < iovs_ptr[i].len) break; } From fea14c78d1374af909a2f11e37fe057777f3985d Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Wed, 8 Mar 2023 14:50:06 +0100 Subject: [PATCH 056/294] wasm-linker: emit build_id section (#14820) The Build ID is a value that uniquely identifies a build. It is intended to capture the "meaning" or inputs of the build, and is usually associated with debug info. Reference: https://github.com/WebAssembly/tool-conventions/blob/main/BuildId.md --- src/link/Wasm.zig | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 513b567210..b5c9ffa991 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2635,6 +2635,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l try man.addOptionalFile(compiler_rt_path); man.hash.addOptionalBytes(options.entry); man.hash.addOptional(options.stack_size_override); + man.hash.add(wasm.base.options.build_id); man.hash.add(options.import_memory); man.hash.add(options.import_table); man.hash.add(options.export_table); @@ -3225,6 +3226,12 @@ fn writeToFile( } if (!wasm.base.options.strip) { + // The build id must be computed on the main sections only, + // so we have to do it now, before the debug sections. + if (wasm.base.options.build_id) { + try emitBuildIdSection(&binary_bytes); + } + // if (wasm.dwarf) |*dwarf| { // const mod = wasm.base.options.module.?; // try dwarf.writeDbgAbbrev(); @@ -3363,6 +3370,33 @@ fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void { ); } +fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8)) !void { + const header_offset = try reserveCustomSectionHeader(binary_bytes); + + const writer = binary_bytes.writer(); + const build_id = "build_id"; + try leb.writeULEB128(writer, @intCast(u32, build_id.len)); + try writer.writeAll(build_id); + + var id: [16]u8 = undefined; + std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); + var uuid: [36]u8 = undefined; + _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{ + std.fmt.fmtSliceHexLower(id[0..4]), std.fmt.fmtSliceHexLower(id[4..6]), std.fmt.fmtSliceHexLower(id[6..8]), + std.fmt.fmtSliceHexLower(id[8..10]), std.fmt.fmtSliceHexLower(id[10..]), + }); + + try leb.writeULEB128(writer, @as(u32, 1)); + try leb.writeULEB128(writer, @as(u32, uuid.len)); + try writer.writeAll(&uuid); + + try writeCustomSectionHeader( + binary_bytes.items, + header_offset, + @intCast(u32, binary_bytes.items.len - header_offset - 6), + ); +} + fn emitFeaturesSection(binary_bytes: *std.ArrayList(u8), enabled_features: []const bool, features_count: u32) !void { const header_offset = try reserveCustomSectionHeader(binary_bytes); @@ -3594,6 +3628,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! try man.addOptionalFile(compiler_rt_path); man.hash.addOptionalBytes(wasm.base.options.entry); man.hash.addOptional(wasm.base.options.stack_size_override); + man.hash.add(wasm.base.options.build_id); man.hash.add(wasm.base.options.import_memory); man.hash.add(wasm.base.options.import_table); man.hash.add(wasm.base.options.export_table); @@ -3760,6 +3795,12 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! if (wasm.base.options.import_symbols) { try argv.append("--allow-undefined"); } + + // XXX - TODO: add when wasm-ld supports --build-id. + // if (wasm.base.options.build_id) { + // try argv.append("--build-id=tree"); + // } + try argv.appendSlice(&.{ "-o", full_out_path }); if (target.cpu.arch == .wasm64) { From ecc0108cea97772b6e921b36d8fdc8f90d5fc6cb Mon Sep 17 00:00:00 2001 From: John Schmidt Date: Wed, 1 Mar 2023 21:44:11 +0100 Subject: [PATCH 057/294] astgen: fill result location with `void` value if no other value With this change, `break` and `break :blk` will fill the result location with `.void_value`, ensuring that the value will be type checked. The same will happen for a for loop that contains no `break`s in it's body. Closes https://github.com/ziglang/zig/issues/14686. --- src/AstGen.zig | 17 ++++------ .../break_void_result_location.zig | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 test/cases/compile_errors/break_void_result_location.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index f91845a1fd..7b2138a535 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1960,7 +1960,10 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn else .@"break"; + block_gz.break_count += 1; if (rhs == 0) { + _ = try rvalue(parent_gz, block_gz.break_result_info, .void_value, node); + try genDefers(parent_gz, scope, parent_scope, .normal_only); // As our last action before the break, "pop" the error trace if needed @@ -1970,7 +1973,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn _ = try parent_gz.addBreak(break_tag, block_inst, .void_value); return Zir.Inst.Ref.unreachable_value; } - block_gz.break_count += 1; const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_info, rhs, node); const search_index = @intCast(Zir.Inst.Index, astgen.instructions.len); @@ -6584,6 +6586,9 @@ fn forExpr( cond_block, break_tag, ); + if (ri.rl.strategy(&loop_scope).tag == .break_void and loop_scope.break_count == 0) { + _ = try rvalue(parent_gz, ri, .void_value, node); + } if (is_statement) { _ = try parent_gz.addUnNode(.ensure_result_used, result, node); } @@ -8525,16 +8530,6 @@ fn builtinCall( } } -fn simpleNoOpVoid( - gz: *GenZir, - ri: ResultInfo, - node: Ast.Node.Index, - tag: Zir.Inst.Tag, -) InnerError!Zir.Inst.Ref { - _ = try gz.addNode(tag, node); - return rvalue(gz, ri, .void_value, node); -} - fn hasDeclOrField( gz: *GenZir, scope: *Scope, diff --git a/test/cases/compile_errors/break_void_result_location.zig b/test/cases/compile_errors/break_void_result_location.zig new file mode 100644 index 0000000000..696ea39667 --- /dev/null +++ b/test/cases/compile_errors/break_void_result_location.zig @@ -0,0 +1,32 @@ +export fn f1() void { + const x: usize = for ("hello") |_| {}; + _ = x; +} +export fn f2() void { + const x: usize = for ("hello") |_| { + break; + }; + _ = x; +} +export fn f3() void { + var t: bool = true; + const x: usize = while (t) { + break; + }; + _ = x; +} +export fn f4() void { + const x: usize = blk: { + break :blk; + }; + _ = x; +} + +// error +// backend=stage2 +// target=native +// +// :2:22: error: expected type 'usize', found 'void' +// :7:9: error: expected type 'usize', found 'void' +// :14:9: error: expected type 'usize', found 'void' +// :18:1: error: expected type 'usize', found 'void' From 06b263825a67e68cec128c640a6287fa1716dc63 Mon Sep 17 00:00:00 2001 From: Jan Philipp Hafer Date: Sun, 5 Mar 2023 18:41:52 +0100 Subject: [PATCH 058/294] std.os: add missing mmap errors Man page for posix lists EMFILE, man page for linux ENFILE. Also posix says "The mmap() function adds an extra reference to the file associated with the file descriptor fildes which is not removed by a subsequent close() on that file descriptor. This reference is removed when there are no more mappings to the file." It sounds counter-intuitive, that a process limit but no system limit can be exceeeded. As far as I understand, fildes is only used for file descriptor backed mmaps. --- lib/std/Thread.zig | 3 +++ lib/std/os.zig | 41 +++++++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 8004f94d7f..27f7fa5030 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -945,6 +945,7 @@ const LinuxThreadImpl = struct { // map all memory needed without read/write permissions // to avoid committing the whole region right away + // anonymous mapping ensures file descriptor limits are not exceeded const mapped = os.mmap( null, map_bytes, @@ -956,6 +957,8 @@ const LinuxThreadImpl = struct { error.MemoryMappingNotSupported => unreachable, error.AccessDenied => unreachable, error.PermissionDenied => unreachable, + error.ProcessFdQuotaExceeded => unreachable, + error.SystemFdQuotaExceeded => unreachable, else => |e| return e, }; assert(mapped.len >= map_bytes); diff --git a/lib/std/os.zig b/lib/std/os.zig index 821c544cc8..6c680e9a38 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -253,6 +253,25 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (builtin. else => undefined, }; +pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE"); + +fn noopSigHandler(_: c_int) callconv(.C) void {} + +/// On default executed by posix startup code before main(), if SIGPIPE is supported. +pub fn maybeIgnoreSigpipe() void { + if (have_sigpipe_support and !std.options.keep_sigpipe) { + const act = Sigaction{ + // We set handler to a noop function instead of SIG.IGN so we don't leak our + // signal disposition to a child process + .handler = .{ .handler = noopSigHandler }, + .mask = empty_sigset, + .flags = 0, + }; + sigaction(SIG.PIPE, &act, null) catch |err| + std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)}); + } +} + /// To obtain errno, call this function with the return value of the /// system function call. For some systems this will obtain the value directly /// from the return code; for others it will use a thread-local errno variable. @@ -4306,6 +4325,8 @@ pub const MMapError = error{ /// a filesystem that was mounted no-exec. PermissionDenied, LockedMemoryLimitExceeded, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, OutOfMemory, } || UnexpectedError; @@ -4347,6 +4368,8 @@ pub fn mmap( .OVERFLOW => unreachable, // The number of pages used for length + offset would overflow. .NODEV => return error.MemoryMappingNotSupported, .INVAL => unreachable, // Invalid parameters to mmap() + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, .NOMEM => return error.OutOfMemory, else => return unexpectedErrno(err), } @@ -7081,21 +7104,3 @@ pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec { else => |err| return unexpectedErrno(err), }; } - -pub const have_sigpipe_support = @hasDecl(@This(), "SIG") and @hasDecl(SIG, "PIPE"); - -fn noopSigHandler(_: c_int) callconv(.C) void {} - -pub fn maybeIgnoreSigpipe() void { - if (have_sigpipe_support and !std.options.keep_sigpipe) { - const act = Sigaction{ - // We set handler to a noop function instead of SIG.IGN so we don't leak our - // signal disposition to a child process - .handler = .{ .handler = noopSigHandler }, - .mask = empty_sigset, - .flags = 0, - }; - sigaction(SIG.PIPE, &act, null) catch |err| - std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)}); - } -} From 3e99afdbfe9a66bc5461148a4754c1e88b33fb88 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 8 Mar 2023 14:21:33 +0100 Subject: [PATCH 059/294] build: add -Dpie option It is becoming increasingly common for distributions to want to enable PIE for all binaries and zig currently does not provide any way to do so aside from patching the build.zig. --- CMakeLists.txt | 7 +++++++ build.zig | 2 ++ 2 files changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 925fd2d639..501d12889f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -827,6 +827,12 @@ else() set(ZIG_STATIC_ARG "") endif() +if(CMAKE_POSITION_INDEPENDENT_CODE) + set(ZIG_PIE_ARG="-Dpie") +else() + set(ZIG_PIE_ARG="") +endif() + set(ZIG_BUILD_ARGS --zig-lib-dir "${CMAKE_SOURCE_DIR}/lib" "-Dconfig_h=${ZIG_CONFIG_H_OUT}" @@ -835,6 +841,7 @@ set(ZIG_BUILD_ARGS ${ZIG_STATIC_ARG} ${ZIG_NO_LIB_ARG} ${ZIG_SINGLE_THREADED_ARG} + ${ZIG_PIE_ARG} "-Dtarget=${ZIG_TARGET_TRIPLE}" "-Dcpu=${ZIG_TARGET_MCPU}" "-Dversion-string=${RESOLVED_ZIG_VERSION}" diff --git a/build.zig b/build.zig index a95c9dfb58..12e5d014e2 100644 --- a/build.zig +++ b/build.zig @@ -152,6 +152,7 @@ pub fn build(b: *std.Build) !void { const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse (enable_llvm or only_c); const sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false; const strip = b.option(bool, "strip", "Omit debug information"); + const pie = b.option(bool, "pie", "Produce a Position Independent Executable"); const value_tracing = b.option(bool, "value-tracing", "Enable extra state tracking to help troubleshoot bugs in the compiler (using the std.debug.Trace API)") orelse false; const mem_leak_frames: u32 = b.option(u32, "mem-leak-frames", "How many stack frames to print when a memory leak occurs. Tests get 2x this amount.") orelse blk: { @@ -162,6 +163,7 @@ pub fn build(b: *std.Build) !void { const exe = addCompilerStep(b, optimize, target); exe.strip = strip; + exe.pie = pie; exe.sanitize_thread = sanitize_thread; exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false; exe.install(); From 6d7fb8f19c864f04d3472e5aec161957193e1e7c Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 23 Feb 2023 14:07:06 +0000 Subject: [PATCH 060/294] Sema: check type of comptime try operand Resolves: #14693 --- src/Sema.zig | 7 ++++++- .../cases/compile_errors/comptime_try_non_error.zig | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/cases/compile_errors/comptime_try_non_error.zig diff --git a/src/Sema.zig b/src/Sema.zig index 8c6e3cf05c..840e8a4c6c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1612,6 +1612,12 @@ fn analyzeBodyInner( const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; const err_union = try sema.resolveInst(extra.data.operand); + const err_union_ty = sema.typeOf(err_union); + if (err_union_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(block, operand_src, "expected error union type, found '{}'", .{ + err_union_ty.fmt(sema.mod), + }); + } const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); assert(is_non_err != .none); const is_non_err_tv = sema.resolveInstConst(block, operand_src, is_non_err, "try operand inside comptime block must be comptime-known") catch |err| { @@ -1619,7 +1625,6 @@ fn analyzeBodyInner( return err; }; if (is_non_err_tv.val.toBool()) { - const err_union_ty = sema.typeOf(err_union); break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false); } const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse diff --git a/test/cases/compile_errors/comptime_try_non_error.zig b/test/cases/compile_errors/comptime_try_non_error.zig new file mode 100644 index 0000000000..9b342f7934 --- /dev/null +++ b/test/cases/compile_errors/comptime_try_non_error.zig @@ -0,0 +1,13 @@ +export fn foo() void { + try bar(); +} + +pub fn bar() u8 { + return 0; +} + +// error +// backend=stage2 +// target=native +// +// :2:12: error: expected error union type, found 'u8' From 134e5748e0b698d51b6daf1983d3aeb1affc142f Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Thu, 9 Mar 2023 06:18:15 +0100 Subject: [PATCH 061/294] Fix incorrect SHA-3 computation with the streaming API (#14852) * Fix SHA3 with streaming Leftover bytes should be added to the buffer, not to the state. (or, always to the state; we can and probably should eventually get rid of the buffer) Fixes #14851 * Add a test for SHA-3 with streaming --- lib/std/crypto/keccak_p.zig | 2 +- lib/std/crypto/sha3.zig | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/std/crypto/keccak_p.zig b/lib/std/crypto/keccak_p.zig index cef3b0cce4..af7c12c8c2 100644 --- a/lib/std/crypto/keccak_p.zig +++ b/lib/std/crypto/keccak_p.zig @@ -231,7 +231,7 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime delim: u8, compti bytes = bytes[rate..]; } if (bytes.len > 0) { - self.st.addBytes(bytes[0..]); + mem.copy(u8, &self.buf, bytes); self.offset = bytes.len; } } diff --git a/lib/std/crypto/sha3.zig b/lib/std/crypto/sha3.zig index 6fc4977f0b..d286e40666 100644 --- a/lib/std/crypto/sha3.zig +++ b/lib/std/crypto/sha3.zig @@ -377,3 +377,17 @@ test "TurboSHAKE-128" { TurboShake(128, 0x06).hash("\xff", &out, .{}); try htest.assertEqual("8ec9c66465ed0d4a6c35d13506718d687a25cb05c74cca1e42501abd83874a67", &out); } + +test "SHA-3 with streaming" { + var msg: [613]u8 = [613]u8{ 0x97, 0xd1, 0x2d, 0x1a, 0x16, 0x2d, 0x36, 0x4d, 0x20, 0x62, 0x19, 0x0b, 0x14, 0x93, 0xbb, 0xf8, 0x5b, 0xea, 0x04, 0xc2, 0x61, 0x8e, 0xd6, 0x08, 0x81, 0xa1, 0x1d, 0x73, 0x27, 0x48, 0xbf, 0xa4, 0xba, 0xb1, 0x9a, 0x48, 0x9c, 0xf9, 0x9b, 0xff, 0x34, 0x48, 0xa9, 0x75, 0xea, 0xc8, 0xa3, 0x48, 0x24, 0x9d, 0x75, 0x27, 0x48, 0xec, 0x03, 0xb0, 0xbb, 0xdf, 0x33, 0x90, 0xe3, 0x93, 0xed, 0x68, 0x24, 0x39, 0x12, 0xdf, 0xea, 0xee, 0x8c, 0x9f, 0x96, 0xde, 0x42, 0x46, 0x8c, 0x2b, 0x17, 0x83, 0x36, 0xfb, 0xf4, 0xf7, 0xff, 0x79, 0xb9, 0x45, 0x41, 0xc9, 0x56, 0x1a, 0x6b, 0x0c, 0xa4, 0x1a, 0xdd, 0x6b, 0x95, 0xe8, 0x03, 0x0f, 0x09, 0x29, 0x40, 0x1b, 0xea, 0x87, 0xfa, 0xb9, 0x18, 0xa9, 0x95, 0x07, 0x7c, 0x2f, 0x7c, 0x33, 0xfb, 0xc5, 0x11, 0x5e, 0x81, 0x0e, 0xbc, 0xae, 0xec, 0xb3, 0xe1, 0x4a, 0x26, 0x56, 0xe8, 0x5b, 0x11, 0x9d, 0x37, 0x06, 0x9b, 0x34, 0x31, 0x6e, 0xa3, 0xba, 0x41, 0xbc, 0x11, 0xd8, 0xc5, 0x15, 0xc9, 0x30, 0x2c, 0x9b, 0xb6, 0x71, 0xd8, 0x7c, 0xbc, 0x38, 0x2f, 0xd5, 0xbd, 0x30, 0x96, 0xd4, 0xa3, 0x00, 0x77, 0x9d, 0x55, 0x4a, 0x33, 0x53, 0xb6, 0xb3, 0x35, 0x1b, 0xae, 0xe5, 0xdc, 0x22, 0x23, 0x85, 0x95, 0x88, 0xf9, 0x3b, 0xbf, 0x74, 0x13, 0xaa, 0xcb, 0x0a, 0x60, 0x79, 0x13, 0x79, 0xc0, 0x4a, 0x02, 0xdb, 0x1c, 0xc9, 0xff, 0x60, 0x57, 0x9a, 0x70, 0x28, 0x58, 0x60, 0xbc, 0x57, 0x07, 0xc7, 0x47, 0x1a, 0x45, 0x71, 0x76, 0x94, 0xfb, 0x05, 0xad, 0xec, 0x12, 0x29, 0x5a, 0x44, 0x6a, 0x81, 0xd9, 0xc6, 0xf0, 0xb6, 0x9b, 0x97, 0x83, 0x69, 0xfb, 0xdc, 0x0d, 0x4a, 0x67, 0xbc, 0x72, 0xf5, 0x43, 0x5e, 0x9b, 0x13, 0xf2, 0xe4, 0x6d, 0x49, 0xdb, 0x76, 0xcb, 0x42, 0x6a, 0x3c, 0x9f, 0xa1, 0xfe, 0x5e, 0xca, 0x0a, 0xfc, 0xfa, 0x39, 0x27, 0xd1, 0x3c, 0xcb, 0x9a, 0xde, 0x4c, 0x6b, 0x09, 0x8b, 0x49, 0xfd, 0x1e, 0x3d, 0x5e, 0x67, 0x7c, 0x57, 0xad, 0x90, 0xcc, 0x46, 0x5f, 0x5c, 0xae, 0x6a, 0x9c, 0xb2, 0xcd, 0x2c, 0x89, 0x78, 0xcf, 0xf1, 0x49, 0x96, 0x55, 0x1e, 0x04, 0xef, 0x0e, 0x1c, 0xde, 0x6c, 0x96, 0x51, 0x00, 0xee, 0x9a, 0x1f, 0x8d, 0x61, 0xbc, 0xeb, 0xb1, 0xa6, 0xa5, 0x21, 0x8b, 0xa7, 0xf8, 0x25, 0x41, 0x48, 0x62, 0x5b, 0x01, 0x6c, 0x7c, 0x2a, 0xe8, 0xff, 0xf9, 0xf9, 0x1f, 0xe2, 0x79, 0x2e, 0xd1, 0xff, 0xa3, 0x2e, 0x1c, 0x3a, 0x1a, 0x5d, 0x2b, 0x7b, 0x87, 0x25, 0x22, 0xa4, 0x90, 0xea, 0x26, 0x9d, 0xdd, 0x13, 0x60, 0x4c, 0x10, 0x03, 0xf6, 0x99, 0xd3, 0x21, 0x0c, 0x69, 0xc6, 0xd8, 0xc8, 0x9e, 0x94, 0x89, 0x51, 0x21, 0xe3, 0x9a, 0xcd, 0xda, 0x54, 0x72, 0x64, 0xae, 0x94, 0x79, 0x36, 0x81, 0x44, 0x14, 0x6d, 0x3a, 0x0e, 0xa6, 0x30, 0xbf, 0x95, 0x99, 0xa6, 0xf5, 0x7f, 0x4f, 0xef, 0xc6, 0x71, 0x2f, 0x36, 0x13, 0x14, 0xa2, 0x9d, 0xc2, 0x0c, 0x0d, 0x4e, 0xc0, 0x02, 0xd3, 0x6f, 0xee, 0x98, 0x5e, 0x24, 0x31, 0x74, 0x11, 0x96, 0x6e, 0x43, 0x57, 0xe8, 0x8e, 0xa0, 0x8d, 0x3d, 0x79, 0x38, 0x20, 0xc2, 0x0f, 0xb4, 0x75, 0x99, 0x3b, 0xb1, 0xf0, 0xe8, 0xe1, 0xda, 0xf9, 0xd4, 0xe6, 0xd6, 0xf4, 0x8a, 0x32, 0x4a, 0x4a, 0x25, 0xa8, 0xd9, 0x60, 0xd6, 0x33, 0x31, 0x97, 0xb9, 0xb6, 0xed, 0x5f, 0xfc, 0x15, 0xbd, 0x13, 0xc0, 0x3a, 0x3f, 0x1f, 0x2d, 0x09, 0x1d, 0xeb, 0x69, 0x6a, 0xfe, 0xd7, 0x95, 0x3e, 0x8a, 0x4e, 0xe1, 0x6e, 0x61, 0xb2, 0x6c, 0xe3, 0x2b, 0x70, 0x60, 0x7e, 0x8c, 0xe4, 0xdd, 0x27, 0x30, 0x7e, 0x0d, 0xc7, 0xb7, 0x9a, 0x1a, 0x3c, 0xcc, 0xa7, 0x22, 0x77, 0x14, 0x05, 0x50, 0x57, 0x31, 0x1b, 0xc8, 0xbf, 0xce, 0x52, 0xaf, 0x9c, 0x8e, 0x10, 0x2e, 0xd2, 0x16, 0xb6, 0x6e, 0x43, 0x10, 0xaf, 0x8b, 0xde, 0x1d, 0x60, 0xb2, 0x7d, 0xe6, 0x2f, 0x08, 0x10, 0x12, 0x7e, 0xb4, 0x76, 0x45, 0xb6, 0xd8, 0x9b, 0x26, 0x40, 0xa1, 0x63, 0x5c, 0x7a, 0x2a, 0xb1, 0x8c, 0xd6, 0xa4, 0x6f, 0x5a, 0xae, 0x33, 0x7e, 0x6d, 0x71, 0xf5, 0xc8, 0x6d, 0x80, 0x1c, 0x35, 0xfc, 0x3f, 0xc1, 0xa6, 0xc6, 0x1a, 0x15, 0x04, 0x6d, 0x76, 0x38, 0x32, 0x95, 0xb2, 0x51, 0x1a, 0xe9, 0x3e, 0x89, 0x9f, 0x0c, 0x79 }; + var out: [Sha3_256.digest_length]u8 = undefined; + + Sha3_256.hash(&msg, &out, .{}); + try htest.assertEqual("5780048dfa381a1d01c747906e4a08711dd34fd712ecd7c6801dd2b38fd81a89", &out); + + var h = Sha3_256.init(.{}); + h.update(msg[0..64]); + h.update(msg[64..613]); + h.final(&out); + try htest.assertEqual("5780048dfa381a1d01c747906e4a08711dd34fd712ecd7c6801dd2b38fd81a89", &out); +} From 12b74b2c0587e1f3f0ce8fdae47ab76e790b0d8e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Mar 2023 21:04:22 -0700 Subject: [PATCH 062/294] CI: more aggressively check zig1 bootstrapping This would have caught the problem we are seeing in #14799. --- ci/aarch64-linux-debug.sh | 6 ++++++ ci/aarch64-linux-release.sh | 6 ++++++ ci/x86_64-linux-debug.sh | 6 ++++++ ci/x86_64-linux-release.sh | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index ca085d2779..94f40c557b 100644 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -98,3 +98,9 @@ unset CXX ninja install stage3/bin/zig test ../test/behavior.zig -I../test +stage3/bin/zig build -p stage4 \ + -Dstatic-llvm \ + -Dtarget=native-native-musl \ + --search-prefix "$PREFIX" \ + --zig-lib-dir "$(pwd)/../lib" +stage4/bin/zig test ../test/behavior.zig -I../test diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index e3dc4530a8..65d6063f25 100644 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -98,3 +98,9 @@ unset CXX ninja install stage3/bin/zig test ../test/behavior.zig -I../test +stage3/bin/zig build -p stage4 \ + -Dstatic-llvm \ + -Dtarget=native-native-musl \ + --search-prefix "$PREFIX" \ + --zig-lib-dir "$(pwd)/../lib" +stage4/bin/zig test ../test/behavior.zig -I../test diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index d3e16a3954..7f2382f04a 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -97,3 +97,9 @@ unset CXX ninja install stage3/bin/zig test ../test/behavior.zig -I../test +stage3/bin/zig build -p stage4 \ + -Dstatic-llvm \ + -Dtarget=native-native-musl \ + --search-prefix "$PREFIX" \ + --zig-lib-dir "$(pwd)/../lib" +stage4/bin/zig test ../test/behavior.zig -I../test diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index cec08fae84..cdb24e4a6f 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -114,3 +114,9 @@ unset CXX ninja install stage3/bin/zig test ../test/behavior.zig -I../test +stage3/bin/zig build -p stage4 \ + -Dstatic-llvm \ + -Dtarget=native-native-musl \ + --search-prefix "$PREFIX" \ + --zig-lib-dir "$(pwd)/../lib" +stage4/bin/zig test ../test/behavior.zig -I../test From 87738cad8607a31537a5c16826ab315990bc73c6 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Thu, 9 Mar 2023 19:14:17 +0100 Subject: [PATCH 063/294] wasm-linker: store symbol's virtual address For data symbols we will now store its virtual address. This means we do no longer have to calculate it each time a relocation asks for the address. This is now done for all data symbols only once rather than every single relocation for that symbol. This now also allows us directly store the virtual address of synthetic symbols without having to create an atom for them. This means we also don't need to have a "synthetic" segment any longer and do not emit the synthetic symbols such as __heap_end and __heap_base into the final binary. --- src/link/Wasm.zig | 53 ++++++++++++++++++++++++++++------------ src/link/Wasm/Atom.zig | 24 +----------------- src/link/Wasm/Object.zig | 2 ++ src/link/Wasm/Symbol.zig | 3 +++ 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 513b567210..9a4166b052 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -468,6 +468,7 @@ fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !Symbol .flags = 0, .tag = tag, .index = undefined, + .virtual_address = undefined, }); try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {}); try wasm.globals.put(wasm.base.allocator, name_offset, loc); @@ -1011,6 +1012,7 @@ pub fn allocateSymbol(wasm: *Wasm) !u32 { .flags = @enumToInt(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = undefined, // will be set after updateDecl .index = undefined, // will be set after updateDecl + .virtual_address = undefined, // will be set during atom allocation }; if (wasm.symbols_free_list.popOrNull()) |index| { wasm.symbols.items[index] = symbol; @@ -1246,6 +1248,7 @@ pub fn lowerUnnamedConst(wasm: *Wasm, tv: TypedValue, decl_index: Module.Decl.In .flags = @enumToInt(Symbol.Flag.WASM_SYM_BINDING_LOCAL), .tag = .data, .index = undefined, + .virtual_address = undefined, }; try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, atom.symbolLoc(), {}); @@ -1292,6 +1295,7 @@ pub fn getGlobalSymbol(wasm: *Wasm, name: []const u8) !u32 { .flags = 0, .index = undefined, // index to type will be set after merging function symbols .tag = .function, + .virtual_address = undefined, }; symbol.setGlobal(true); symbol.setUndefined(true); @@ -1788,6 +1792,30 @@ fn allocateAtoms(wasm: *Wasm) !void { } } +/// For each data symbol, sets the virtual address. +fn allocateVirtualAddresses(wasm: *Wasm) void { + for (wasm.resolved_symbols.keys()) |loc| { + const symbol = loc.getSymbol(wasm); + if (symbol.tag != .data) { + continue; // only data symbols have virtual addresses + } + const atom_index = wasm.symbol_atom.get(loc) orelse { + // synthetic symbol that does not contain an atom + continue; + }; + + const atom = wasm.getAtom(atom_index); + const merge_segment = wasm.base.options.output_mode != .Obj; + const segment_info = if (atom.file) |object_index| blk: { + break :blk wasm.objects.items[object_index].segment_info; + } else wasm.segment_info.values(); + const segment_name = segment_info[symbol.index].outputName(merge_segment); + const segment_index = wasm.data_segments.get(segment_name).?; + const segment = wasm.segments.items[segment_index]; + symbol.virtual_address = atom.offset + segment.offset; + } +} + fn sortDataSegments(wasm: *Wasm) !void { var new_mapping: std.StringArrayHashMapUnmanaged(u32) = .{}; try new_mapping.ensureUnusedCapacity(wasm.base.allocator, wasm.data_segments.count()); @@ -2137,13 +2165,10 @@ fn setupExports(wasm: *Wasm) !void { break :blk try wasm.string_table.put(wasm.base.allocator, sym_name); }; const exp: types.Export = if (symbol.tag == .data) exp: { - const atom_index = wasm.symbol_atom.get(sym_loc).?; - const atom = wasm.getAtom(atom_index); - const va = atom.getVA(wasm, symbol); const global_index = @intCast(u32, wasm.imported_globals_count + wasm.wasm_globals.items.len); try wasm.wasm_globals.append(wasm.base.allocator, .{ .global_type = .{ .valtype = .i32, .mutable = false }, - .init = .{ .i32_const = @intCast(i32, va) }, + .init = .{ .i32_const = @intCast(i32, symbol.virtual_address) }, }); break :exp .{ .name = export_name, @@ -2240,12 +2265,8 @@ fn setupMemory(wasm: *Wasm) !void { // One of the linked object files has a reference to the __heap_base symbol. // We must set its virtual address so it can be used in relocations. if (wasm.findGlobalSymbol("__heap_base")) |loc| { - const segment_index = wasm.data_segments.get(".synthetic").?; - const segment = &wasm.segments.items[segment_index]; - segment.offset = 0; // for simplicity we store the entire VA into atom's offset. - const atom_index = wasm.symbol_atom.get(loc).?; - const atom = wasm.getAtomPtr(atom_index); - atom.offset = @intCast(u32, mem.alignForwardGeneric(u64, memory_ptr, heap_alignment)); + const symbol = loc.getSymbol(wasm); + symbol.virtual_address = @intCast(u32, mem.alignForwardGeneric(u64, memory_ptr, heap_alignment)); } // Setup the max amount of pages @@ -2274,12 +2295,8 @@ fn setupMemory(wasm: *Wasm) !void { log.debug("Total memory pages: {d}", .{wasm.memories.limits.min}); if (wasm.findGlobalSymbol("__heap_end")) |loc| { - const segment_index = wasm.data_segments.get(".synthetic").?; - const segment = &wasm.segments.items[segment_index]; - segment.offset = 0; - const atom_index = wasm.symbol_atom.get(loc).?; - const atom = wasm.getAtomPtr(atom_index); - atom.offset = @intCast(u32, memory_ptr); + const symbol = loc.getSymbol(wasm); + symbol.virtual_address = @intCast(u32, memory_ptr); } if (wasm.base.options.max_memory) |max_memory| { @@ -2417,6 +2434,7 @@ pub fn getErrorTableSymbol(wasm: *Wasm) !u32 { .tag = .data, .flags = 0, .index = 0, + .virtual_address = undefined, }; symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); @@ -2449,6 +2467,7 @@ fn populateErrorNameTable(wasm: *Wasm) !void { .tag = .data, .flags = 0, .index = 0, + .virtual_address = undefined, }; names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); @@ -2748,6 +2767,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l try wasm.allocateAtoms(); try wasm.setupMemory(); + wasm.allocateVirtualAddresses(); wasm.mapFunctionTable(); try wasm.mergeSections(); try wasm.mergeTypes(); @@ -2866,6 +2886,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod try wasm.allocateAtoms(); try wasm.setupMemory(); + wasm.allocateVirtualAddresses(); wasm.mapFunctionTable(); try wasm.mergeSections(); try wasm.mergeTypes(); diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index e719f8dfcc..0c9d761f05 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -89,21 +89,6 @@ pub fn getSymbolIndex(atom: Atom) ?u32 { return atom.sym_index; } -/// Returns the virtual address of the `Atom`. This is the address starting -/// from the first entry within a section. -pub fn getVA(atom: Atom, wasm: *const Wasm, symbol: *const Symbol) u32 { - if (symbol.tag == .function) return atom.offset; - std.debug.assert(symbol.tag == .data); - const merge_segment = wasm.base.options.output_mode != .Obj; - const segment_info = if (atom.file) |object_index| blk: { - break :blk wasm.objects.items[object_index].segment_info; - } else wasm.segment_info.values(); - const segment_name = segment_info[symbol.index].outputName(merge_segment); - const segment_index = wasm.data_segments.get(segment_name).?; - const segment = wasm.segments.items[segment_index]; - return segment.offset + atom.offset; -} - /// Resolves the relocations within the atom, writing the new value /// at the calculated offset. pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void { @@ -186,14 +171,7 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa if (symbol.isUndefined()) { return 0; } - const target_atom_index = wasm_bin.symbol_atom.get(target_loc) orelse { - // this can only occur during incremental-compilation when a relocation - // still points to a freed decl. It is fine to emit the value 0 here - // as no actual code will point towards it. - return 0; - }; - const target_atom = wasm_bin.getAtom(target_atom_index); - const va = @intCast(i32, target_atom.getVA(wasm_bin, symbol)); + const va = @intCast(i64, symbol.virtual_address); return @intCast(u32, va + relocation.addend); }, .R_WASM_EVENT_INDEX_LEB => return symbol.index, diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index 82cab2528a..45c9464ec8 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -270,6 +270,7 @@ fn checkLegacyIndirectFunctionTable(object: *Object) !?Symbol { .name = table_import.name, .tag = .table, .index = 0, + .virtual_address = undefined, }; table_symbol.setFlag(.WASM_SYM_UNDEFINED); table_symbol.setFlag(.WASM_SYM_NO_STRIP); @@ -758,6 +759,7 @@ fn Parser(comptime ReaderType: type) type { .tag = tag, .name = undefined, .index = undefined, + .virtual_address = undefined, }; switch (tag) { diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 089eee289e..156b507a32 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -20,6 +20,9 @@ name: u32, index: u32, /// Represents the kind of the symbol, such as a function or global. tag: Tag, +/// Contains the virtual address of the symbol, relative to the start of its section. +/// This differs from the offset of an `Atom` which is relative to the start of a segment. +virtual_address: u32, pub const Tag = enum { function, From 95f6a5935a675efe6d30bc2388e7a0bc6b742c6d Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:20:57 +0100 Subject: [PATCH 064/294] TurboSHAKE: change default delimiter to 0x1F (#14857) The TurboSHAKE paper just got published: https://eprint.iacr.org/2023/342.pdf and unlike the previous K12 paper, suggests 0x1F instead of 0x01 as the default value for "D". --- lib/std/crypto/sha3.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/crypto/sha3.zig b/lib/std/crypto/sha3.zig index d286e40666..1f48f87c53 100644 --- a/lib/std/crypto/sha3.zig +++ b/lib/std/crypto/sha3.zig @@ -20,7 +20,7 @@ pub const Shake256 = Shake(256); /// TurboSHAKE128 is a XOF (a secure hash function with a variable output length), with a 128 bit security level. /// It is based on the same permutation as SHA3 and SHAKE128, but which much higher performance. -/// The delimiter is 0x01 by default, but can be changed for context-separation. +/// The delimiter is 0x1f by default, but can be changed for context-separation. pub fn TurboShake128(comptime delim: ?u8) type { return TurboShake(128, delim); } @@ -96,7 +96,7 @@ pub fn Shake(comptime security_level: u11) type { /// The TurboSHAKE extendable output hash function. /// https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/ pub fn TurboShake(comptime security_level: u11, comptime delim: ?u8) type { - return ShakeLike(security_level, delim orelse 0x01, 12); + return ShakeLike(security_level, delim orelse 0x1f, 12); } fn ShakeLike(comptime security_level: u11, comptime delim: u8, comptime rounds: u5) type { From afb26f4e6b39431001eff75cc8ce19144cb5301a Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 2 Mar 2023 12:45:34 -0600 Subject: [PATCH 065/294] std.http: add connection pooling and make keep-alive requests by default --- lib/std/http/Client.zig | 211 ++++++++++++++++++++++++++++++---------- 1 file changed, 161 insertions(+), 50 deletions(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 7f62b2d597..d4d8f85ad1 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -21,11 +21,27 @@ ca_bundle: std.crypto.Certificate.Bundle = .{}, /// it will first rescan the system for root certificates. next_https_rescan_certs: bool = true, +connection_pool: std.TailQueue(Connection) = .{}, + +const ConnectionPool = std.TailQueue(Connection); +const ConnectionNode = ConnectionPool.Node; + +pub fn release(client: *Client, node: *ConnectionNode) void { + if (node.data.unusable) return node.data.close(client); + + client.connection_pool.append(node); +} + pub const Connection = struct { stream: net.Stream, /// undefined unless protocol is tls. - tls_client: std.crypto.tls.Client, + tls_client: std.crypto.tls.Client, // TODO: allocate this, it's currently 16 KB. protocol: Protocol, + host: []u8, + port: u16, + + // This connection has been part of a non keepalive request and cannot be added to the pool. + unusable: bool = false, pub const Protocol = enum { plain, tls }; @@ -56,6 +72,17 @@ pub const Connection = struct { .tls => return conn.tls_client.write(conn.stream, buffer), } } + + pub fn close(conn: *Connection, client: *const Client) void { + if (conn.protocol == .tls) { + // try to cleanly close the TLS connection, for any server that cares. + _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; + } + + conn.stream.close(); + + client.allocator.free(conn.host); + } }; /// TODO: emit error.UnexpectedEndOfStream or something like that when the read @@ -63,7 +90,7 @@ pub const Connection = struct { /// close_notify protection on underlying TLS streams. pub const Request = struct { client: *Client, - connection: Connection, + connection: *ConnectionNode, redirects_left: u32, response: Response, /// These are stored in Request so that they are available when following @@ -79,6 +106,7 @@ pub const Request = struct { header_bytes: std.ArrayListUnmanaged(u8), max_header_bytes: usize, next_chunk_length: u64, + done: bool, pub const Headers = struct { status: http.Status, @@ -86,6 +114,7 @@ pub const Request = struct { location: ?[]const u8 = null, content_length: ?u64 = null, transfer_encoding: ?http.TransferEncoding = null, + connection_close: bool = true, pub fn parse(bytes: []const u8) !Response.Headers { var it = mem.split(u8, bytes[0 .. bytes.len - 4], "\r\n"); @@ -126,6 +155,14 @@ pub const Request = struct { if (headers.transfer_encoding != null) return error.HttpHeadersInvalid; headers.transfer_encoding = std.meta.stringToEnum(http.TransferEncoding, header_value) orelse return error.HttpTransferEncodingUnsupported; + } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { + if (std.ascii.eqlIgnoreCase(header_value, "keep-alive")) { + headers.connection_close = false; + } else if (std.ascii.eqlIgnoreCase(header_value, "close")) { + headers.connection_close = true; + } else { + return error.HttpConnectionHeaderUnsupported; + } } } @@ -185,10 +222,10 @@ pub const Request = struct { chunk_r, chunk_data, - pub fn zeroMeansEnd(state: State) bool { - return switch (state) { - .finished, .chunk_data => true, - else => false, + pub fn isContent(self: State) bool { + return switch (self) { + .invalid, .start, .seen_r, .seen_rn, .seen_rnr => false, + .finished, .chunk_size_prefix_r, .chunk_size_prefix_n, .chunk_size, .chunk_r, .chunk_data => true, }; } }; @@ -201,6 +238,7 @@ pub const Request = struct { .max_header_bytes = max, .header_bytes_owned = true, .next_chunk_length = undefined, + .done = false, }; } @@ -212,6 +250,7 @@ pub const Request = struct { .max_header_bytes = buf.len, .header_bytes_owned = false, .next_chunk_length = undefined, + .done = false, }; } @@ -501,6 +540,7 @@ pub const Request = struct { pub const Headers = struct { version: http.Version = .@"HTTP/1.1", method: http.Method = .GET, + connection_close: bool = false, }; pub const Options = struct { @@ -545,6 +585,7 @@ pub const Request = struct { HttpHeadersExceededSizeLimit, HttpRedirectMissingLocation, HttpTransferEncodingUnsupported, + HttpConnectionHeaderUnsupported, HttpContentLengthUnknown, TooManyHttpRedirects, ShortHttpStatusLine, @@ -669,8 +710,9 @@ pub const Request = struct { assert(len <= buffer.len); var index: usize = 0; while (index < len) { - const zero_means_end = req.response.state.zeroMeansEnd(); const amt = try readAdvanced(req, buffer[index..]); + const zero_means_end = req.response.done and req.response.headers.status.class() != .redirect; + if (amt == 0 and zero_means_end) break; index += amt; } @@ -680,7 +722,29 @@ pub const Request = struct { /// This one can return 0 without meaning EOF. /// TODO change to readvAdvanced pub fn readAdvanced(req: *Request, buffer: []u8) !usize { - var in = buffer[0..try req.connection.read(buffer)]; + if (req.response.done) { + if (req.response.headers.status.class() == .redirect) { + if (req.redirects_left == 0) return error.TooManyHttpRedirects; + + const location = req.response.headers.location orelse + return error.HttpRedirectMissingLocation; + const new_url = try std.Uri.parse(location); + const new_req = try req.client.request(new_url, req.headers, .{ + .max_redirects = req.redirects_left - 1, + .header_strategy = if (req.response.header_bytes_owned) .{ + .dynamic = req.response.max_header_bytes, + } else .{ + .static = req.response.header_bytes.unusedCapacitySlice(), + }, + }); + req.deinit(); + req.* = new_req; + } else { + return 0; + } + } + + var in = buffer[0..try req.connection.data.read(buffer)]; var out_index: usize = 0; while (true) { switch (req.response.state) { @@ -698,24 +762,10 @@ pub const Request = struct { if (req.response.state == .finished) { req.response.headers = try Response.Headers.parse(req.response.header_bytes.items); - if (req.response.headers.status.class() == .redirect) { - if (req.redirects_left == 0) return error.TooManyHttpRedirects; - const location = req.response.headers.location orelse - return error.HttpRedirectMissingLocation; - const new_url = try std.Uri.parse(location); - const new_req = try req.client.request(new_url, req.headers, .{ - .max_redirects = req.redirects_left - 1, - .header_strategy = if (req.response.header_bytes_owned) .{ - .dynamic = req.response.max_header_bytes, - } else .{ - .static = req.response.header_bytes.unusedCapacitySlice(), - }, - }); - req.deinit(); - req.* = new_req; - assert(out_index == 0); - in = buffer[0..try req.connection.read(buffer)]; - continue; + if (req.response.headers.connection_close == true) { + req.connection.data.unusable = true; + } else { + req.connection.data.unusable = false; } if (req.response.headers.transfer_encoding) |transfer_encoding| { @@ -742,11 +792,29 @@ pub const Request = struct { return 0; }, .finished => { + const sub_amt = @intCast(usize, @min(req.response.next_chunk_length, in.len)); + req.response.next_chunk_length -= sub_amt; + + if (req.response.next_chunk_length == 0) { + req.client.release(req.connection); + req.connection = undefined; + + req.response.done = true; + assert(in.len == sub_amt); // TODO: figure out how to not read more than necessary. + + if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) return 0; + + mem.copy(u8, buffer[out_index..], in[0..sub_amt]); + return out_index + sub_amt; + } + + if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) return 0; + if (in.ptr == buffer.ptr) { - return in.len; + return sub_amt; } else { - mem.copy(u8, buffer[out_index..], in); - return out_index + in.len; + mem.copy(u8, buffer[out_index..], in[0..sub_amt]); + return out_index + sub_amt; } }, .chunk_size_prefix_r => switch (in.len) { @@ -793,7 +861,10 @@ pub const Request = struct { .invalid => return error.HttpHeadersInvalid, .chunk_data => { if (req.response.next_chunk_length == 0) { - req.response.state = .start; + req.response.done = true; + req.client.release(req.connection); + req.connection = undefined; + return out_index; } in = in[i..]; @@ -807,20 +878,27 @@ pub const Request = struct { // TODO https://github.com/ziglang/zig/issues/14039 const sub_amt = @intCast(usize, @min(req.response.next_chunk_length, in.len)); req.response.next_chunk_length -= sub_amt; - if (req.response.next_chunk_length > 0) { - if (in.ptr == buffer.ptr) { - return sub_amt; - } else { - mem.copy(u8, buffer[out_index..], in[0..sub_amt]); - out_index += sub_amt; - return out_index; - } + + if (req.response.next_chunk_length == 0) { + req.response.state = .chunk_size_prefix_r; + in = in[sub_amt..]; + + if (req.response.headers.status.class() == .redirect) continue; + + mem.copy(u8, buffer[out_index..], in[0..sub_amt]); + out_index += sub_amt; + continue; + } + + if (req.response.headers.status.class() == .redirect) return 0; + + if (in.ptr == buffer.ptr) { + return sub_amt; + } else { + mem.copy(u8, buffer[out_index..], in[0..sub_amt]); + out_index += sub_amt; + return out_index; } - mem.copy(u8, buffer[out_index..], in[0..sub_amt]); - out_index += sub_amt; - req.response.state = .chunk_size_prefix_r; - in = in[sub_amt..]; - continue; }, } } @@ -844,24 +922,52 @@ pub const Request = struct { }; pub fn deinit(client: *Client) void { + var next = client.connection_pool.first; + while (next) |node| { + next = node.next; + + node.data.close(client); + + client.allocator.destroy(node); + } + client.ca_bundle.deinit(client.allocator); client.* = undefined; } -pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) !Connection { - var conn: Connection = .{ +pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) !*ConnectionNode { + var potential = client.connection_pool.last; + while (potential) |node| { + const same_host = mem.eql(u8, node.data.host, host); + const same_port = node.data.port == port; + const same_protocol = node.data.protocol == protocol; + + if (same_host and same_port and same_protocol) { + client.connection_pool.remove(node); + return node; + } + + potential = node.prev; + } + + const conn = try client.allocator.create(ConnectionNode); + errdefer client.allocator.destroy(conn); + + conn.* = .{ .data = .{ .stream = try net.tcpConnectToHost(client.allocator, host, port), .tls_client = undefined, .protocol = protocol, - }; + .host = try client.allocator.dupe(u8, host), + .port = port, + } }; switch (protocol) { .plain => {}, .tls => { - conn.tls_client = try std.crypto.tls.Client.init(conn.stream, client.ca_bundle, host); + conn.data.tls_client = try std.crypto.tls.Client.init(conn.data.stream, client.ca_bundle, host); // This is appropriate for HTTPS because the HTTP headers contain // the content length which is used to detect truncation attacks. - conn.tls_client.allow_truncation_attacks = true; + conn.data.tls_client.allow_truncation_attacks = true; }, } @@ -908,10 +1014,15 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req try h.appendSlice(@tagName(headers.version)); try h.appendSlice("\r\nHost: "); try h.appendSlice(host); - try h.appendSlice("\r\nConnection: close\r\n\r\n"); + if (headers.connection_close) { + try h.appendSlice("\r\nConnection: close"); + } else { + try h.appendSlice("\r\nConnection: keep-alive"); + } + try h.appendSlice("\r\n\r\n"); const header_bytes = h.slice(); - try req.connection.writeAll(header_bytes); + try req.connection.data.writeAll(header_bytes); } return req; From 8d86194b6e31788263d2cbdd03e2a8cde4134c37 Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 6 Mar 2023 20:11:56 -0600 Subject: [PATCH 066/294] add error sets to tcpConnect* and tls.Client.init --- lib/std/crypto/tls/Client.zig | 50 ++++++++++++++++++++++++++++++++++- lib/std/net.zig | 34 +++++++++++++++++++++--- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 627ad7ea59..01bf957820 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -88,11 +88,59 @@ pub const StreamInterface = struct { } }; +pub fn InitError(comptime Stream: type) type { + return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error { + InsufficientEntropy, + DiskQuota, + LockViolation, + NotOpenForWriting, + TlsAlert, + TlsUnexpectedMessage, + TlsIllegalParameter, + TlsDecryptFailure, + TlsRecordOverflow, + TlsBadRecordMac, + CertificateFieldHasInvalidLength, + CertificateHostMismatch, + CertificatePublicKeyInvalid, + CertificateExpired, + CertificateFieldHasWrongDataType, + CertificateIssuerMismatch, + CertificateNotYetValid, + CertificateSignatureAlgorithmMismatch, + CertificateSignatureAlgorithmUnsupported, + CertificateSignatureInvalid, + CertificateSignatureInvalidLength, + CertificateSignatureNamedCurveUnsupported, + CertificateSignatureUnsupportedBitCount, + TlsCertificateNotVerified, + TlsBadSignatureScheme, + TlsBadRsaSignatureBitCount, + InvalidEncoding, + IdentityElement, + SignatureVerificationFailed, + TlsDecryptError, + TlsConnectionTruncated, + TlsDecodeError, + UnsupportedCertificateVersion, + CertificateTimeInvalid, + CertificateHasUnrecognizedObjectId, + CertificateHasInvalidBitString, + MessageTooLong, + NegativeIntoUnsigned, + TargetTooSmall, + BufferTooSmall, + InvalidSignature, + NotSquare, + NonCanonical, + }; +} + /// Initiates a TLS handshake and establishes a TLSv1.3 session with `stream`, which /// must conform to `StreamInterface`. /// /// `host` is only borrowed during this function call. -pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) !Client { +pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) InitError(@TypeOf(stream))!Client { const host_len = @intCast(u16, host.len); var random_buffer: [128]u8 = undefined; diff --git a/lib/std/net.zig b/lib/std/net.zig index 50a0f8b9d7..cf112cbab9 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -702,8 +702,10 @@ pub const AddressList = struct { } }; +pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError; + /// All memory allocated with `allocator` will be freed before this function returns. -pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) !Stream { +pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream { const list = try getAddressList(allocator, name, port); defer list.deinit(); @@ -720,7 +722,9 @@ pub fn tcpConnectToHost(allocator: mem.Allocator, name: []const u8, port: u16) ! return std.os.ConnectError.ConnectionRefused; } -pub fn tcpConnectToAddress(address: Address) !Stream { +pub const TcpConnectToAddressError = std.os.SocketError || std.os.ConnectError; + +pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream { const nonblock = if (std.io.is_async) os.SOCK.NONBLOCK else 0; const sock_flags = os.SOCK.STREAM | nonblock | (if (builtin.target.os.tag == .windows) 0 else os.SOCK.CLOEXEC); @@ -737,8 +741,32 @@ pub fn tcpConnectToAddress(address: Address) !Stream { return Stream{ .handle = sockfd }; } +const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || error { + // TODO: break this up into error sets from the various underlying functions + + TemporaryNameServerFailure, + NameServerFailure, + AddressFamilyNotSupported, + UnknownHostName, + ServiceUnavailable, + Unexpected, + + HostLacksNetworkAddresses, + + InvalidCharacter, + InvalidEnd, + NonCanonical, + Overflow, + Incomplete, + InvalidIpv4Mapping, + InvalidIPAddressFormat, + + InterfaceNotFound, + FileSystem, +}; + /// Call `AddressList.deinit` on the result. -pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) !*AddressList { +pub fn getAddressList(allocator: mem.Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList { const result = blk: { var arena = std.heap.ArenaAllocator.init(allocator); errdefer arena.deinit(); From fd2f906d1ede2b65ba21eec59137b2d4b676eedc Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 6 Mar 2023 20:13:15 -0600 Subject: [PATCH 067/294] std.http: handle compressed payloads --- lib/std/http.zig | 10 + lib/std/http/Client.zig | 769 ++++++++++++++++++++++++++-------------- 2 files changed, 504 insertions(+), 275 deletions(-) diff --git a/lib/std/http.zig b/lib/std/http.zig index 7c2a2da605..d4cc259f19 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -253,6 +253,16 @@ pub const TransferEncoding = enum { gzip, }; +pub const Connection = enum { + keep_alive, + close, +}; + +pub const CustomHeader = struct { + name: []const u8, + value: []const u8, +}; + const std = @import("std.zig"); test { diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index d4d8f85ad1..cac6571798 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -21,27 +21,51 @@ ca_bundle: std.crypto.Certificate.Bundle = .{}, /// it will first rescan the system for root certificates. next_https_rescan_certs: bool = true, -connection_pool: std.TailQueue(Connection) = .{}, +connection_mutex: std.Thread.Mutex = .{}, +connection_pool: ConnectionPool = .{}, +connection_used: ConnectionPool = .{}, const ConnectionPool = std.TailQueue(Connection); const ConnectionNode = ConnectionPool.Node; -pub fn release(client: *Client, node: *ConnectionNode) void { - if (node.data.unusable) return node.data.close(client); +/// Acquires an existing connection from the connection pool. This function is threadsafe. +pub fn acquire(client: *Client, node: *ConnectionNode) void { + client.connection_mutex.lock(); + defer client.connection_mutex.unlock(); + client.connection_pool.remove(node); + client.connection_used.append(node); +} + +/// Tries to release a connection back to the connection pool. This function is threadsafe. +/// If the connection is marked as closing, it will be closed instead. +pub fn release(client: *Client, node: *ConnectionNode) void { + if (node.data.closing) { + node.data.close(client); + + return client.allocator.destroy(node); + } + + client.connection_mutex.lock(); + defer client.connection_mutex.unlock(); + + client.connection_used.remove(node); client.connection_pool.append(node); } +const DeflateDecompressor = std.compress.zlib.ZlibStream(Request.ReaderRaw); +const GzipDecompressor = std.compress.gzip.Decompress(Request.ReaderRaw); + pub const Connection = struct { stream: net.Stream, /// undefined unless protocol is tls. - tls_client: std.crypto.tls.Client, // TODO: allocate this, it's currently 16 KB. + tls_client: *std.crypto.tls.Client, // TODO: allocate this, it's currently 16 KB. protocol: Protocol, host: []u8, port: u16, // This connection has been part of a non keepalive request and cannot be added to the pool. - unusable: bool = false, + closing: bool = false, pub const Protocol = enum { plain, tls }; @@ -59,6 +83,24 @@ pub const Connection = struct { } } + pub const ReadError = std.net.Stream.ReadError || error{ + TlsConnectionTruncated, + TlsRecordOverflow, + TlsDecodeError, + TlsAlert, + TlsBadRecordMac, + Overflow, + TlsBadLength, + TlsIllegalParameter, + TlsUnexpectedMessage, + }; + + pub const Reader = std.io.Reader(*Connection, ReadError, read); + + pub fn reader(conn: *Connection) Reader { + return Reader{ .context = conn }; + } + pub fn writeAll(conn: *Connection, buffer: []const u8) !void { switch (conn.protocol) { .plain => return conn.stream.writeAll(buffer), @@ -73,10 +115,18 @@ pub const Connection = struct { } } + pub const WriteError = std.net.Stream.WriteError || error{}; + pub const Writer = std.io.Writer(*Connection, WriteError, write); + + pub fn writer(conn: *Connection) Writer { + return Writer{ .context = conn }; + } + pub fn close(conn: *Connection, client: *const Client) void { if (conn.protocol == .tls) { // try to cleanly close the TLS connection, for any server that cares. _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; + client.allocator.destroy(conn.tls_client); } conn.stream.close(); @@ -85,10 +135,10 @@ pub const Connection = struct { } }; -/// TODO: emit error.UnexpectedEndOfStream or something like that when the read -/// data does not match the content length. This is necessary since HTTPS disables -/// close_notify protection on underlying TLS streams. pub const Request = struct { + const read_buffer_size = 8192; + const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size); + client: *Client, connection: *ConnectionNode, redirects_left: u32, @@ -97,6 +147,11 @@ pub const Request = struct { /// redirects. headers: Headers, + /// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning. + read_buffer: [read_buffer_size]u8 = undefined, + read_buffer_start: ReadBufferIndex = 0, + read_buffer_len: ReadBufferIndex = 0, + pub const Response = struct { headers: Response.Headers, state: State, @@ -106,15 +161,24 @@ pub const Request = struct { header_bytes: std.ArrayListUnmanaged(u8), max_header_bytes: usize, next_chunk_length: u64, - done: bool, + done: bool = false, + + compression: union(enum) { + deflate: DeflateDecompressor, + gzip: GzipDecompressor, + none: void, + } = .none, pub const Headers = struct { status: http.Status, version: http.Version, location: ?[]const u8 = null, content_length: ?u64 = null, - transfer_encoding: ?http.TransferEncoding = null, - connection_close: bool = true, + transfer_encoding: ?http.TransferEncoding = null, // This should only ever be chunked, compression is handled separately. + transfer_compression: ?http.TransferEncoding = null, + connection: http.Connection = .close, + + number_of_headers: usize = 0, pub fn parse(bytes: []const u8) !Response.Headers { var it = mem.split(u8, bytes[0 .. bytes.len - 4], "\r\n"); @@ -137,6 +201,8 @@ pub const Request = struct { }; while (it.next()) |line| { + headers.number_of_headers += 1; + if (line.len == 0) return error.HttpHeadersInvalid; switch (line[0]) { ' ', '\t' => return error.HttpHeaderContinuationsUnsupported, @@ -152,14 +218,65 @@ pub const Request = struct { if (headers.content_length != null) return error.HttpHeadersInvalid; headers.content_length = try std.fmt.parseInt(u64, header_value, 10); } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) { - if (headers.transfer_encoding != null) return error.HttpHeadersInvalid; - headers.transfer_encoding = std.meta.stringToEnum(http.TransferEncoding, header_value) orelse + if (headers.transfer_encoding != null or headers.transfer_compression != null) return error.HttpHeadersInvalid; + + // Transfer-Encoding: second, first + // Transfer-Encoding: deflate, chunked + var iter = std.mem.splitBackwards(u8, header_value, ","); + + if (iter.next()) |first| { + const kind = std.meta.stringToEnum( + http.TransferEncoding, + std.mem.trim(u8, first, " "), + ) orelse + return error.HttpTransferEncodingUnsupported; + + switch (kind) { + .chunked => headers.transfer_encoding = .chunked, + .compress => headers.transfer_compression = .compress, + .deflate => headers.transfer_compression = .deflate, + .gzip => headers.transfer_compression = .gzip, + } + } + + if (iter.next()) |second| { + if (headers.transfer_compression != null) return error.HttpTransferEncodingUnsupported; + + const kind = std.meta.stringToEnum( + http.TransferEncoding, + std.mem.trim(u8, second, " "), + ) orelse + return error.HttpTransferEncodingUnsupported; + + switch (kind) { + .chunked => return error.HttpHeadersInvalid, // chunked must come last + .compress => return error.HttpTransferEncodingUnsupported, // compress not supported + .deflate => headers.transfer_compression = .deflate, + .gzip => headers.transfer_compression = .gzip, + } + } + + if (iter.next()) |_| return error.HttpTransferEncodingUnsupported; + } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) { + if (headers.transfer_compression != null) return error.HttpHeadersInvalid; + + const kind = std.meta.stringToEnum( + http.TransferEncoding, + std.mem.trim(u8, header_value, " "), + ) orelse return error.HttpTransferEncodingUnsupported; + + switch (kind) { + .chunked => return error.HttpHeadersInvalid, // not transfer encoding + .compress => return error.HttpTransferEncodingUnsupported, // compress not supported + .deflate => headers.transfer_compression = .deflate, + .gzip => headers.transfer_compression = .gzip, + } } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { if (std.ascii.eqlIgnoreCase(header_value, "keep-alive")) { - headers.connection_close = false; + headers.connection = .keep_alive; } else if (std.ascii.eqlIgnoreCase(header_value, "close")) { - headers.connection_close = true; + headers.connection = .close; } else { return error.HttpConnectionHeaderUnsupported; } @@ -238,7 +355,6 @@ pub const Request = struct { .max_header_bytes = max, .header_bytes_owned = true, .next_chunk_length = undefined, - .done = false, }; } @@ -250,7 +366,6 @@ pub const Request = struct { .max_header_bytes = buf.len, .header_bytes_owned = false, .next_chunk_length = undefined, - .done = false, }; } @@ -537,10 +652,19 @@ pub const Request = struct { } }; + pub const RequestTransfer = union(enum) { + content_length: u64, + chunked: void, + none: void, + }; + pub const Headers = struct { version: http.Version = .@"HTTP/1.1", method: http.Method = .GET, - connection_close: bool = false, + connection: http.Connection = .keep_alive, + transfer_encoding: RequestTransfer = .none, + + custom: []const http.CustomHeader = &[_]http.CustomHeader{}, }; pub const Options = struct { @@ -561,167 +685,131 @@ pub const Request = struct { }; }; - /// May be skipped if header strategy is buffer. + /// Frees all resources associated with the request. pub fn deinit(req: *Request) void { + switch (req.response.compression) { + .none => {}, + .deflate => |*deflate| deflate.deinit(), + .gzip => |*gzip| gzip.deinit(), + } + if (req.response.header_bytes_owned) { req.response.header_bytes.deinit(req.client.allocator); } + + if (!req.response.done) { + // If the response wasn't fully read, then we need to close the connection. + req.connection.data.closing = true; + req.client.release(req.connection); + } + req.* = undefined; } - pub const Reader = std.io.Reader(*Request, ReadError, read); - - pub fn reader(req: *Request) Reader { - return .{ .context = req }; - } - - pub fn readAll(req: *Request, buffer: []u8) !usize { - return readAtLeast(req, buffer, buffer.len); - } - - pub const ReadError = net.Stream.ReadError || error{ - // From HTTP protocol - HttpHeadersInvalid, - HttpHeadersExceededSizeLimit, - HttpRedirectMissingLocation, - HttpTransferEncodingUnsupported, - HttpConnectionHeaderUnsupported, - HttpContentLengthUnknown, + const ReadRawError = Connection.ReadError || std.Uri.ParseError || RequestError || error{ + UnexpectedEndOfStream, TooManyHttpRedirects, - ShortHttpStatusLine, - BadHttpVersion, - HttpHeaderContinuationsUnsupported, - UnsupportedUrlScheme, - UriMissingHost, - UnknownHostName, - - // Network problems - NetworkUnreachable, - HostLacksNetworkAddresses, - TemporaryNameServerFailure, - NameServerFailure, - ProtocolFamilyNotAvailable, - ProtocolNotSupported, - - // System resource problems - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - OutOfMemory, - - // TLS problems - InsufficientEntropy, - TlsConnectionTruncated, - TlsRecordOverflow, - TlsDecodeError, - TlsAlert, - TlsBadRecordMac, - TlsBadLength, - TlsIllegalParameter, - TlsUnexpectedMessage, - TlsDecryptFailure, - CertificateFieldHasInvalidLength, - CertificateHostMismatch, - CertificatePublicKeyInvalid, - CertificateExpired, - CertificateFieldHasWrongDataType, - CertificateIssuerMismatch, - CertificateNotYetValid, - CertificateSignatureAlgorithmMismatch, - CertificateSignatureAlgorithmUnsupported, - CertificateSignatureInvalid, - CertificateSignatureInvalidLength, - CertificateSignatureNamedCurveUnsupported, - CertificateSignatureUnsupportedBitCount, - TlsCertificateNotVerified, - TlsBadSignatureScheme, - TlsBadRsaSignatureBitCount, - TlsDecryptError, - UnsupportedCertificateVersion, - CertificateTimeInvalid, - CertificateHasUnrecognizedObjectId, - CertificateHasInvalidBitString, - CertificateAuthorityBundleTooBig, - - // TODO: convert to higher level errors - InvalidFormat, - InvalidPort, - UnexpectedCharacter, - Overflow, - InvalidCharacter, - AddressFamilyNotSupported, - AddressInUse, - AddressNotAvailable, - ConnectionPending, - ConnectionRefused, - FileNotFound, - PermissionDenied, - ServiceUnavailable, - SocketTypeNotSupported, - FileTooBig, - LockViolation, - NoSpaceLeft, - NotOpenForWriting, - InvalidEncoding, - IdentityElement, - NonCanonical, - SignatureVerificationFailed, - MessageTooLong, - NegativeIntoUnsigned, - TargetTooSmall, - BufferTooSmall, - InvalidSignature, - NotSquare, - DiskQuota, - InvalidEnd, - Incomplete, - InvalidIpv4Mapping, - InvalidIPAddressFormat, - BadPathName, - DeviceBusy, - FileBusy, - FileLocksNotSupported, - InvalidHandle, - InvalidUtf8, - NameTooLong, - NoDevice, - PathAlreadyExists, - PipeBusy, - SharingViolation, - SymLinkLoop, - FileSystem, - InterfaceNotFound, - AlreadyBound, - FileDescriptorNotASocket, - NetworkSubsystemFailed, - NotDir, - ReadOnlyFileSystem, - Unseekable, - MissingEndCertificateMarker, - InvalidPadding, - EndOfStream, - InvalidArgument, + HttpRedirectMissingLocation, + HttpHeadersInvalid, }; - pub fn read(req: *Request, buffer: []u8) ReadError!usize { - return readAtLeast(req, buffer, 1); - } + const ReaderRaw = std.io.Reader(*Request, ReadRawError, readRaw); + + /// Read from the underlying stream, without decompressing or parsing the headers. Must be called + /// after waitForCompleteHead() has returned successfully. + pub fn readRaw(req: *Request, buffer: []u8) ReadRawError!usize { + assert(req.response.state.isContent()); - pub fn readAtLeast(req: *Request, buffer: []u8, len: usize) !usize { - assert(len <= buffer.len); var index: usize = 0; - while (index < len) { - const amt = try readAdvanced(req, buffer[index..]); + while (index == 0) { + const amt = try req.readRawAdvanced(buffer[index..]); const zero_means_end = req.response.done and req.response.headers.status.class() != .redirect; if (amt == 0 and zero_means_end) break; index += amt; } + return index; } + fn checkForCompleteHead(req: *Request, buffer: []u8) !usize { + switch (req.response.state) { + .invalid => unreachable, + .start, .seen_r, .seen_rn, .seen_rnr => {}, + else => return 0, // No more headers to read. + } + + const i = req.response.findHeadersEnd(buffer[0..]); + if (req.response.state == .invalid) return error.HttpHeadersInvalid; + + const headers_data = buffer[0..i]; + if (req.response.header_bytes.items.len + headers_data.len > req.response.max_header_bytes) { + return error.HttpHeadersExceededSizeLimit; + } + try req.response.header_bytes.appendSlice(req.client.allocator, headers_data); + + if (req.response.state == .finished) { + req.response.headers = try Response.Headers.parse(req.response.header_bytes.items); + + if (req.response.headers.connection == .keep_alive) { + req.connection.data.closing = false; + } else { + req.connection.data.closing = true; + } + + if (req.response.headers.transfer_encoding) |transfer_encoding| { + switch (transfer_encoding) { + .chunked => { + req.response.next_chunk_length = 0; + req.response.state = .chunk_size; + }, + .compress => unreachable, + .deflate => unreachable, + .gzip => unreachable, + } + } else if (req.response.headers.content_length) |content_length| { + req.response.next_chunk_length = content_length; + } else { + req.response.done = true; + } + + return i; + } + + return 0; + } + + pub const WaitForCompleteHeadError = ReadRawError || error { + UnexpectedEndOfStream, + + HttpHeadersExceededSizeLimit, + ShortHttpStatusLine, + BadHttpVersion, + HttpHeaderContinuationsUnsupported, + HttpTransferEncodingUnsupported, + HttpConnectionHeaderUnsupported, + }; + + /// Reads a complete response head. Any leftover data is stored in the request. This function is idempotent. + pub fn waitForCompleteHead(req: *Request) WaitForCompleteHeadError!void { + if (req.response.state.isContent()) return; + + while (true) { + const nread = try req.connection.data.read(req.read_buffer[0..]); + const amt = try checkForCompleteHead(req, req.read_buffer[0..nread]); + + if (amt != 0) { + req.read_buffer_start = @intCast(ReadBufferIndex, amt); + req.read_buffer_len = @intCast(ReadBufferIndex, nread); + return; + } else if (nread == 0) { + return error.UnexpectedEndOfStream; + } + } + } + /// This one can return 0 without meaning EOF. - /// TODO change to readvAdvanced - pub fn readAdvanced(req: *Request, buffer: []u8) !usize { + fn readRawAdvanced(req: *Request, buffer: []u8) !usize { if (req.response.done) { if (req.response.headers.status.class() == .redirect) { if (req.redirects_left == 0) return error.TooManyHttpRedirects; @@ -744,82 +832,56 @@ pub const Request = struct { } } - var in = buffer[0..try req.connection.data.read(buffer)]; + // var in: []const u8 = undefined; + if (req.read_buffer_start == req.read_buffer_len) { + const nread = try req.connection.data.read(req.read_buffer[0..]); + if (nread == 0) return error.UnexpectedEndOfStream; + + req.read_buffer_start = 0; + req.read_buffer_len = @intCast(ReadBufferIndex, nread); + } + var out_index: usize = 0; while (true) { switch (req.response.state) { - .invalid => unreachable, - .start, .seen_r, .seen_rn, .seen_rnr => { - const i = req.response.findHeadersEnd(in); - if (req.response.state == .invalid) return error.HttpHeadersInvalid; - - const headers_data = in[0..i]; - if (req.response.header_bytes.items.len + headers_data.len > req.response.max_header_bytes) { - return error.HttpHeadersExceededSizeLimit; - } - try req.response.header_bytes.appendSlice(req.client.allocator, headers_data); - - if (req.response.state == .finished) { - req.response.headers = try Response.Headers.parse(req.response.header_bytes.items); - - if (req.response.headers.connection_close == true) { - req.connection.data.unusable = true; - } else { - req.connection.data.unusable = false; - } - - if (req.response.headers.transfer_encoding) |transfer_encoding| { - switch (transfer_encoding) { - .chunked => { - req.response.next_chunk_length = 0; - req.response.state = .chunk_size; - }, - .compress => return error.HttpTransferEncodingUnsupported, - .deflate => return error.HttpTransferEncodingUnsupported, - .gzip => return error.HttpTransferEncodingUnsupported, - } - } else if (req.response.headers.content_length) |content_length| { - req.response.next_chunk_length = content_length; - } else { - return error.HttpContentLengthUnknown; - } - - in = in[i..]; - continue; - } - - assert(out_index == 0); - return 0; - }, + .invalid, .start, .seen_r, .seen_rn, .seen_rnr => unreachable, .finished => { - const sub_amt = @intCast(usize, @min(req.response.next_chunk_length, in.len)); - req.response.next_chunk_length -= sub_amt; + // TODO https://github.com/ziglang/zig/issues/14039 + const buf_avail = req.read_buffer_len - req.read_buffer_start; + const data_avail = req.response.next_chunk_length; + const out_avail = buffer.len; + + if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) { + const can_read = @intCast(usize, @min(buf_avail, data_avail)); + req.response.next_chunk_length -= can_read; + + if (req.response.next_chunk_length == 0) { + req.client.release(req.connection); + req.connection = undefined; + req.response.done = true; + continue; + } + + return 0; // skip over as much data as possible + } + + const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail)); + req.response.next_chunk_length -= can_read; + + mem.copy(u8, buffer[0..], req.read_buffer[req.read_buffer_start..][0..can_read]); + req.read_buffer_start += @intCast(ReadBufferIndex, can_read); if (req.response.next_chunk_length == 0) { req.client.release(req.connection); req.connection = undefined; - req.response.done = true; - assert(in.len == sub_amt); // TODO: figure out how to not read more than necessary. - - if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) return 0; - - mem.copy(u8, buffer[out_index..], in[0..sub_amt]); - return out_index + sub_amt; } - if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) return 0; - - if (in.ptr == buffer.ptr) { - return sub_amt; - } else { - mem.copy(u8, buffer[out_index..], in[0..sub_amt]); - return out_index + sub_amt; - } + return can_read; }, - .chunk_size_prefix_r => switch (in.len) { + .chunk_size_prefix_r => switch (req.read_buffer_len - req.read_buffer_start) { 0 => return out_index, - 1 => switch (in[0]) { + 1 => switch (req.read_buffer[req.read_buffer_start]) { '\r' => { req.response.state = .chunk_size_prefix_n; return out_index; @@ -829,9 +891,9 @@ pub const Request = struct { return error.HttpHeadersInvalid; }, }, - else => switch (int16(in[0..2])) { + else => switch (int16(req.read_buffer[req.read_buffer_start..][0..2])) { int16("\r\n") => { - in = in[2..]; + req.read_buffer_start += 2; req.response.state = .chunk_size; continue; }, @@ -841,11 +903,11 @@ pub const Request = struct { }, }, }, - .chunk_size_prefix_n => switch (in.len) { + .chunk_size_prefix_n => switch (req.read_buffer_len - req.read_buffer_start) { 0 => return out_index, - else => switch (in[0]) { + else => switch (req.read_buffer[req.read_buffer_start]) { '\n' => { - in = in[1..]; + req.read_buffer_start += 1; req.response.state = .chunk_size; continue; }, @@ -856,7 +918,7 @@ pub const Request = struct { }, }, .chunk_size, .chunk_r => { - const i = req.response.findChunkedLen(in); + const i = req.response.findChunkedLen(req.read_buffer[req.read_buffer_start..req.read_buffer_len]); switch (req.response.state) { .invalid => return error.HttpHeadersInvalid, .chunk_data => { @@ -867,7 +929,8 @@ pub const Request = struct { return out_index; } - in = in[i..]; + + req.read_buffer_start += @intCast(ReadBufferIndex, i); continue; }, .chunk_size => return out_index, @@ -876,34 +939,129 @@ pub const Request = struct { }, .chunk_data => { // TODO https://github.com/ziglang/zig/issues/14039 - const sub_amt = @intCast(usize, @min(req.response.next_chunk_length, in.len)); - req.response.next_chunk_length -= sub_amt; + const buf_avail = req.read_buffer_len - req.read_buffer_start; + const data_avail = req.response.next_chunk_length; + const out_avail = buffer.len - out_index; + + if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) { + const can_read = @intCast(usize, @min(buf_avail, data_avail)); + req.response.next_chunk_length -= can_read; + + if (req.response.next_chunk_length == 0) { + req.client.release(req.connection); + req.connection = undefined; + req.response.done = true; + continue; + } + + return 0; // skip over as much data as possible + } + + const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail)); + req.response.next_chunk_length -= can_read; + + mem.copy(u8, buffer[out_index..], req.read_buffer[req.read_buffer_start..][0..can_read]); + req.read_buffer_start += @intCast(ReadBufferIndex, can_read); + out_index += can_read; if (req.response.next_chunk_length == 0) { req.response.state = .chunk_size_prefix_r; - in = in[sub_amt..]; - if (req.response.headers.status.class() == .redirect) continue; - - mem.copy(u8, buffer[out_index..], in[0..sub_amt]); - out_index += sub_amt; continue; } - if (req.response.headers.status.class() == .redirect) return 0; - - if (in.ptr == buffer.ptr) { - return sub_amt; - } else { - mem.copy(u8, buffer[out_index..], in[0..sub_amt]); - out_index += sub_amt; - return out_index; - } + return out_index; }, } } } + pub const ReadError = DeflateDecompressor.Error || GzipDecompressor.Error || WaitForCompleteHeadError || error{ + BadHeader, + InvalidCompression, + StreamTooLong, + InvalidWindowSize, + }; + + pub const Reader = std.io.Reader(*Request, ReadError, read); + + pub fn reader(req: *Request) Reader { + return .{ .context = req }; + } + + pub fn read(req: *Request, buffer: []u8) ReadError!usize { + if (!req.response.state.isContent()) try req.waitForCompleteHead(); + + if (req.response.compression == .none and req.response.state.isContent()) { + if (req.response.headers.transfer_compression) |compression| { + switch (compression) { + .compress => unreachable, + .deflate => req.response.compression = .{ + .deflate = try std.compress.zlib.zlibStream(req.client.allocator, ReaderRaw{ .context = req }), + }, + .gzip => req.response.compression = .{ + .gzip = try std.compress.gzip.decompress(req.client.allocator, ReaderRaw{ .context = req }), + }, + .chunked => unreachable, + } + } + } + + return switch (req.response.compression) { + .deflate => |*deflate| try deflate.read(buffer), + .gzip => |*gzip| try gzip.read(buffer), + else => try req.readRaw(buffer), + }; + } + + pub fn readAll(req: *Request, buffer: []u8) !usize { + var index: usize = 0; + while (index < buffer.len) { + const amt = try read(req, buffer[index..]); + if (amt == 0) break; + index += amt; + } + return index; + } + + pub const WriteError = Connection.WriteError || error{MessageTooLong}; + + pub const Writer = std.io.Writer(*Request, WriteError, write); + + pub fn writer(req: *Request) Writer { + return .{ .context = req }; + } + + /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent. + pub fn write(req: *Request, bytes: []const u8) !usize { + switch (req.headers.transfer_encoding) { + .chunked => { + try req.connection.data.writer().print("{x}\r\n", .{bytes.len}); + try req.connection.data.writeAll(bytes); + try req.connection.data.writeAll("\r\n"); + + return bytes.len; + }, + .content_length => |*len| { + if (len.* < bytes.len) return error.MessageTooLong; + + const amt = try req.connection.data.write(bytes); + len.* -= amt; + return amt; + }, + .none => return error.NotWriteable, + } + } + + /// Finish the body of a request. This notifies the server that you have no more data to send. + pub fn finish(req: *Request) !void { + switch (req.headers.transfer_encoding) { + .chunked => try req.connection.data.writeAll("0\r\n"), + .content_length => |len| if (len != 0) return error.MessageNotCompleted, + .none => {}, + } + } + inline fn int16(array: *const [2]u8) u16 { return @bitCast(u16, array.*); } @@ -917,6 +1075,10 @@ pub const Request = struct { } test { + const builtin = @import("builtin"); + + if (builtin.os.tag == .wasi) return error.SkipZigTest; + _ = Response; } }; @@ -931,23 +1093,39 @@ pub fn deinit(client: *Client) void { client.allocator.destroy(node); } + next = client.connection_used.first; + while (next) |node| { + next = node.next; + + node.data.close(client); + + client.allocator.destroy(node); + } + client.ca_bundle.deinit(client.allocator); client.* = undefined; } -pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) !*ConnectionNode { - var potential = client.connection_pool.last; - while (potential) |node| { - const same_host = mem.eql(u8, node.data.host, host); - const same_port = node.data.port == port; - const same_protocol = node.data.protocol == protocol; +pub const ConnectError = std.mem.Allocator.Error || std.net.TcpConnectToHostError || std.crypto.tls.Client.InitError(std.net.Stream); - if (same_host and same_port and same_protocol) { - client.connection_pool.remove(node); - return node; +pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionNode { + { // Search through the connection pool for a potential connection. + client.connection_mutex.lock(); + defer client.connection_mutex.unlock(); + + var potential = client.connection_pool.last; + while (potential) |node| { + const same_host = mem.eql(u8, node.data.host, host); + const same_port = node.data.port == port; + const same_protocol = node.data.protocol == protocol; + + if (same_host and same_port and same_protocol) { + client.acquire(node); + return node; + } + + potential = node.prev; } - - potential = node.prev; } const conn = try client.allocator.create(ConnectionNode); @@ -964,17 +1142,35 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio switch (protocol) { .plain => {}, .tls => { - conn.data.tls_client = try std.crypto.tls.Client.init(conn.data.stream, client.ca_bundle, host); + conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client); + conn.data.tls_client.* = try std.crypto.tls.Client.init(conn.data.stream, client.ca_bundle, host); // This is appropriate for HTTPS because the HTTP headers contain // the content length which is used to detect truncation attacks. conn.data.tls_client.allow_truncation_attacks = true; }, } + { + client.connection_mutex.lock(); + defer client.connection_mutex.unlock(); + + client.connection_used.append(conn); + } + return conn; } -pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Request.Options) !Request { +pub const RequestError = ConnectError || Connection.WriteError || error{ + UnsupportedUrlScheme, + UriMissingHost, + + CertificateAuthorityBundleTooBig, + InvalidPadding, + MissingEndCertificateMarker, + Unseekable, +}; + +pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Request.Options) RequestError!Request { const protocol: Connection.Protocol = if (mem.eql(u8, uri.scheme, "http")) .plain else if (mem.eql(u8, uri.scheme, "https")) @@ -990,8 +1186,13 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req const host = uri.host orelse return error.UriMissingHost; if (client.next_https_rescan_certs and protocol == .tls) { - try client.ca_bundle.rescan(client.allocator); - client.next_https_rescan_certs = false; + client.connection_mutex.lock(); // TODO: this could be so much better than reusing the connection pool mutex. + defer client.connection_mutex.unlock(); + + if (client.next_https_rescan_certs) { + try client.ca_bundle.rescan(client.allocator); + client.next_https_rescan_certs = false; + } } var req: Request = .{ @@ -1006,23 +1207,39 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req }; { - var h = try std.BoundedArray(u8, 1000).init(0); - try h.appendSlice(@tagName(headers.method)); - try h.appendSlice(" "); - try h.appendSlice(uri.path); - try h.appendSlice(" "); - try h.appendSlice(@tagName(headers.version)); - try h.appendSlice("\r\nHost: "); - try h.appendSlice(host); - if (headers.connection_close) { - try h.appendSlice("\r\nConnection: close"); - } else { - try h.appendSlice("\r\nConnection: keep-alive"); - } - try h.appendSlice("\r\n\r\n"); + var buffered = std.io.bufferedWriter(req.connection.data.writer()); + const writer = buffered.writer(); - const header_bytes = h.slice(); - try req.connection.data.writeAll(header_bytes); + try writer.writeAll(@tagName(headers.method)); + try writer.writeByte(' '); + try writer.writeAll(uri.path); + try writer.writeByte(' '); + try writer.writeAll(@tagName(headers.version)); + try writer.writeAll("\r\nHost: "); + try writer.writeAll(host); + if (headers.connection == .close) { + try writer.writeAll("\r\nConnection: close"); + } else { + try writer.writeAll("\r\nConnection: keep-alive"); + } + try writer.writeAll("\r\nAccept-Encoding: gzip, deflate"); + + switch (headers.transfer_encoding) { + .chunked => try writer.writeAll("\r\nTransfer-Encoding: chunked"), + .content_length => |content_length| try writer.print("\r\nContent-Length: {d}", .{content_length}), + .none => {}, + } + + for (headers.custom) |header| { + try writer.writeAll("\r\n"); + try writer.writeAll(header.name); + try writer.writeAll(": "); + try writer.writeAll(header.value); + } + + try writer.writeAll("\r\n\r\n"); + + try buffered.flush(); } return req; @@ -1036,5 +1253,7 @@ test { return error.SkipZigTest; } + if (builtin.os.tag == .wasi) return error.SkipZigTest; + _ = Request; } From 0a4130f364c2714b206257d0cf589103da823407 Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 6 Mar 2023 23:35:35 -0600 Subject: [PATCH 068/294] std.http: handle relative redirects --- lib/std/Uri.zig | 109 ++++++++++++++++++++++---- lib/std/crypto/tls/Client.zig | 2 +- lib/std/http/Client.zig | 140 +++++++++++++++++++++++----------- lib/std/net.zig | 6 +- 4 files changed, 196 insertions(+), 61 deletions(-) diff --git a/lib/std/Uri.zig b/lib/std/Uri.zig index 015b6c34f6..eb6311a19b 100644 --- a/lib/std/Uri.zig +++ b/lib/std/Uri.zig @@ -16,15 +16,27 @@ fragment: ?[]const u8, /// Applies URI encoding and replaces all reserved characters with their respective %XX code. pub fn escapeString(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 { + return escapeStringWithFn(allocator, input, isUnreserved); +} + +pub fn escapePath(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 { + return escapeStringWithFn(allocator, input, isPathChar); +} + +pub fn escapeQuery(allocator: std.mem.Allocator, input: []const u8) error{OutOfMemory}![]const u8 { + return escapeStringWithFn(allocator, input, isQueryChar); +} + +pub fn escapeStringWithFn(allocator: std.mem.Allocator, input: []const u8, comptime keepUnescaped: fn (c: u8) bool) std.mem.Allocator.Error![]const u8 { var outsize: usize = 0; for (input) |c| { - outsize += if (isUnreserved(c)) @as(usize, 1) else 3; + outsize += if (keepUnescaped(c)) @as(usize, 1) else 3; } var output = try allocator.alloc(u8, outsize); var outptr: usize = 0; for (input) |c| { - if (isUnreserved(c)) { + if (keepUnescaped(c)) { output[outptr] = c; outptr += 1; } else { @@ -94,13 +106,14 @@ pub fn unescapeString(allocator: std.mem.Allocator, input: []const u8) error{Out pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort }; -/// Parses the URI or returns an error. +/// Parses the URI or returns an error. This function is not compliant, but is required to parse +/// some forms of URIs in the wild. Such as HTTP Location headers. /// The return value will contain unescaped strings pointing into the /// original `text`. Each component that is provided, will be non-`null`. -pub fn parse(text: []const u8) ParseError!Uri { +pub fn parseWithoutScheme(text: []const u8) ParseError!Uri { var reader = SliceReader{ .slice = text }; var uri = Uri{ - .scheme = reader.readWhile(isSchemeChar), + .scheme = "", .user = null, .password = null, .host = null, @@ -110,14 +123,6 @@ pub fn parse(text: []const u8) ParseError!Uri { .fragment = null, }; - // after the scheme, a ':' must appear - if (reader.get()) |c| { - if (c != ':') - return error.UnexpectedCharacter; - } else { - return error.InvalidFormat; - } - if (reader.peekPrefix("//")) { // authority part std.debug.assert(reader.get().? == '/'); std.debug.assert(reader.get().? == '/'); @@ -179,6 +184,76 @@ pub fn parse(text: []const u8) ParseError!Uri { return uri; } +/// Parses the URI or returns an error. +/// The return value will contain unescaped strings pointing into the +/// original `text`. Each component that is provided, will be non-`null`. +pub fn parse(text: []const u8) ParseError!Uri { + var reader = SliceReader{ .slice = text }; + const scheme = reader.readWhile(isSchemeChar); + + // after the scheme, a ':' must appear + if (reader.get()) |c| { + if (c != ':') + return error.UnexpectedCharacter; + } else { + return error.InvalidFormat; + } + + var uri = try parseWithoutScheme(reader.readUntilEof()); + uri.scheme = scheme; + + return uri; +} + +/// Resolves a URI against a base URI, conforming to RFC 3986, Section 5. +/// arena owns any memory allocated by this function. +pub fn resolve(Base: Uri, R: Uri, strict: bool, arena: std.mem.Allocator) !Uri { + var T: Uri = undefined; + + if (R.scheme.len > 0 and !((!strict) and (std.mem.eql(u8, R.scheme, Base.scheme)))) { + T.scheme = R.scheme; + T.user = R.user; + T.host = R.host; + T.port = R.port; + T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path }); + T.query = R.query; + } else { + if (R.host) |host| { + T.user = R.user; + T.host = host; + T.port = R.port; + T.path = R.path; + T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path }); + T.query = R.query; + } else { + if (R.path.len == 0) { + T.path = Base.path; + if (R.query) |query| { + T.query = query; + } else { + T.query = Base.query; + } + } else { + if (R.path[0] == '/') { + T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path }); + } else { + T.path = try std.fs.path.resolvePosix(arena, &.{ "/", Base.path, R.path }); + } + T.query = R.query; + } + + T.user = Base.user; + T.host = Base.host; + T.port = Base.port; + } + T.scheme = Base.scheme; + } + + T.fragment = R.fragment; + + return T; +} + const SliceReader = struct { const Self = @This(); @@ -284,6 +359,14 @@ fn isPathSeparator(c: u8) bool { }; } +fn isPathChar(c: u8) bool { + return isUnreserved(c) or isSubLimit(c) or c == '/' or c == ':' or c == '@'; +} + +fn isQueryChar(c: u8) bool { + return isPathChar(c) or c == '?'; +} + fn isQuerySeparator(c: u8) bool { return switch (c) { '#' => true, diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 01bf957820..bc59459ff9 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -89,7 +89,7 @@ pub const StreamInterface = struct { }; pub fn InitError(comptime Stream: type) type { - return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error { + return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || error{ InsufficientEntropy, DiskQuota, LockViolation, diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index cac6571798..5b3a74d292 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -29,9 +29,10 @@ const ConnectionPool = std.TailQueue(Connection); const ConnectionNode = ConnectionPool.Node; /// Acquires an existing connection from the connection pool. This function is threadsafe. -pub fn acquire(client: *Client, node: *ConnectionNode) void { - client.connection_mutex.lock(); - defer client.connection_mutex.unlock(); +/// If the caller already holds the connection mutex, it should pass `true` for `held`. +pub fn acquire(client: *Client, node: *ConnectionNode, held: bool) void { + if (!held) client.connection_mutex.lock(); + defer if (!held) client.connection_mutex.unlock(); client.connection_pool.remove(node); client.connection_used.append(node); @@ -40,16 +41,17 @@ pub fn acquire(client: *Client, node: *ConnectionNode) void { /// Tries to release a connection back to the connection pool. This function is threadsafe. /// If the connection is marked as closing, it will be closed instead. pub fn release(client: *Client, node: *ConnectionNode) void { + client.connection_mutex.lock(); + defer client.connection_mutex.unlock(); + + client.connection_used.remove(node); + if (node.data.closing) { node.data.close(client); return client.allocator.destroy(node); } - client.connection_mutex.lock(); - defer client.connection_mutex.unlock(); - - client.connection_used.remove(node); client.connection_pool.append(node); } @@ -83,7 +85,7 @@ pub const Connection = struct { } } - pub const ReadError = std.net.Stream.ReadError || error{ + pub const ReadError = net.Stream.ReadError || error{ TlsConnectionTruncated, TlsRecordOverflow, TlsDecodeError, @@ -115,7 +117,7 @@ pub const Connection = struct { } } - pub const WriteError = std.net.Stream.WriteError || error{}; + pub const WriteError = net.Stream.WriteError || error{}; pub const Writer = std.io.Writer(*Connection, WriteError, write); pub fn writer(conn: *Connection) Writer { @@ -139,14 +141,21 @@ pub const Request = struct { const read_buffer_size = 8192; const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size); + uri: Uri, client: *Client, connection: *ConnectionNode, - redirects_left: u32, response: Response, /// These are stored in Request so that they are available when following /// redirects. headers: Headers, + redirects_left: u32, + handle_redirects: bool, + compression_init: bool, + + /// Used as a allocator for resolving redirects locations. + arena: std.heap.ArenaAllocator, + /// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning. read_buffer: [read_buffer_size]u8 = undefined, read_buffer_start: ReadBufferIndex = 0, @@ -661,6 +670,7 @@ pub const Request = struct { pub const Headers = struct { version: http.Version = .@"HTTP/1.1", method: http.Method = .GET, + user_agent: []const u8 = "Zig (std.http)", connection: http.Connection = .keep_alive, transfer_encoding: RequestTransfer = .none, @@ -668,6 +678,7 @@ pub const Request = struct { }; pub const Options = struct { + handle_redirects: bool = true, max_redirects: u32 = 3, header_strategy: HeaderStrategy = .{ .dynamic = 16 * 1024 }, @@ -703,10 +714,11 @@ pub const Request = struct { req.client.release(req.connection); } + req.arena.deinit(); req.* = undefined; } - const ReadRawError = Connection.ReadError || std.Uri.ParseError || RequestError || error{ + const ReadRawError = Connection.ReadError || Uri.ParseError || RequestError || error{ UnexpectedEndOfStream, TooManyHttpRedirects, HttpRedirectMissingLocation, @@ -723,9 +735,7 @@ pub const Request = struct { var index: usize = 0; while (index == 0) { const amt = try req.readRawAdvanced(buffer[index..]); - const zero_means_end = req.response.done and req.response.headers.status.class() != .redirect; - - if (amt == 0 and zero_means_end) break; + if (amt == 0 and req.response.done) break; index += amt; } @@ -769,6 +779,8 @@ pub const Request = struct { } } else if (req.response.headers.content_length) |content_length| { req.response.next_chunk_length = content_length; + + if (content_length == 0) req.response.done = true; } else { req.response.done = true; } @@ -779,7 +791,7 @@ pub const Request = struct { return 0; } - pub const WaitForCompleteHeadError = ReadRawError || error { + pub const WaitForCompleteHeadError = ReadRawError || error{ UnexpectedEndOfStream, HttpHeadersExceededSizeLimit, @@ -810,27 +822,8 @@ pub const Request = struct { /// This one can return 0 without meaning EOF. fn readRawAdvanced(req: *Request, buffer: []u8) !usize { - if (req.response.done) { - if (req.response.headers.status.class() == .redirect) { - if (req.redirects_left == 0) return error.TooManyHttpRedirects; - - const location = req.response.headers.location orelse - return error.HttpRedirectMissingLocation; - const new_url = try std.Uri.parse(location); - const new_req = try req.client.request(new_url, req.headers, .{ - .max_redirects = req.redirects_left - 1, - .header_strategy = if (req.response.header_bytes_owned) .{ - .dynamic = req.response.max_header_bytes, - } else .{ - .static = req.response.header_bytes.unusedCapacitySlice(), - }, - }); - req.deinit(); - req.* = new_req; - } else { - return 0; - } - } + assert(req.response.state.isContent()); + if (req.response.done) return 0; // var in: []const u8 = undefined; if (req.read_buffer_start == req.read_buffer_len) { @@ -851,7 +844,7 @@ pub const Request = struct { const data_avail = req.response.next_chunk_length; const out_avail = buffer.len; - if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) { + if (req.handle_redirects and req.response.headers.status.class() == .redirect) { const can_read = @intCast(usize, @min(buf_avail, data_avail)); req.response.next_chunk_length -= can_read; @@ -859,7 +852,6 @@ pub const Request = struct { req.client.release(req.connection); req.connection = undefined; req.response.done = true; - continue; } return 0; // skip over as much data as possible @@ -943,7 +935,7 @@ pub const Request = struct { const data_avail = req.response.next_chunk_length; const out_avail = buffer.len - out_index; - if (req.response.state.isContent() and req.response.headers.status.class() == .redirect) { + if (req.handle_redirects and req.response.headers.status.class() == .redirect) { const can_read = @intCast(usize, @min(buf_avail, data_avail)); req.response.next_chunk_length -= can_read; @@ -990,9 +982,41 @@ pub const Request = struct { } pub fn read(req: *Request, buffer: []u8) ReadError!usize { - if (!req.response.state.isContent()) try req.waitForCompleteHead(); + while (true) { + if (!req.response.state.isContent()) try req.waitForCompleteHead(); - if (req.response.compression == .none and req.response.state.isContent()) { + if (req.handle_redirects and req.response.headers.status.class() == .redirect) { + assert(try req.readRaw(buffer) == 0); + + if (req.redirects_left == 0) return error.TooManyHttpRedirects; + + const location = req.response.headers.location orelse + return error.HttpRedirectMissingLocation; + const new_url = Uri.parse(location) catch try Uri.parseWithoutScheme(location); + + var new_arena = std.heap.ArenaAllocator.init(req.client.allocator); + const resolved_url = try req.uri.resolve(new_url, false, new_arena.allocator()); + errdefer new_arena.deinit(); + + req.arena.deinit(); + req.arena = new_arena; + + const new_req = try req.client.request(resolved_url, req.headers, .{ + .max_redirects = req.redirects_left - 1, + .header_strategy = if (req.response.header_bytes_owned) .{ + .dynamic = req.response.max_header_bytes, + } else .{ + .static = req.response.header_bytes.unusedCapacitySlice(), + }, + }); + req.deinit(); + req.* = new_req; + } else { + break; + } + } + + if (req.response.compression == .none) { if (req.response.headers.transfer_compression) |compression| { switch (compression) { .compress => unreachable, @@ -1084,6 +1108,8 @@ pub const Request = struct { }; pub fn deinit(client: *Client) void { + client.connection_mutex.lock(); + var next = client.connection_pool.first; while (next) |node| { next = node.next; @@ -1106,7 +1132,7 @@ pub fn deinit(client: *Client) void { client.* = undefined; } -pub const ConnectError = std.mem.Allocator.Error || std.net.TcpConnectToHostError || std.crypto.tls.Client.InitError(std.net.Stream); +pub const ConnectError = std.mem.Allocator.Error || net.TcpConnectToHostError || std.crypto.tls.Client.InitError(net.Stream); pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionNode { { // Search through the connection pool for a potential connection. @@ -1120,7 +1146,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio const same_protocol = node.data.protocol == protocol; if (same_host and same_port and same_protocol) { - client.acquire(node); + client.acquire(node, true); return node; } @@ -1168,6 +1194,7 @@ pub const RequestError = ConnectError || Connection.WriteError || error{ InvalidPadding, MissingEndCertificateMarker, Unseekable, + EndOfStream, }; pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Request.Options) RequestError!Request { @@ -1196,27 +1223,52 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req } var req: Request = .{ + .uri = uri, .client = client, .headers = headers, .connection = try client.connect(host, port, protocol), .redirects_left = options.max_redirects, + .handle_redirects = options.handle_redirects, + .compression_init = false, .response = switch (options.header_strategy) { .dynamic => |max| Request.Response.initDynamic(max), .static => |buf| Request.Response.initStatic(buf), }, + .arena = undefined, }; + req.arena = std.heap.ArenaAllocator.init(client.allocator); + { var buffered = std.io.bufferedWriter(req.connection.data.writer()); const writer = buffered.writer(); + const escaped_path = try Uri.escapePath(client.allocator, uri.path); + defer client.allocator.free(escaped_path); + + const escaped_query = if (uri.query) |q| try Uri.escapeQuery(client.allocator, q) else null; + defer if (escaped_query) |q| client.allocator.free(q); + + const escaped_fragment = if (uri.fragment) |f| try Uri.escapeQuery(client.allocator, f) else null; + defer if (escaped_fragment) |f| client.allocator.free(f); + try writer.writeAll(@tagName(headers.method)); try writer.writeByte(' '); - try writer.writeAll(uri.path); + try writer.writeAll(escaped_path); + if (escaped_query) |q| { + try writer.writeByte('?'); + try writer.writeAll(q); + } + if (escaped_fragment) |f| { + try writer.writeByte('#'); + try writer.writeAll(f); + } try writer.writeByte(' '); try writer.writeAll(@tagName(headers.version)); try writer.writeAll("\r\nHost: "); try writer.writeAll(host); + try writer.writeAll("\r\nUser-Agent: "); + try writer.writeAll(headers.user_agent); if (headers.connection == .close) { try writer.writeAll("\r\nConnection: close"); } else { diff --git a/lib/std/net.zig b/lib/std/net.zig index cf112cbab9..7222433fd5 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -741,9 +741,9 @@ pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream { return Stream{ .handle = sockfd }; } -const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || error { +const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.os.SocketError || std.os.BindError || error{ // TODO: break this up into error sets from the various underlying functions - + TemporaryNameServerFailure, NameServerFailure, AddressFamilyNotSupported, @@ -760,7 +760,7 @@ const GetAddressListError = std.mem.Allocator.Error || std.fs.File.OpenError || Incomplete, InvalidIpv4Mapping, InvalidIPAddressFormat, - + InterfaceNotFound, FileSystem, }; From 634e7155048aeaf15553d866783930f3d22b375c Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 8 Mar 2023 08:20:53 -0600 Subject: [PATCH 069/294] std.http: split Client's parts into their own files --- lib/std/http.zig | 5 + lib/std/http/Client.zig | 988 +------------------------------ lib/std/http/Client/Request.zig | 488 +++++++++++++++ lib/std/http/Client/Response.zig | 506 ++++++++++++++++ 4 files changed, 1010 insertions(+), 977 deletions(-) create mode 100644 lib/std/http/Client/Request.zig create mode 100644 lib/std/http/Client/Response.zig diff --git a/lib/std/http.zig b/lib/std/http.zig index d4cc259f19..ef89f09925 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -248,9 +248,14 @@ pub const Status = enum(u10) { pub const TransferEncoding = enum { chunked, + // compression is intentionally omitted here, as std.http.Client stores it as content-encoding +}; + +pub const ContentEncoding = enum { compress, deflate, gzip, + zstd, }; pub const Connection = enum { diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 5b3a74d292..d44b1d098d 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -13,6 +13,9 @@ const Uri = std.Uri; const Allocator = std.mem.Allocator; const testing = std.testing; +pub const Request = @import("Client/Request.zig"); +pub const Response = @import("Client/Response.zig"); + /// Used for tcpConnectToHost and storing HTTP headers when an externally /// managed buffer is not provided. allocator: Allocator, @@ -25,8 +28,8 @@ connection_mutex: std.Thread.Mutex = .{}, connection_pool: ConnectionPool = .{}, connection_used: ConnectionPool = .{}, -const ConnectionPool = std.TailQueue(Connection); -const ConnectionNode = ConnectionPool.Node; +pub const ConnectionPool = std.TailQueue(Connection); +pub const ConnectionNode = ConnectionPool.Node; /// Acquires an existing connection from the connection pool. This function is threadsafe. /// If the caller already holds the connection mutex, it should pass `true` for `held`. @@ -55,8 +58,9 @@ pub fn release(client: *Client, node: *ConnectionNode) void { client.connection_pool.append(node); } -const DeflateDecompressor = std.compress.zlib.ZlibStream(Request.ReaderRaw); -const GzipDecompressor = std.compress.gzip.Decompress(Request.ReaderRaw); +pub const DeflateDecompressor = std.compress.zlib.ZlibStream(Request.ReaderRaw); +pub const GzipDecompressor = std.compress.gzip.Decompress(Request.ReaderRaw); +pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Request.ReaderRaw, .{}); pub const Connection = struct { stream: net.Stream, @@ -137,976 +141,6 @@ pub const Connection = struct { } }; -pub const Request = struct { - const read_buffer_size = 8192; - const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size); - - uri: Uri, - client: *Client, - connection: *ConnectionNode, - response: Response, - /// These are stored in Request so that they are available when following - /// redirects. - headers: Headers, - - redirects_left: u32, - handle_redirects: bool, - compression_init: bool, - - /// Used as a allocator for resolving redirects locations. - arena: std.heap.ArenaAllocator, - - /// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning. - read_buffer: [read_buffer_size]u8 = undefined, - read_buffer_start: ReadBufferIndex = 0, - read_buffer_len: ReadBufferIndex = 0, - - pub const Response = struct { - headers: Response.Headers, - state: State, - header_bytes_owned: bool, - /// This could either be a fixed buffer provided by the API user or it - /// could be our own array list. - header_bytes: std.ArrayListUnmanaged(u8), - max_header_bytes: usize, - next_chunk_length: u64, - done: bool = false, - - compression: union(enum) { - deflate: DeflateDecompressor, - gzip: GzipDecompressor, - none: void, - } = .none, - - pub const Headers = struct { - status: http.Status, - version: http.Version, - location: ?[]const u8 = null, - content_length: ?u64 = null, - transfer_encoding: ?http.TransferEncoding = null, // This should only ever be chunked, compression is handled separately. - transfer_compression: ?http.TransferEncoding = null, - connection: http.Connection = .close, - - number_of_headers: usize = 0, - - pub fn parse(bytes: []const u8) !Response.Headers { - var it = mem.split(u8, bytes[0 .. bytes.len - 4], "\r\n"); - - const first_line = it.first(); - if (first_line.len < 12) - return error.ShortHttpStatusLine; - - const version: http.Version = switch (int64(first_line[0..8])) { - int64("HTTP/1.0") => .@"HTTP/1.0", - int64("HTTP/1.1") => .@"HTTP/1.1", - else => return error.BadHttpVersion, - }; - if (first_line[8] != ' ') return error.HttpHeadersInvalid; - const status = @intToEnum(http.Status, parseInt3(first_line[9..12].*)); - - var headers: Response.Headers = .{ - .version = version, - .status = status, - }; - - while (it.next()) |line| { - headers.number_of_headers += 1; - - if (line.len == 0) return error.HttpHeadersInvalid; - switch (line[0]) { - ' ', '\t' => return error.HttpHeaderContinuationsUnsupported, - else => {}, - } - var line_it = mem.split(u8, line, ": "); - const header_name = line_it.first(); - const header_value = line_it.rest(); - if (std.ascii.eqlIgnoreCase(header_name, "location")) { - if (headers.location != null) return error.HttpHeadersInvalid; - headers.location = header_value; - } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { - if (headers.content_length != null) return error.HttpHeadersInvalid; - headers.content_length = try std.fmt.parseInt(u64, header_value, 10); - } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) { - if (headers.transfer_encoding != null or headers.transfer_compression != null) return error.HttpHeadersInvalid; - - // Transfer-Encoding: second, first - // Transfer-Encoding: deflate, chunked - var iter = std.mem.splitBackwards(u8, header_value, ","); - - if (iter.next()) |first| { - const kind = std.meta.stringToEnum( - http.TransferEncoding, - std.mem.trim(u8, first, " "), - ) orelse - return error.HttpTransferEncodingUnsupported; - - switch (kind) { - .chunked => headers.transfer_encoding = .chunked, - .compress => headers.transfer_compression = .compress, - .deflate => headers.transfer_compression = .deflate, - .gzip => headers.transfer_compression = .gzip, - } - } - - if (iter.next()) |second| { - if (headers.transfer_compression != null) return error.HttpTransferEncodingUnsupported; - - const kind = std.meta.stringToEnum( - http.TransferEncoding, - std.mem.trim(u8, second, " "), - ) orelse - return error.HttpTransferEncodingUnsupported; - - switch (kind) { - .chunked => return error.HttpHeadersInvalid, // chunked must come last - .compress => return error.HttpTransferEncodingUnsupported, // compress not supported - .deflate => headers.transfer_compression = .deflate, - .gzip => headers.transfer_compression = .gzip, - } - } - - if (iter.next()) |_| return error.HttpTransferEncodingUnsupported; - } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) { - if (headers.transfer_compression != null) return error.HttpHeadersInvalid; - - const kind = std.meta.stringToEnum( - http.TransferEncoding, - std.mem.trim(u8, header_value, " "), - ) orelse - return error.HttpTransferEncodingUnsupported; - - switch (kind) { - .chunked => return error.HttpHeadersInvalid, // not transfer encoding - .compress => return error.HttpTransferEncodingUnsupported, // compress not supported - .deflate => headers.transfer_compression = .deflate, - .gzip => headers.transfer_compression = .gzip, - } - } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { - if (std.ascii.eqlIgnoreCase(header_value, "keep-alive")) { - headers.connection = .keep_alive; - } else if (std.ascii.eqlIgnoreCase(header_value, "close")) { - headers.connection = .close; - } else { - return error.HttpConnectionHeaderUnsupported; - } - } - } - - return headers; - } - - test "parse headers" { - const example = - "HTTP/1.1 301 Moved Permanently\r\n" ++ - "Location: https://www.example.com/\r\n" ++ - "Content-Type: text/html; charset=UTF-8\r\n" ++ - "Content-Length: 220\r\n\r\n"; - const parsed = try Response.Headers.parse(example); - try testing.expectEqual(http.Version.@"HTTP/1.1", parsed.version); - try testing.expectEqual(http.Status.moved_permanently, parsed.status); - try testing.expectEqualStrings("https://www.example.com/", parsed.location orelse - return error.TestFailed); - try testing.expectEqual(@as(?u64, 220), parsed.content_length); - } - - test "header continuation" { - const example = - "HTTP/1.0 200 OK\r\n" ++ - "Content-Type: text/html;\r\n charset=UTF-8\r\n" ++ - "Content-Length: 220\r\n\r\n"; - try testing.expectError( - error.HttpHeaderContinuationsUnsupported, - Response.Headers.parse(example), - ); - } - - test "extra content length" { - const example = - "HTTP/1.0 200 OK\r\n" ++ - "Content-Length: 220\r\n" ++ - "Content-Type: text/html; charset=UTF-8\r\n" ++ - "content-length: 220\r\n\r\n"; - try testing.expectError( - error.HttpHeadersInvalid, - Response.Headers.parse(example), - ); - } - }; - - pub const State = enum { - /// Begin header parsing states. - invalid, - start, - seen_r, - seen_rn, - seen_rnr, - finished, - /// Begin transfer-encoding: chunked parsing states. - chunk_size_prefix_r, - chunk_size_prefix_n, - chunk_size, - chunk_r, - chunk_data, - - pub fn isContent(self: State) bool { - return switch (self) { - .invalid, .start, .seen_r, .seen_rn, .seen_rnr => false, - .finished, .chunk_size_prefix_r, .chunk_size_prefix_n, .chunk_size, .chunk_r, .chunk_data => true, - }; - } - }; - - pub fn initDynamic(max: usize) Response { - return .{ - .state = .start, - .headers = undefined, - .header_bytes = .{}, - .max_header_bytes = max, - .header_bytes_owned = true, - .next_chunk_length = undefined, - }; - } - - pub fn initStatic(buf: []u8) Response { - return .{ - .state = .start, - .headers = undefined, - .header_bytes = .{ .items = buf[0..0], .capacity = buf.len }, - .max_header_bytes = buf.len, - .header_bytes_owned = false, - .next_chunk_length = undefined, - }; - } - - /// Returns how many bytes are part of HTTP headers. Always less than or - /// equal to bytes.len. If the amount returned is less than bytes.len, it - /// means the headers ended and the first byte after the double \r\n\r\n is - /// located at `bytes[result]`. - pub fn findHeadersEnd(r: *Response, bytes: []const u8) usize { - var index: usize = 0; - - // TODO: https://github.com/ziglang/zig/issues/8220 - state: while (true) { - switch (r.state) { - .invalid => unreachable, - .finished => unreachable, - .start => while (true) { - switch (bytes.len - index) { - 0 => return index, - 1 => { - if (bytes[index] == '\r') - r.state = .seen_r; - return index + 1; - }, - 2 => { - if (int16(bytes[index..][0..2]) == int16("\r\n")) { - r.state = .seen_rn; - } else if (bytes[index + 1] == '\r') { - r.state = .seen_r; - } - return index + 2; - }, - 3 => { - if (int16(bytes[index..][0..2]) == int16("\r\n") and - bytes[index + 2] == '\r') - { - r.state = .seen_rnr; - } else if (int16(bytes[index + 1 ..][0..2]) == int16("\r\n")) { - r.state = .seen_rn; - } else if (bytes[index + 2] == '\r') { - r.state = .seen_r; - } - return index + 3; - }, - 4...15 => { - if (int32(bytes[index..][0..4]) == int32("\r\n\r\n")) { - r.state = .finished; - return index + 4; - } else if (int16(bytes[index + 1 ..][0..2]) == int16("\r\n") and - bytes[index + 3] == '\r') - { - r.state = .seen_rnr; - index += 4; - continue :state; - } else if (int16(bytes[index + 2 ..][0..2]) == int16("\r\n")) { - r.state = .seen_rn; - index += 4; - continue :state; - } else if (bytes[index + 3] == '\r') { - r.state = .seen_r; - index += 4; - continue :state; - } - index += 4; - continue; - }, - else => { - const chunk = bytes[index..][0..16]; - const v: @Vector(16, u8) = chunk.*; - const matches_r = v == @splat(16, @as(u8, '\r')); - const iota = std.simd.iota(u8, 16); - const default = @splat(16, @as(u8, 16)); - const sub_index = @reduce(.Min, @select(u8, matches_r, iota, default)); - switch (sub_index) { - 0...12 => { - index += sub_index + 4; - if (int32(chunk[sub_index..][0..4]) == int32("\r\n\r\n")) { - r.state = .finished; - return index; - } - continue; - }, - 13 => { - index += 16; - if (int16(chunk[14..][0..2]) == int16("\n\r")) { - r.state = .seen_rnr; - continue :state; - } - continue; - }, - 14 => { - index += 16; - if (chunk[15] == '\n') { - r.state = .seen_rn; - continue :state; - } - continue; - }, - 15 => { - r.state = .seen_r; - index += 16; - continue :state; - }, - 16 => { - index += 16; - continue; - }, - else => unreachable, - } - }, - } - }, - - .seen_r => switch (bytes.len - index) { - 0 => return index, - 1 => { - switch (bytes[index]) { - '\n' => r.state = .seen_rn, - '\r' => r.state = .seen_r, - else => r.state = .start, - } - return index + 1; - }, - 2 => { - if (int16(bytes[index..][0..2]) == int16("\n\r")) { - r.state = .seen_rnr; - return index + 2; - } - r.state = .start; - return index + 2; - }, - else => { - if (int16(bytes[index..][0..2]) == int16("\n\r") and - bytes[index + 2] == '\n') - { - r.state = .finished; - return index + 3; - } - index += 3; - r.state = .start; - continue :state; - }, - }, - .seen_rn => switch (bytes.len - index) { - 0 => return index, - 1 => { - switch (bytes[index]) { - '\r' => r.state = .seen_rnr, - else => r.state = .start, - } - return index + 1; - }, - else => { - if (int16(bytes[index..][0..2]) == int16("\r\n")) { - r.state = .finished; - return index + 2; - } - index += 2; - r.state = .start; - continue :state; - }, - }, - .seen_rnr => switch (bytes.len - index) { - 0 => return index, - else => { - if (bytes[index] == '\n') { - r.state = .finished; - return index + 1; - } - index += 1; - r.state = .start; - continue :state; - }, - }, - .chunk_size_prefix_r => unreachable, - .chunk_size_prefix_n => unreachable, - .chunk_size => unreachable, - .chunk_r => unreachable, - .chunk_data => unreachable, - } - - return index; - } - } - - pub fn findChunkedLen(r: *Response, bytes: []const u8) usize { - var i: usize = 0; - if (r.state == .chunk_size) { - while (i < bytes.len) : (i += 1) { - const digit = switch (bytes[i]) { - '0'...'9' => |b| b - '0', - 'A'...'Z' => |b| b - 'A' + 10, - 'a'...'z' => |b| b - 'a' + 10, - '\r' => { - r.state = .chunk_r; - i += 1; - break; - }, - else => { - r.state = .invalid; - return i; - }, - }; - const mul = @mulWithOverflow(r.next_chunk_length, 16); - if (mul[1] != 0) { - r.state = .invalid; - return i; - } - const add = @addWithOverflow(mul[0], digit); - if (add[1] != 0) { - r.state = .invalid; - return i; - } - r.next_chunk_length = add[0]; - } else { - return i; - } - } - assert(r.state == .chunk_r); - if (i == bytes.len) return i; - - if (bytes[i] == '\n') { - r.state = .chunk_data; - return i + 1; - } else { - r.state = .invalid; - return i; - } - } - - fn parseInt3(nnn: @Vector(3, u8)) u10 { - const zero: @Vector(3, u8) = .{ '0', '0', '0' }; - const mmm: @Vector(3, u10) = .{ 100, 10, 1 }; - return @reduce(.Add, @as(@Vector(3, u10), nnn -% zero) *% mmm); - } - - test parseInt3 { - const expectEqual = std.testing.expectEqual; - try expectEqual(@as(u10, 0), parseInt3("000".*)); - try expectEqual(@as(u10, 418), parseInt3("418".*)); - try expectEqual(@as(u10, 999), parseInt3("999".*)); - } - - test "find headers end basic" { - var buffer: [1]u8 = undefined; - var r = Response.initStatic(&buffer); - try testing.expectEqual(@as(usize, 10), r.findHeadersEnd("HTTP/1.1 4")); - try testing.expectEqual(@as(usize, 2), r.findHeadersEnd("18")); - try testing.expectEqual(@as(usize, 8), r.findHeadersEnd(" lol\r\n\r\nblah blah")); - } - - test "find headers end vectorized" { - var buffer: [1]u8 = undefined; - var r = Response.initStatic(&buffer); - const example = - "HTTP/1.1 301 Moved Permanently\r\n" ++ - "Location: https://www.example.com/\r\n" ++ - "Content-Type: text/html; charset=UTF-8\r\n" ++ - "Content-Length: 220\r\n" ++ - "\r\ncontent"; - try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example)); - } - - test "find headers end bug" { - var buffer: [1]u8 = undefined; - var r = Response.initStatic(&buffer); - const trail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - const example = - "HTTP/1.1 200 OK\r\n" ++ - "Access-Control-Allow-Origin: https://render.githubusercontent.com\r\n" ++ - "content-disposition: attachment; filename=zig-0.10.0.tar.gz\r\n" ++ - "Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\r\n" ++ - "Content-Type: application/x-gzip\r\n" ++ - "ETag: \"bfae0af6b01c7c0d89eb667cb5f0e65265968aeebda2689177e6b26acd3155ca\"\r\n" ++ - "Strict-Transport-Security: max-age=31536000\r\n" ++ - "Vary: Authorization,Accept-Encoding,Origin\r\n" ++ - "X-Content-Type-Options: nosniff\r\n" ++ - "X-Frame-Options: deny\r\n" ++ - "X-XSS-Protection: 1; mode=block\r\n" ++ - "Date: Fri, 06 Jan 2023 22:26:22 GMT\r\n" ++ - "Transfer-Encoding: chunked\r\n" ++ - "X-GitHub-Request-Id: 89C6:17E9:A7C9E:124B51:63B8A00E\r\n" ++ - "connection: close\r\n\r\n" ++ trail; - try testing.expectEqual(@as(usize, example.len - trail.len), r.findHeadersEnd(example)); - } - }; - - pub const RequestTransfer = union(enum) { - content_length: u64, - chunked: void, - none: void, - }; - - pub const Headers = struct { - version: http.Version = .@"HTTP/1.1", - method: http.Method = .GET, - user_agent: []const u8 = "Zig (std.http)", - connection: http.Connection = .keep_alive, - transfer_encoding: RequestTransfer = .none, - - custom: []const http.CustomHeader = &[_]http.CustomHeader{}, - }; - - pub const Options = struct { - handle_redirects: bool = true, - max_redirects: u32 = 3, - header_strategy: HeaderStrategy = .{ .dynamic = 16 * 1024 }, - - pub const HeaderStrategy = union(enum) { - /// In this case, the client's Allocator will be used to store the - /// entire HTTP header. This value is the maximum total size of - /// HTTP headers allowed, otherwise - /// error.HttpHeadersExceededSizeLimit is returned from read(). - dynamic: usize, - /// This is used to store the entire HTTP header. If the HTTP - /// header is too big to fit, `error.HttpHeadersExceededSizeLimit` - /// is returned from read(). When this is used, `error.OutOfMemory` - /// cannot be returned from `read()`. - static: []u8, - }; - }; - - /// Frees all resources associated with the request. - pub fn deinit(req: *Request) void { - switch (req.response.compression) { - .none => {}, - .deflate => |*deflate| deflate.deinit(), - .gzip => |*gzip| gzip.deinit(), - } - - if (req.response.header_bytes_owned) { - req.response.header_bytes.deinit(req.client.allocator); - } - - if (!req.response.done) { - // If the response wasn't fully read, then we need to close the connection. - req.connection.data.closing = true; - req.client.release(req.connection); - } - - req.arena.deinit(); - req.* = undefined; - } - - const ReadRawError = Connection.ReadError || Uri.ParseError || RequestError || error{ - UnexpectedEndOfStream, - TooManyHttpRedirects, - HttpRedirectMissingLocation, - HttpHeadersInvalid, - }; - - const ReaderRaw = std.io.Reader(*Request, ReadRawError, readRaw); - - /// Read from the underlying stream, without decompressing or parsing the headers. Must be called - /// after waitForCompleteHead() has returned successfully. - pub fn readRaw(req: *Request, buffer: []u8) ReadRawError!usize { - assert(req.response.state.isContent()); - - var index: usize = 0; - while (index == 0) { - const amt = try req.readRawAdvanced(buffer[index..]); - if (amt == 0 and req.response.done) break; - index += amt; - } - - return index; - } - - fn checkForCompleteHead(req: *Request, buffer: []u8) !usize { - switch (req.response.state) { - .invalid => unreachable, - .start, .seen_r, .seen_rn, .seen_rnr => {}, - else => return 0, // No more headers to read. - } - - const i = req.response.findHeadersEnd(buffer[0..]); - if (req.response.state == .invalid) return error.HttpHeadersInvalid; - - const headers_data = buffer[0..i]; - if (req.response.header_bytes.items.len + headers_data.len > req.response.max_header_bytes) { - return error.HttpHeadersExceededSizeLimit; - } - try req.response.header_bytes.appendSlice(req.client.allocator, headers_data); - - if (req.response.state == .finished) { - req.response.headers = try Response.Headers.parse(req.response.header_bytes.items); - - if (req.response.headers.connection == .keep_alive) { - req.connection.data.closing = false; - } else { - req.connection.data.closing = true; - } - - if (req.response.headers.transfer_encoding) |transfer_encoding| { - switch (transfer_encoding) { - .chunked => { - req.response.next_chunk_length = 0; - req.response.state = .chunk_size; - }, - .compress => unreachable, - .deflate => unreachable, - .gzip => unreachable, - } - } else if (req.response.headers.content_length) |content_length| { - req.response.next_chunk_length = content_length; - - if (content_length == 0) req.response.done = true; - } else { - req.response.done = true; - } - - return i; - } - - return 0; - } - - pub const WaitForCompleteHeadError = ReadRawError || error{ - UnexpectedEndOfStream, - - HttpHeadersExceededSizeLimit, - ShortHttpStatusLine, - BadHttpVersion, - HttpHeaderContinuationsUnsupported, - HttpTransferEncodingUnsupported, - HttpConnectionHeaderUnsupported, - }; - - /// Reads a complete response head. Any leftover data is stored in the request. This function is idempotent. - pub fn waitForCompleteHead(req: *Request) WaitForCompleteHeadError!void { - if (req.response.state.isContent()) return; - - while (true) { - const nread = try req.connection.data.read(req.read_buffer[0..]); - const amt = try checkForCompleteHead(req, req.read_buffer[0..nread]); - - if (amt != 0) { - req.read_buffer_start = @intCast(ReadBufferIndex, amt); - req.read_buffer_len = @intCast(ReadBufferIndex, nread); - return; - } else if (nread == 0) { - return error.UnexpectedEndOfStream; - } - } - } - - /// This one can return 0 without meaning EOF. - fn readRawAdvanced(req: *Request, buffer: []u8) !usize { - assert(req.response.state.isContent()); - if (req.response.done) return 0; - - // var in: []const u8 = undefined; - if (req.read_buffer_start == req.read_buffer_len) { - const nread = try req.connection.data.read(req.read_buffer[0..]); - if (nread == 0) return error.UnexpectedEndOfStream; - - req.read_buffer_start = 0; - req.read_buffer_len = @intCast(ReadBufferIndex, nread); - } - - var out_index: usize = 0; - while (true) { - switch (req.response.state) { - .invalid, .start, .seen_r, .seen_rn, .seen_rnr => unreachable, - .finished => { - // TODO https://github.com/ziglang/zig/issues/14039 - const buf_avail = req.read_buffer_len - req.read_buffer_start; - const data_avail = req.response.next_chunk_length; - const out_avail = buffer.len; - - if (req.handle_redirects and req.response.headers.status.class() == .redirect) { - const can_read = @intCast(usize, @min(buf_avail, data_avail)); - req.response.next_chunk_length -= can_read; - - if (req.response.next_chunk_length == 0) { - req.client.release(req.connection); - req.connection = undefined; - req.response.done = true; - } - - return 0; // skip over as much data as possible - } - - const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail)); - req.response.next_chunk_length -= can_read; - - mem.copy(u8, buffer[0..], req.read_buffer[req.read_buffer_start..][0..can_read]); - req.read_buffer_start += @intCast(ReadBufferIndex, can_read); - - if (req.response.next_chunk_length == 0) { - req.client.release(req.connection); - req.connection = undefined; - req.response.done = true; - } - - return can_read; - }, - .chunk_size_prefix_r => switch (req.read_buffer_len - req.read_buffer_start) { - 0 => return out_index, - 1 => switch (req.read_buffer[req.read_buffer_start]) { - '\r' => { - req.response.state = .chunk_size_prefix_n; - return out_index; - }, - else => { - req.response.state = .invalid; - return error.HttpHeadersInvalid; - }, - }, - else => switch (int16(req.read_buffer[req.read_buffer_start..][0..2])) { - int16("\r\n") => { - req.read_buffer_start += 2; - req.response.state = .chunk_size; - continue; - }, - else => { - req.response.state = .invalid; - return error.HttpHeadersInvalid; - }, - }, - }, - .chunk_size_prefix_n => switch (req.read_buffer_len - req.read_buffer_start) { - 0 => return out_index, - else => switch (req.read_buffer[req.read_buffer_start]) { - '\n' => { - req.read_buffer_start += 1; - req.response.state = .chunk_size; - continue; - }, - else => { - req.response.state = .invalid; - return error.HttpHeadersInvalid; - }, - }, - }, - .chunk_size, .chunk_r => { - const i = req.response.findChunkedLen(req.read_buffer[req.read_buffer_start..req.read_buffer_len]); - switch (req.response.state) { - .invalid => return error.HttpHeadersInvalid, - .chunk_data => { - if (req.response.next_chunk_length == 0) { - req.response.done = true; - req.client.release(req.connection); - req.connection = undefined; - - return out_index; - } - - req.read_buffer_start += @intCast(ReadBufferIndex, i); - continue; - }, - .chunk_size => return out_index, - else => unreachable, - } - }, - .chunk_data => { - // TODO https://github.com/ziglang/zig/issues/14039 - const buf_avail = req.read_buffer_len - req.read_buffer_start; - const data_avail = req.response.next_chunk_length; - const out_avail = buffer.len - out_index; - - if (req.handle_redirects and req.response.headers.status.class() == .redirect) { - const can_read = @intCast(usize, @min(buf_avail, data_avail)); - req.response.next_chunk_length -= can_read; - - if (req.response.next_chunk_length == 0) { - req.client.release(req.connection); - req.connection = undefined; - req.response.done = true; - continue; - } - - return 0; // skip over as much data as possible - } - - const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail)); - req.response.next_chunk_length -= can_read; - - mem.copy(u8, buffer[out_index..], req.read_buffer[req.read_buffer_start..][0..can_read]); - req.read_buffer_start += @intCast(ReadBufferIndex, can_read); - out_index += can_read; - - if (req.response.next_chunk_length == 0) { - req.response.state = .chunk_size_prefix_r; - - continue; - } - - return out_index; - }, - } - } - } - - pub const ReadError = DeflateDecompressor.Error || GzipDecompressor.Error || WaitForCompleteHeadError || error{ - BadHeader, - InvalidCompression, - StreamTooLong, - InvalidWindowSize, - }; - - pub const Reader = std.io.Reader(*Request, ReadError, read); - - pub fn reader(req: *Request) Reader { - return .{ .context = req }; - } - - pub fn read(req: *Request, buffer: []u8) ReadError!usize { - while (true) { - if (!req.response.state.isContent()) try req.waitForCompleteHead(); - - if (req.handle_redirects and req.response.headers.status.class() == .redirect) { - assert(try req.readRaw(buffer) == 0); - - if (req.redirects_left == 0) return error.TooManyHttpRedirects; - - const location = req.response.headers.location orelse - return error.HttpRedirectMissingLocation; - const new_url = Uri.parse(location) catch try Uri.parseWithoutScheme(location); - - var new_arena = std.heap.ArenaAllocator.init(req.client.allocator); - const resolved_url = try req.uri.resolve(new_url, false, new_arena.allocator()); - errdefer new_arena.deinit(); - - req.arena.deinit(); - req.arena = new_arena; - - const new_req = try req.client.request(resolved_url, req.headers, .{ - .max_redirects = req.redirects_left - 1, - .header_strategy = if (req.response.header_bytes_owned) .{ - .dynamic = req.response.max_header_bytes, - } else .{ - .static = req.response.header_bytes.unusedCapacitySlice(), - }, - }); - req.deinit(); - req.* = new_req; - } else { - break; - } - } - - if (req.response.compression == .none) { - if (req.response.headers.transfer_compression) |compression| { - switch (compression) { - .compress => unreachable, - .deflate => req.response.compression = .{ - .deflate = try std.compress.zlib.zlibStream(req.client.allocator, ReaderRaw{ .context = req }), - }, - .gzip => req.response.compression = .{ - .gzip = try std.compress.gzip.decompress(req.client.allocator, ReaderRaw{ .context = req }), - }, - .chunked => unreachable, - } - } - } - - return switch (req.response.compression) { - .deflate => |*deflate| try deflate.read(buffer), - .gzip => |*gzip| try gzip.read(buffer), - else => try req.readRaw(buffer), - }; - } - - pub fn readAll(req: *Request, buffer: []u8) !usize { - var index: usize = 0; - while (index < buffer.len) { - const amt = try read(req, buffer[index..]); - if (amt == 0) break; - index += amt; - } - return index; - } - - pub const WriteError = Connection.WriteError || error{MessageTooLong}; - - pub const Writer = std.io.Writer(*Request, WriteError, write); - - pub fn writer(req: *Request) Writer { - return .{ .context = req }; - } - - /// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent. - pub fn write(req: *Request, bytes: []const u8) !usize { - switch (req.headers.transfer_encoding) { - .chunked => { - try req.connection.data.writer().print("{x}\r\n", .{bytes.len}); - try req.connection.data.writeAll(bytes); - try req.connection.data.writeAll("\r\n"); - - return bytes.len; - }, - .content_length => |*len| { - if (len.* < bytes.len) return error.MessageTooLong; - - const amt = try req.connection.data.write(bytes); - len.* -= amt; - return amt; - }, - .none => return error.NotWriteable, - } - } - - /// Finish the body of a request. This notifies the server that you have no more data to send. - pub fn finish(req: *Request) !void { - switch (req.headers.transfer_encoding) { - .chunked => try req.connection.data.writeAll("0\r\n"), - .content_length => |len| if (len != 0) return error.MessageNotCompleted, - .none => {}, - } - } - - inline fn int16(array: *const [2]u8) u16 { - return @bitCast(u16, array.*); - } - - inline fn int32(array: *const [4]u8) u32 { - return @bitCast(u32, array.*); - } - - inline fn int64(array: *const [8]u8) u64 { - return @bitCast(u64, array.*); - } - - test { - const builtin = @import("builtin"); - - if (builtin.os.tag == .wasi) return error.SkipZigTest; - - _ = Response; - } -}; - pub fn deinit(client: *Client) void { client.connection_mutex.lock(); @@ -1231,8 +265,8 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req .handle_redirects = options.handle_redirects, .compression_init = false, .response = switch (options.header_strategy) { - .dynamic => |max| Request.Response.initDynamic(max), - .static => |buf| Request.Response.initStatic(buf), + .dynamic => |max| Response.initDynamic(max), + .static => |buf| Response.initStatic(buf), }, .arena = undefined, }; @@ -1274,7 +308,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req } else { try writer.writeAll("\r\nConnection: keep-alive"); } - try writer.writeAll("\r\nAccept-Encoding: gzip, deflate"); + try writer.writeAll("\r\nAccept-Encoding: gzip, deflate, zstd"); switch (headers.transfer_encoding) { .chunked => try writer.writeAll("\r\nTransfer-Encoding: chunked"), diff --git a/lib/std/http/Client/Request.zig b/lib/std/http/Client/Request.zig new file mode 100644 index 0000000000..26ce5cb7bf --- /dev/null +++ b/lib/std/http/Client/Request.zig @@ -0,0 +1,488 @@ +const std = @import("std"); +const http = std.http; +const Uri = std.Uri; +const mem = std.mem; +const assert = std.debug.assert; + +const Client = @import("../Client.zig"); +const Connection = Client.Connection; +const ConnectionNode = Client.ConnectionNode; +const Response = @import("Response.zig"); + +const Request = @This(); + +const read_buffer_size = 8192; +const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size); + +uri: Uri, +client: *Client, +connection: *ConnectionNode, +response: Response, +/// These are stored in Request so that they are available when following +/// redirects. +headers: Headers, + +redirects_left: u32, +handle_redirects: bool, +compression_init: bool, + +/// Used as a allocator for resolving redirects locations. +arena: std.heap.ArenaAllocator, + +/// Read buffer for the connection. This is used to pull in large amounts of data from the connection even if the user asks for a small amount. This can probably be removed with careful planning. +read_buffer: [read_buffer_size]u8 = undefined, +read_buffer_start: ReadBufferIndex = 0, +read_buffer_len: ReadBufferIndex = 0, + +pub const RequestTransfer = union(enum) { + content_length: u64, + chunked: void, + none: void, +}; + +pub const Headers = struct { + version: http.Version = .@"HTTP/1.1", + method: http.Method = .GET, + user_agent: []const u8 = "zig (std.http)", + connection: http.Connection = .keep_alive, + transfer_encoding: RequestTransfer = .none, + + custom: []const http.CustomHeader = &[_]http.CustomHeader{}, +}; + +pub const Options = struct { + handle_redirects: bool = true, + max_redirects: u32 = 3, + header_strategy: HeaderStrategy = .{ .dynamic = 16 * 1024 }, + + pub const HeaderStrategy = union(enum) { + /// In this case, the client's Allocator will be used to store the + /// entire HTTP header. This value is the maximum total size of + /// HTTP headers allowed, otherwise + /// error.HttpHeadersExceededSizeLimit is returned from read(). + dynamic: usize, + /// This is used to store the entire HTTP header. If the HTTP + /// header is too big to fit, `error.HttpHeadersExceededSizeLimit` + /// is returned from read(). When this is used, `error.OutOfMemory` + /// cannot be returned from `read()`. + static: []u8, + }; +}; + +/// Frees all resources associated with the request. +pub fn deinit(req: *Request) void { + switch (req.response.compression) { + .none => {}, + .deflate => |*deflate| deflate.deinit(), + .gzip => |*gzip| gzip.deinit(), + .zstd => |*zstd| zstd.deinit(), + } + + if (req.response.header_bytes_owned) { + req.response.header_bytes.deinit(req.client.allocator); + } + + if (!req.response.done) { + // If the response wasn't fully read, then we need to close the connection. + req.connection.data.closing = true; + req.client.release(req.connection); + } + + req.arena.deinit(); + req.* = undefined; +} + +pub const ReadRawError = Connection.ReadError || Uri.ParseError || Client.RequestError || error{ + UnexpectedEndOfStream, + TooManyHttpRedirects, + HttpRedirectMissingLocation, + HttpHeadersInvalid, +}; + +pub const ReaderRaw = std.io.Reader(*Request, ReadRawError, readRaw); + +/// Read from the underlying stream, without decompressing or parsing the headers. Must be called +/// after waitForCompleteHead() has returned successfully. +pub fn readRaw(req: *Request, buffer: []u8) ReadRawError!usize { + assert(req.response.state.isContent()); + + var index: usize = 0; + while (index == 0) { + const amt = try req.readRawAdvanced(buffer[index..]); + if (amt == 0 and req.response.done) break; + index += amt; + } + + return index; +} + +fn checkForCompleteHead(req: *Request, buffer: []u8) !usize { + switch (req.response.state) { + .invalid => unreachable, + .start, .seen_r, .seen_rn, .seen_rnr => {}, + else => return 0, // No more headers to read. + } + + const i = req.response.findHeadersEnd(buffer[0..]); + if (req.response.state == .invalid) return error.HttpHeadersInvalid; + + const headers_data = buffer[0..i]; + if (req.response.header_bytes.items.len + headers_data.len > req.response.max_header_bytes) { + return error.HttpHeadersExceededSizeLimit; + } + try req.response.header_bytes.appendSlice(req.client.allocator, headers_data); + + if (req.response.state == .finished) { + req.response.headers = try Response.Headers.parse(req.response.header_bytes.items); + + if (req.response.upgrade) |_| { + req.connection.data.closing = false; + req.response.done = true; + return i; + } + + if (req.response.headers.connection == .keep_alive) { + req.connection.data.closing = false; + } else { + req.connection.data.closing = true; + } + + if (req.response.headers.transfer_encoding) |transfer_encoding| { + switch (transfer_encoding) { + .chunked => { + req.response.next_chunk_length = 0; + req.response.state = .chunk_size; + }, + } + } else if (req.response.headers.content_length) |content_length| { + req.response.next_chunk_length = content_length; + + if (content_length == 0) req.response.done = true; + } else { + req.response.done = true; + } + + return i; + } + + return 0; +} + +pub const WaitForCompleteHeadError = ReadRawError || error{ + UnexpectedEndOfStream, + + HttpHeadersExceededSizeLimit, + ShortHttpStatusLine, + BadHttpVersion, + HttpHeaderContinuationsUnsupported, + HttpTransferEncodingUnsupported, + HttpConnectionHeaderUnsupported, +}; + +/// Reads a complete response head. Any leftover data is stored in the request. This function is idempotent. +pub fn waitForCompleteHead(req: *Request) WaitForCompleteHeadError!void { + if (req.response.state.isContent()) return; + + while (true) { + const nread = try req.connection.data.read(req.read_buffer[0..]); + const amt = try checkForCompleteHead(req, req.read_buffer[0..nread]); + + if (amt != 0) { + req.read_buffer_start = @intCast(ReadBufferIndex, amt); + req.read_buffer_len = @intCast(ReadBufferIndex, nread); + return; + } else if (nread == 0) { + return error.UnexpectedEndOfStream; + } + } +} + +/// This one can return 0 without meaning EOF. +fn readRawAdvanced(req: *Request, buffer: []u8) !usize { + assert(req.response.state.isContent()); + if (req.response.done) return 0; + + // var in: []const u8 = undefined; + if (req.read_buffer_start == req.read_buffer_len) { + const nread = try req.connection.data.read(req.read_buffer[0..]); + if (nread == 0) return error.UnexpectedEndOfStream; + + req.read_buffer_start = 0; + req.read_buffer_len = @intCast(ReadBufferIndex, nread); + } + + var out_index: usize = 0; + while (true) { + switch (req.response.state) { + .invalid, .start, .seen_r, .seen_rn, .seen_rnr => unreachable, + .finished => { + // TODO https://github.com/ziglang/zig/issues/14039 + const buf_avail = req.read_buffer_len - req.read_buffer_start; + const data_avail = req.response.next_chunk_length; + const out_avail = buffer.len; + + if (req.handle_redirects and req.response.headers.status.class() == .redirect) { + const can_read = @intCast(usize, @min(buf_avail, data_avail)); + req.response.next_chunk_length -= can_read; + + if (req.response.next_chunk_length == 0) { + req.client.release(req.connection); + req.connection = undefined; + req.response.done = true; + } + + return 0; // skip over as much data as possible + } + + const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail)); + req.response.next_chunk_length -= can_read; + + mem.copy(u8, buffer[0..], req.read_buffer[req.read_buffer_start..][0..can_read]); + req.read_buffer_start += @intCast(ReadBufferIndex, can_read); + + if (req.response.next_chunk_length == 0) { + req.client.release(req.connection); + req.connection = undefined; + req.response.done = true; + } + + return can_read; + }, + .chunk_size_prefix_r => switch (req.read_buffer_len - req.read_buffer_start) { + 0 => return out_index, + 1 => switch (req.read_buffer[req.read_buffer_start]) { + '\r' => { + req.response.state = .chunk_size_prefix_n; + return out_index; + }, + else => { + req.response.state = .invalid; + return error.HttpHeadersInvalid; + }, + }, + else => switch (int16(req.read_buffer[req.read_buffer_start..][0..2])) { + int16("\r\n") => { + req.read_buffer_start += 2; + req.response.state = .chunk_size; + continue; + }, + else => { + req.response.state = .invalid; + return error.HttpHeadersInvalid; + }, + }, + }, + .chunk_size_prefix_n => switch (req.read_buffer_len - req.read_buffer_start) { + 0 => return out_index, + else => switch (req.read_buffer[req.read_buffer_start]) { + '\n' => { + req.read_buffer_start += 1; + req.response.state = .chunk_size; + continue; + }, + else => { + req.response.state = .invalid; + return error.HttpHeadersInvalid; + }, + }, + }, + .chunk_size, .chunk_r => { + const i = req.response.findChunkedLen(req.read_buffer[req.read_buffer_start..req.read_buffer_len]); + switch (req.response.state) { + .invalid => return error.HttpHeadersInvalid, + .chunk_data => { + if (req.response.next_chunk_length == 0) { + req.response.done = true; + req.client.release(req.connection); + req.connection = undefined; + + return out_index; + } + + req.read_buffer_start += @intCast(ReadBufferIndex, i); + continue; + }, + .chunk_size => return out_index, + else => unreachable, + } + }, + .chunk_data => { + // TODO https://github.com/ziglang/zig/issues/14039 + const buf_avail = req.read_buffer_len - req.read_buffer_start; + const data_avail = req.response.next_chunk_length; + const out_avail = buffer.len - out_index; + + if (req.handle_redirects and req.response.headers.status.class() == .redirect) { + const can_read = @intCast(usize, @min(buf_avail, data_avail)); + req.response.next_chunk_length -= can_read; + + if (req.response.next_chunk_length == 0) { + req.client.release(req.connection); + req.connection = undefined; + req.response.done = true; + continue; + } + + return 0; // skip over as much data as possible + } + + const can_read = @intCast(usize, @min(@min(buf_avail, data_avail), out_avail)); + req.response.next_chunk_length -= can_read; + + mem.copy(u8, buffer[out_index..], req.read_buffer[req.read_buffer_start..][0..can_read]); + req.read_buffer_start += @intCast(ReadBufferIndex, can_read); + out_index += can_read; + + if (req.response.next_chunk_length == 0) { + req.response.state = .chunk_size_prefix_r; + + continue; + } + + return out_index; + }, + } + } +} + +pub const ReadError = Client.DeflateDecompressor.Error || Client.GzipDecompressor.Error || Client.ZstdDecompressor.Error || WaitForCompleteHeadError || error{ + BadHeader, + InvalidCompression, + StreamTooLong, + InvalidWindowSize, + CompressionNotSupported +}; + +pub const Reader = std.io.Reader(*Request, ReadError, read); + +pub fn reader(req: *Request) Reader { + return .{ .context = req }; +} + +pub fn read(req: *Request, buffer: []u8) ReadError!usize { + while (true) { + if (!req.response.state.isContent()) try req.waitForCompleteHead(); + + if (req.handle_redirects and req.response.headers.status.class() == .redirect) { + assert(try req.readRaw(buffer) == 0); + + if (req.redirects_left == 0) return error.TooManyHttpRedirects; + + const location = req.response.headers.location orelse + return error.HttpRedirectMissingLocation; + const new_url = Uri.parse(location) catch try Uri.parseWithoutScheme(location); + + var new_arena = std.heap.ArenaAllocator.init(req.client.allocator); + const resolved_url = try req.uri.resolve(new_url, false, new_arena.allocator()); + errdefer new_arena.deinit(); + + req.arena.deinit(); + req.arena = new_arena; + + const new_req = try req.client.request(resolved_url, req.headers, .{ + .max_redirects = req.redirects_left - 1, + .header_strategy = if (req.response.header_bytes_owned) .{ + .dynamic = req.response.max_header_bytes, + } else .{ + .static = req.response.header_bytes.unusedCapacitySlice(), + }, + }); + req.deinit(); + req.* = new_req; + } else { + break; + } + } + + if (req.response.compression == .none) { + if (req.response.headers.transfer_compression) |compression| { + switch (compression) { + .compress => return error.CompressionNotSupported, + .deflate => req.response.compression = .{ + .deflate = try std.compress.zlib.zlibStream(req.client.allocator, ReaderRaw{ .context = req }), + }, + .gzip => req.response.compression = .{ + .gzip = try std.compress.gzip.decompress(req.client.allocator, ReaderRaw{ .context = req }), + }, + .zstd => req.response.compression = .{ + .zstd = std.compress.zstd.decompressStream(req.client.allocator, ReaderRaw{ .context = req }), + }, + } + } + } + + return switch (req.response.compression) { + .deflate => |*deflate| try deflate.read(buffer), + .gzip => |*gzip| try gzip.read(buffer), + .zstd => |*zstd| try zstd.read(buffer), + else => try req.readRaw(buffer), + }; +} + +pub fn readAll(req: *Request, buffer: []u8) !usize { + var index: usize = 0; + while (index < buffer.len) { + const amt = try read(req, buffer[index..]); + if (amt == 0) break; + index += amt; + } + return index; +} + +pub const WriteError = Connection.WriteError || error{MessageTooLong}; + +pub const Writer = std.io.Writer(*Request, WriteError, write); + +pub fn writer(req: *Request) Writer { + return .{ .context = req }; +} + +/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent. +pub fn write(req: *Request, bytes: []const u8) !usize { + switch (req.headers.transfer_encoding) { + .chunked => { + try req.connection.data.writer().print("{x}\r\n", .{bytes.len}); + try req.connection.data.writeAll(bytes); + try req.connection.data.writeAll("\r\n"); + + return bytes.len; + }, + .content_length => |*len| { + if (len.* < bytes.len) return error.MessageTooLong; + + const amt = try req.connection.data.write(bytes); + len.* -= amt; + return amt; + }, + .none => return error.NotWriteable, + } +} + +/// Finish the body of a request. This notifies the server that you have no more data to send. +pub fn finish(req: *Request) !void { + switch (req.headers.transfer_encoding) { + .chunked => try req.connection.data.writeAll("0\r\n"), + .content_length => |len| if (len != 0) return error.MessageNotCompleted, + .none => {}, + } +} + +inline fn int16(array: *const [2]u8) u16 { + return @bitCast(u16, array.*); +} + +inline fn int32(array: *const [4]u8) u32 { + return @bitCast(u32, array.*); +} + +inline fn int64(array: *const [8]u8) u64 { + return @bitCast(u64, array.*); +} + +test { + const builtin = @import("builtin"); + + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + _ = Response; +} diff --git a/lib/std/http/Client/Response.zig b/lib/std/http/Client/Response.zig new file mode 100644 index 0000000000..bc064f9a20 --- /dev/null +++ b/lib/std/http/Client/Response.zig @@ -0,0 +1,506 @@ +const std = @import("std"); +const http = std.http; +const mem = std.mem; +const testing = std.testing; +const assert = std.debug.assert; + +const Client = @import("../Client.zig"); +const Response = @This(); + +headers: Headers, +state: State, +header_bytes_owned: bool, +/// This could either be a fixed buffer provided by the API user or it +/// could be our own array list. +header_bytes: std.ArrayListUnmanaged(u8), +max_header_bytes: usize, +next_chunk_length: u64, +done: bool = false, + +compression: union(enum) { + deflate: Client.DeflateDecompressor, + gzip: Client.GzipDecompressor, + zstd: Client.ZstdDecompressor, + none: void, +} = .none, + +pub const Headers = struct { + status: http.Status, + version: http.Version, + location: ?[]const u8 = null, + content_length: ?u64 = null, + transfer_encoding: ?http.TransferEncoding = null, + transfer_compression: ?http.ContentEncoding = null, + connection: http.Connection = .close, + + number_of_headers: usize = 0, + + pub fn parse(bytes: []const u8) !Headers { + var it = mem.split(u8, bytes[0 .. bytes.len - 4], "\r\n"); + + const first_line = it.first(); + if (first_line.len < 12) + return error.ShortHttpStatusLine; + + const version: http.Version = switch (int64(first_line[0..8])) { + int64("HTTP/1.0") => .@"HTTP/1.0", + int64("HTTP/1.1") => .@"HTTP/1.1", + else => return error.BadHttpVersion, + }; + if (first_line[8] != ' ') return error.HttpHeadersInvalid; + const status = @intToEnum(http.Status, parseInt3(first_line[9..12].*)); + + var headers: Headers = .{ + .version = version, + .status = status, + }; + + while (it.next()) |line| { + headers.number_of_headers += 1; + + if (line.len == 0) return error.HttpHeadersInvalid; + switch (line[0]) { + ' ', '\t' => return error.HttpHeaderContinuationsUnsupported, + else => {}, + } + var line_it = mem.split(u8, line, ": "); + const header_name = line_it.first(); + const header_value = line_it.rest(); + if (std.ascii.eqlIgnoreCase(header_name, "location")) { + if (headers.location != null) return error.HttpHeadersInvalid; + headers.location = header_value; + } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { + if (headers.content_length != null) return error.HttpHeadersInvalid; + headers.content_length = try std.fmt.parseInt(u64, header_value, 10); + } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) { + if (headers.transfer_encoding != null or headers.transfer_compression != null) return error.HttpHeadersInvalid; + + // Transfer-Encoding: second, first + // Transfer-Encoding: deflate, chunked + var iter = std.mem.splitBackwards(u8, header_value, ","); + + if (iter.next()) |first| { + const trimmed = std.mem.trim(u8, first, " "); + + if (std.meta.stringToEnum(http.TransferEncoding, trimmed)) |te| { + headers.transfer_encoding = te; + } else if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { + headers.transfer_compression = ce; + } else { + return error.HttpTransferEncodingUnsupported; + } + } + + if (iter.next()) |second| { + if (headers.transfer_compression != null) return error.HttpTransferEncodingUnsupported; + + const trimmed = std.mem.trim(u8, second, " "); + + if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { + headers.transfer_compression = ce; + } else { + return error.HttpTransferEncodingUnsupported; + } + } + + if (iter.next()) |_| return error.HttpTransferEncodingUnsupported; + } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) { + if (headers.transfer_compression != null) return error.HttpHeadersInvalid; + + const trimmed = std.mem.trim(u8, header_value, " "); + + if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { + headers.transfer_compression = ce; + } else { + return error.HttpTransferEncodingUnsupported; + } + } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { + if (std.ascii.eqlIgnoreCase(header_value, "keep-alive")) { + headers.connection = .keep_alive; + } else if (std.ascii.eqlIgnoreCase(header_value, "close")) { + headers.connection = .close; + } else { + return error.HttpConnectionHeaderUnsupported; + } + } + } + + return headers; + } + + test "parse headers" { + const example = + "HTTP/1.1 301 Moved Permanently\r\n" ++ + "Location: https://www.example.com/\r\n" ++ + "Content-Type: text/html; charset=UTF-8\r\n" ++ + "Content-Length: 220\r\n\r\n"; + const parsed = try Headers.parse(example); + try testing.expectEqual(http.Version.@"HTTP/1.1", parsed.version); + try testing.expectEqual(http.Status.moved_permanently, parsed.status); + try testing.expectEqualStrings("https://www.example.com/", parsed.location orelse + return error.TestFailed); + try testing.expectEqual(@as(?u64, 220), parsed.content_length); + } + + test "header continuation" { + const example = + "HTTP/1.0 200 OK\r\n" ++ + "Content-Type: text/html;\r\n charset=UTF-8\r\n" ++ + "Content-Length: 220\r\n\r\n"; + try testing.expectError( + error.HttpHeaderContinuationsUnsupported, + Headers.parse(example), + ); + } + + test "extra content length" { + const example = + "HTTP/1.0 200 OK\r\n" ++ + "Content-Length: 220\r\n" ++ + "Content-Type: text/html; charset=UTF-8\r\n" ++ + "content-length: 220\r\n\r\n"; + try testing.expectError( + error.HttpHeadersInvalid, + Headers.parse(example), + ); + } +}; + +inline fn int16(array: *const [2]u8) u16 { + return @bitCast(u16, array.*); +} + +inline fn int32(array: *const [4]u8) u32 { + return @bitCast(u32, array.*); +} + +inline fn int64(array: *const [8]u8) u64 { + return @bitCast(u64, array.*); +} + +pub const State = enum { + /// Begin header parsing states. + invalid, + start, + seen_r, + seen_rn, + seen_rnr, + finished, + /// Begin transfer-encoding: chunked parsing states. + chunk_size_prefix_r, + chunk_size_prefix_n, + chunk_size, + chunk_r, + chunk_data, + + pub fn isContent(self: State) bool { + return switch (self) { + .invalid, .start, .seen_r, .seen_rn, .seen_rnr => false, + .finished, .chunk_size_prefix_r, .chunk_size_prefix_n, .chunk_size, .chunk_r, .chunk_data => true, + }; + } +}; + +pub fn initDynamic(max: usize) Response { + return .{ + .state = .start, + .headers = undefined, + .header_bytes = .{}, + .max_header_bytes = max, + .header_bytes_owned = true, + .next_chunk_length = undefined, + }; +} + +pub fn initStatic(buf: []u8) Response { + return .{ + .state = .start, + .headers = undefined, + .header_bytes = .{ .items = buf[0..0], .capacity = buf.len }, + .max_header_bytes = buf.len, + .header_bytes_owned = false, + .next_chunk_length = undefined, + }; +} + +/// Returns how many bytes are part of HTTP headers. Always less than or +/// equal to bytes.len. If the amount returned is less than bytes.len, it +/// means the headers ended and the first byte after the double \r\n\r\n is +/// located at `bytes[result]`. +pub fn findHeadersEnd(r: *Response, bytes: []const u8) usize { + var index: usize = 0; + + // TODO: https://github.com/ziglang/zig/issues/8220 + state: while (true) { + switch (r.state) { + .invalid => unreachable, + .finished => unreachable, + .start => while (true) { + switch (bytes.len - index) { + 0 => return index, + 1 => { + if (bytes[index] == '\r') + r.state = .seen_r; + return index + 1; + }, + 2 => { + if (int16(bytes[index..][0..2]) == int16("\r\n")) { + r.state = .seen_rn; + } else if (bytes[index + 1] == '\r') { + r.state = .seen_r; + } + return index + 2; + }, + 3 => { + if (int16(bytes[index..][0..2]) == int16("\r\n") and + bytes[index + 2] == '\r') + { + r.state = .seen_rnr; + } else if (int16(bytes[index + 1 ..][0..2]) == int16("\r\n")) { + r.state = .seen_rn; + } else if (bytes[index + 2] == '\r') { + r.state = .seen_r; + } + return index + 3; + }, + 4...15 => { + if (int32(bytes[index..][0..4]) == int32("\r\n\r\n")) { + r.state = .finished; + return index + 4; + } else if (int16(bytes[index + 1 ..][0..2]) == int16("\r\n") and + bytes[index + 3] == '\r') + { + r.state = .seen_rnr; + index += 4; + continue :state; + } else if (int16(bytes[index + 2 ..][0..2]) == int16("\r\n")) { + r.state = .seen_rn; + index += 4; + continue :state; + } else if (bytes[index + 3] == '\r') { + r.state = .seen_r; + index += 4; + continue :state; + } + index += 4; + continue; + }, + else => { + const chunk = bytes[index..][0..16]; + const v: @Vector(16, u8) = chunk.*; + const matches_r = v == @splat(16, @as(u8, '\r')); + const iota = std.simd.iota(u8, 16); + const default = @splat(16, @as(u8, 16)); + const sub_index = @reduce(.Min, @select(u8, matches_r, iota, default)); + switch (sub_index) { + 0...12 => { + index += sub_index + 4; + if (int32(chunk[sub_index..][0..4]) == int32("\r\n\r\n")) { + r.state = .finished; + return index; + } + continue; + }, + 13 => { + index += 16; + if (int16(chunk[14..][0..2]) == int16("\n\r")) { + r.state = .seen_rnr; + continue :state; + } + continue; + }, + 14 => { + index += 16; + if (chunk[15] == '\n') { + r.state = .seen_rn; + continue :state; + } + continue; + }, + 15 => { + r.state = .seen_r; + index += 16; + continue :state; + }, + 16 => { + index += 16; + continue; + }, + else => unreachable, + } + }, + } + }, + + .seen_r => switch (bytes.len - index) { + 0 => return index, + 1 => { + switch (bytes[index]) { + '\n' => r.state = .seen_rn, + '\r' => r.state = .seen_r, + else => r.state = .start, + } + return index + 1; + }, + 2 => { + if (int16(bytes[index..][0..2]) == int16("\n\r")) { + r.state = .seen_rnr; + return index + 2; + } + r.state = .start; + return index + 2; + }, + else => { + if (int16(bytes[index..][0..2]) == int16("\n\r") and + bytes[index + 2] == '\n') + { + r.state = .finished; + return index + 3; + } + index += 3; + r.state = .start; + continue :state; + }, + }, + .seen_rn => switch (bytes.len - index) { + 0 => return index, + 1 => { + switch (bytes[index]) { + '\r' => r.state = .seen_rnr, + else => r.state = .start, + } + return index + 1; + }, + else => { + if (int16(bytes[index..][0..2]) == int16("\r\n")) { + r.state = .finished; + return index + 2; + } + index += 2; + r.state = .start; + continue :state; + }, + }, + .seen_rnr => switch (bytes.len - index) { + 0 => return index, + else => { + if (bytes[index] == '\n') { + r.state = .finished; + return index + 1; + } + index += 1; + r.state = .start; + continue :state; + }, + }, + .chunk_size_prefix_r => unreachable, + .chunk_size_prefix_n => unreachable, + .chunk_size => unreachable, + .chunk_r => unreachable, + .chunk_data => unreachable, + } + + return index; + } +} + +pub fn findChunkedLen(r: *Response, bytes: []const u8) usize { + var i: usize = 0; + if (r.state == .chunk_size) { + while (i < bytes.len) : (i += 1) { + const digit = switch (bytes[i]) { + '0'...'9' => |b| b - '0', + 'A'...'Z' => |b| b - 'A' + 10, + 'a'...'z' => |b| b - 'a' + 10, + '\r' => { + r.state = .chunk_r; + i += 1; + break; + }, + else => { + r.state = .invalid; + return i; + }, + }; + const mul = @mulWithOverflow(r.next_chunk_length, 16); + if (mul[1] != 0) { + r.state = .invalid; + return i; + } + const add = @addWithOverflow(mul[0], digit); + if (add[1] != 0) { + r.state = .invalid; + return i; + } + r.next_chunk_length = add[0]; + } else { + return i; + } + } + assert(r.state == .chunk_r); + if (i == bytes.len) return i; + + if (bytes[i] == '\n') { + r.state = .chunk_data; + return i + 1; + } else { + r.state = .invalid; + return i; + } +} + +fn parseInt3(nnn: @Vector(3, u8)) u10 { + const zero: @Vector(3, u8) = .{ '0', '0', '0' }; + const mmm: @Vector(3, u10) = .{ 100, 10, 1 }; + return @reduce(.Add, @as(@Vector(3, u10), nnn -% zero) *% mmm); +} + +test parseInt3 { + const expectEqual = std.testing.expectEqual; + try expectEqual(@as(u10, 0), parseInt3("000".*)); + try expectEqual(@as(u10, 418), parseInt3("418".*)); + try expectEqual(@as(u10, 999), parseInt3("999".*)); +} + +test "find headers end basic" { + var buffer: [1]u8 = undefined; + var r = Response.initStatic(&buffer); + try testing.expectEqual(@as(usize, 10), r.findHeadersEnd("HTTP/1.1 4")); + try testing.expectEqual(@as(usize, 2), r.findHeadersEnd("18")); + try testing.expectEqual(@as(usize, 8), r.findHeadersEnd(" lol\r\n\r\nblah blah")); +} + +test "find headers end vectorized" { + var buffer: [1]u8 = undefined; + var r = Response.initStatic(&buffer); + const example = + "HTTP/1.1 301 Moved Permanently\r\n" ++ + "Location: https://www.example.com/\r\n" ++ + "Content-Type: text/html; charset=UTF-8\r\n" ++ + "Content-Length: 220\r\n" ++ + "\r\ncontent"; + try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example)); +} + +test "find headers end bug" { + var buffer: [1]u8 = undefined; + var r = Response.initStatic(&buffer); + const trail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + const example = + "HTTP/1.1 200 OK\r\n" ++ + "Access-Control-Allow-Origin: https://render.githubusercontent.com\r\n" ++ + "content-disposition: attachment; filename=zig-0.10.0.tar.gz\r\n" ++ + "Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\r\n" ++ + "Content-Type: application/x-gzip\r\n" ++ + "ETag: \"bfae0af6b01c7c0d89eb667cb5f0e65265968aeebda2689177e6b26acd3155ca\"\r\n" ++ + "Strict-Transport-Security: max-age=31536000\r\n" ++ + "Vary: Authorization,Accept-Encoding,Origin\r\n" ++ + "X-Content-Type-Options: nosniff\r\n" ++ + "X-Frame-Options: deny\r\n" ++ + "X-XSS-Protection: 1; mode=block\r\n" ++ + "Date: Fri, 06 Jan 2023 22:26:22 GMT\r\n" ++ + "Transfer-Encoding: chunked\r\n" ++ + "X-GitHub-Request-Id: 89C6:17E9:A7C9E:124B51:63B8A00E\r\n" ++ + "connection: close\r\n\r\n" ++ trail; + try testing.expectEqual(@as(usize, example.len - trail.len), r.findHeadersEnd(example)); +} From 524e0cd987a52a60ce1014aa27cd73f99a3b9958 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 8 Mar 2023 11:27:13 -0600 Subject: [PATCH 070/294] std.http: rework connection pool into its own type --- lib/std/http/Client.zig | 189 +++++++++++++++++++------------ lib/std/http/Client/Request.zig | 22 ++-- lib/std/http/Client/Response.zig | 5 +- lib/std/std.zig | 5 + 4 files changed, 134 insertions(+), 87 deletions(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index d44b1d098d..baf0239388 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -16,6 +16,9 @@ const testing = std.testing; pub const Request = @import("Client/Request.zig"); pub const Response = @import("Client/Response.zig"); +pub const default_connection_pool_size = 32; +const connection_pool_size = std.options.http_connection_pool_size; + /// Used for tcpConnectToHost and storing HTTP headers when an externally /// managed buffer is not provided. allocator: Allocator, @@ -24,39 +27,115 @@ ca_bundle: std.crypto.Certificate.Bundle = .{}, /// it will first rescan the system for root certificates. next_https_rescan_certs: bool = true, -connection_mutex: std.Thread.Mutex = .{}, connection_pool: ConnectionPool = .{}, -connection_used: ConnectionPool = .{}, -pub const ConnectionPool = std.TailQueue(Connection); -pub const ConnectionNode = ConnectionPool.Node; +pub const ConnectionPool = struct { + pub const Criteria = struct { + host: []const u8, + port: u16, + is_tls: bool, + }; -/// Acquires an existing connection from the connection pool. This function is threadsafe. -/// If the caller already holds the connection mutex, it should pass `true` for `held`. -pub fn acquire(client: *Client, node: *ConnectionNode, held: bool) void { - if (!held) client.connection_mutex.lock(); - defer if (!held) client.connection_mutex.unlock(); + const Queue = std.TailQueue(Connection); + pub const Node = Queue.Node; - client.connection_pool.remove(node); - client.connection_used.append(node); -} + mutex: std.Thread.Mutex = .{}, + used: Queue = .{}, + free: Queue = .{}, + free_len: usize = 0, + free_size: usize = default_connection_pool_size, -/// Tries to release a connection back to the connection pool. This function is threadsafe. -/// If the connection is marked as closing, it will be closed instead. -pub fn release(client: *Client, node: *ConnectionNode) void { - client.connection_mutex.lock(); - defer client.connection_mutex.unlock(); + /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe. + /// If no connection is found, null is returned. + pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Node { + pool.mutex.lock(); + defer pool.mutex.unlock(); - client.connection_used.remove(node); + var next = pool.free.last; + while (next) |node| : (next = node.prev) { + if ((node.data.protocol == .tls) != criteria.is_tls) continue; + if (node.data.port != criteria.port) continue; + if (std.mem.eql(u8, node.data.host, criteria.host)) continue; - if (node.data.closing) { - node.data.close(client); + pool.acquireUnsafe(node); + return node; + } - return client.allocator.destroy(node); + return null; } - client.connection_pool.append(node); -} + /// Acquires an existing connection from the connection pool. This function is not threadsafe. + pub fn acquireUnsafe(pool: *ConnectionPool, node: *Node) void { + pool.free.remove(node); + pool.free_len -= 1; + + pool.used.append(node); + } + + /// Acquires an existing connection from the connection pool. This function is threadsafe. + pub fn acquire(pool: *ConnectionPool, node: *Node) void { + pool.mutex.lock(); + defer pool.mutex.unlock(); + + return pool.acquireUnsafe(node); + } + + /// Tries to release a connection back to the connection pool. This function is threadsafe. + /// If the connection is marked as closing, it will be closed instead. + pub fn release(pool: *ConnectionPool, client: *Client, node: *Node) void { + pool.mutex.lock(); + defer pool.mutex.unlock(); + + pool.used.remove(node); + + if (node.data.closing) { + node.data.close(client); + + return client.allocator.destroy(node); + } + + if (pool.free_len + 1 >= pool.free_size) { + const popped = pool.free.popFirst() orelse unreachable; + + popped.data.close(client); + + return client.allocator.destroy(popped); + } + + pool.free.append(node); + pool.free_len += 1; + } + + /// Adds a newly created node to the pool of used connections. This function is threadsafe. + pub fn addUsed(pool: *ConnectionPool, node: *Node) void { + pool.mutex.lock(); + defer pool.mutex.unlock(); + + pool.used.append(node); + } + + pub fn deinit(pool: *ConnectionPool, client: *Client) void { + pool.mutex.lock(); + + var next = pool.free.first; + while (next) |node| { + defer client.allocator.destroy(node); + next = node.next; + + node.data.close(client); + } + + next = pool.used.first; + while (next) |node| { + defer client.allocator.destroy(node); + next = node.next; + + node.data.close(client); + } + + pool.* = undefined; + } +}; pub const DeflateDecompressor = std.compress.zlib.ZlibStream(Request.ReaderRaw); pub const GzipDecompressor = std.compress.gzip.Decompress(Request.ReaderRaw); @@ -142,25 +221,7 @@ pub const Connection = struct { }; pub fn deinit(client: *Client) void { - client.connection_mutex.lock(); - - var next = client.connection_pool.first; - while (next) |node| { - next = node.next; - - node.data.close(client); - - client.allocator.destroy(node); - } - - next = client.connection_used.first; - while (next) |node| { - next = node.next; - - node.data.close(client); - - client.allocator.destroy(node); - } + client.connection_pool.deinit(client); client.ca_bundle.deinit(client.allocator); client.* = undefined; @@ -168,36 +229,25 @@ pub fn deinit(client: *Client) void { pub const ConnectError = std.mem.Allocator.Error || net.TcpConnectToHostError || std.crypto.tls.Client.InitError(net.Stream); -pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionNode { - { // Search through the connection pool for a potential connection. - client.connection_mutex.lock(); - defer client.connection_mutex.unlock(); +pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionPool.Node { + if (client.connection_pool.findConnection(.{ + .host = host, + .port = port, + .is_tls = protocol == .tls, + })) |node| + return node; - var potential = client.connection_pool.last; - while (potential) |node| { - const same_host = mem.eql(u8, node.data.host, host); - const same_port = node.data.port == port; - const same_protocol = node.data.protocol == protocol; - - if (same_host and same_port and same_protocol) { - client.acquire(node, true); - return node; - } - - potential = node.prev; - } - } - - const conn = try client.allocator.create(ConnectionNode); + const conn = try client.allocator.create(ConnectionPool.Node); errdefer client.allocator.destroy(conn); + conn.* = .{ .data = undefined }; - conn.* = .{ .data = .{ + conn.data = .{ .stream = try net.tcpConnectToHost(client.allocator, host, port), .tls_client = undefined, .protocol = protocol, .host = try client.allocator.dupe(u8, host), .port = port, - } }; + }; switch (protocol) { .plain => {}, @@ -210,12 +260,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio }, } - { - client.connection_mutex.lock(); - defer client.connection_mutex.unlock(); - - client.connection_used.append(conn); - } + client.connection_pool.addUsed(conn); return conn; } @@ -247,8 +292,8 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req const host = uri.host orelse return error.UriMissingHost; if (client.next_https_rescan_certs and protocol == .tls) { - client.connection_mutex.lock(); // TODO: this could be so much better than reusing the connection pool mutex. - defer client.connection_mutex.unlock(); + client.connection_pool.mutex.lock(); // TODO: this could be so much better than reusing the connection pool mutex. + defer client.connection_pool.mutex.unlock(); if (client.next_https_rescan_certs) { try client.ca_bundle.rescan(client.allocator); diff --git a/lib/std/http/Client/Request.zig b/lib/std/http/Client/Request.zig index 26ce5cb7bf..9e2ebd2d6c 100644 --- a/lib/std/http/Client/Request.zig +++ b/lib/std/http/Client/Request.zig @@ -6,7 +6,7 @@ const assert = std.debug.assert; const Client = @import("../Client.zig"); const Connection = Client.Connection; -const ConnectionNode = Client.ConnectionNode; +const ConnectionNode = Client.ConnectionPool.Node; const Response = @import("Response.zig"); const Request = @This(); @@ -85,7 +85,7 @@ pub fn deinit(req: *Request) void { if (!req.response.done) { // If the response wasn't fully read, then we need to close the connection. req.connection.data.closing = true; - req.client.release(req.connection); + req.client.connection_pool.release(req.client, req.connection); } req.arena.deinit(); @@ -135,7 +135,7 @@ fn checkForCompleteHead(req: *Request, buffer: []u8) !usize { if (req.response.state == .finished) { req.response.headers = try Response.Headers.parse(req.response.header_bytes.items); - if (req.response.upgrade) |_| { + if (req.response.headers.upgrade) |_| { req.connection.data.closing = false; req.response.done = true; return i; @@ -226,7 +226,7 @@ fn readRawAdvanced(req: *Request, buffer: []u8) !usize { req.response.next_chunk_length -= can_read; if (req.response.next_chunk_length == 0) { - req.client.release(req.connection); + req.client.connection_pool.release(req.client, req.connection); req.connection = undefined; req.response.done = true; } @@ -241,7 +241,7 @@ fn readRawAdvanced(req: *Request, buffer: []u8) !usize { req.read_buffer_start += @intCast(ReadBufferIndex, can_read); if (req.response.next_chunk_length == 0) { - req.client.release(req.connection); + req.client.connection_pool.release(req.client, req.connection); req.connection = undefined; req.response.done = true; } @@ -293,7 +293,7 @@ fn readRawAdvanced(req: *Request, buffer: []u8) !usize { .chunk_data => { if (req.response.next_chunk_length == 0) { req.response.done = true; - req.client.release(req.connection); + req.client.connection_pool.release(req.client, req.connection); req.connection = undefined; return out_index; @@ -317,7 +317,7 @@ fn readRawAdvanced(req: *Request, buffer: []u8) !usize { req.response.next_chunk_length -= can_read; if (req.response.next_chunk_length == 0) { - req.client.release(req.connection); + req.client.connection_pool.release(req.client, req.connection); req.connection = undefined; req.response.done = true; continue; @@ -345,13 +345,7 @@ fn readRawAdvanced(req: *Request, buffer: []u8) !usize { } } -pub const ReadError = Client.DeflateDecompressor.Error || Client.GzipDecompressor.Error || Client.ZstdDecompressor.Error || WaitForCompleteHeadError || error{ - BadHeader, - InvalidCompression, - StreamTooLong, - InvalidWindowSize, - CompressionNotSupported -}; +pub const ReadError = Client.DeflateDecompressor.Error || Client.GzipDecompressor.Error || Client.ZstdDecompressor.Error || WaitForCompleteHeadError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize, CompressionNotSupported }; pub const Reader = std.io.Reader(*Request, ReadError, read); diff --git a/lib/std/http/Client/Response.zig b/lib/std/http/Client/Response.zig index bc064f9a20..8b2a9a4918 100644 --- a/lib/std/http/Client/Response.zig +++ b/lib/std/http/Client/Response.zig @@ -32,6 +32,7 @@ pub const Headers = struct { transfer_encoding: ?http.TransferEncoding = null, transfer_compression: ?http.ContentEncoding = null, connection: http.Connection = .close, + upgrade: ?[]const u8 = null, number_of_headers: usize = 0, @@ -93,7 +94,7 @@ pub const Headers = struct { if (iter.next()) |second| { if (headers.transfer_compression != null) return error.HttpTransferEncodingUnsupported; - + const trimmed = std.mem.trim(u8, second, " "); if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { @@ -122,6 +123,8 @@ pub const Headers = struct { } else { return error.HttpConnectionHeaderUnsupported; } + } else if (std.ascii.eqlIgnoreCase(header_name, "upgrade")) { + headers.upgrade = header_value; } } diff --git a/lib/std/std.zig b/lib/std/std.zig index c1c682e224..e888ade659 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -185,6 +185,11 @@ pub const options = struct { options_override.keep_sigpipe else false; + + pub const http_connection_pool_size = if (@hasDecl(options_override, "http_connection_pool_size")) + options_override.http_connection_pool_size + else + http.Client.default_connection_pool_size; }; // This forces the start.zig file to be imported, and the comptime logic inside that From 14590e956e06903ac408af57874d3b5a0d697670 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 9 Mar 2023 15:35:54 +0000 Subject: [PATCH 071/294] Fix test case added in 6d7fb8f --- test/cases/compile_errors/comptime_try_non_error.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/cases/compile_errors/comptime_try_non_error.zig b/test/cases/compile_errors/comptime_try_non_error.zig index 9b342f7934..935148414c 100644 --- a/test/cases/compile_errors/comptime_try_non_error.zig +++ b/test/cases/compile_errors/comptime_try_non_error.zig @@ -1,4 +1,8 @@ -export fn foo() void { +comptime { + foo(); +} + +fn foo() void { try bar(); } @@ -10,4 +14,5 @@ pub fn bar() u8 { // backend=stage2 // target=native // -// :2:12: error: expected error union type, found 'u8' +// :6:12: error: expected error union type, found 'u8' +// :2:8: note: called from here From b445bbfea205702770e4e9de4e456c3e8750f8ed Mon Sep 17 00:00:00 2001 From: antlilja Date: Thu, 9 Mar 2023 20:39:15 +0100 Subject: [PATCH 072/294] Add ability to import dependencies from build.zig --- src/Package.zig | 7 +++++-- src/main.zig | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Package.zig b/src/Package.zig index ed93500980..c238d3d567 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -215,6 +215,7 @@ pub const build_zig_basename = "build.zig"; pub fn fetchAndAddDependencies( pkg: *Package, + root_pkg: *Package, arena: Allocator, thread_pool: *ThreadPool, http_client: *std.http.Client, @@ -295,7 +296,8 @@ pub fn fetchAndAddDependencies( all_modules, ); - try pkg.fetchAndAddDependencies( + try sub_pkg.fetchAndAddDependencies( + root_pkg, arena, thread_pool, http_client, @@ -309,7 +311,8 @@ pub fn fetchAndAddDependencies( all_modules, ); - try add(pkg, gpa, fqn, sub_pkg); + try pkg.add(gpa, name, sub_pkg); + try root_pkg.add(gpa, fqn, sub_pkg); try dependencies_source.writer().print(" pub const {s} = @import(\"{}\");\n", .{ std.zig.fmtId(fqn), std.zig.fmtEscapes(fqn), diff --git a/src/main.zig b/src/main.zig index b134b7183e..95cfca1463 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4228,6 +4228,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi .root_src_path = "build_runner.zig", }; + var build_pkg: Package = .{ + .root_src_directory = build_directory, + .root_src_path = build_zig_basename, + }; if (!build_options.omit_pkg_fetching_code) { var http_client: std.http.Client = .{ .allocator = gpa }; defer http_client.deinit(); @@ -4249,7 +4253,8 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi // Here we borrow main package's table and will replace it with a fresh // one after this process completes. - main_pkg.fetchAndAddDependencies( + build_pkg.fetchAndAddDependencies( + &main_pkg, arena, &thread_pool, &http_client, @@ -4280,11 +4285,6 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi mem.swap(Package.Table, &main_pkg.table, &deps_pkg.table); try main_pkg.add(gpa, "@dependencies", deps_pkg); } - - var build_pkg: Package = .{ - .root_src_directory = build_directory, - .root_src_path = build_zig_basename, - }; try main_pkg.add(gpa, "@build", &build_pkg); const comp = Compilation.create(gpa, .{ From 0ee9a52507fe30983f7933cb19a5bfde3b40a60c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 10 Mar 2023 06:20:10 +0100 Subject: [PATCH 073/294] wasm-linker: remove synthetic segments & atoms --- src/link/Wasm.zig | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 9a4166b052..4292e00ed9 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -887,32 +887,12 @@ fn resolveLazySymbols(wasm: *Wasm) !void { const loc = try wasm.createSyntheticSymbol("__heap_base", .data); try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc); _ = wasm.resolved_symbols.swapRemove(loc); // we don't want to emit this symbol, only use it for relocations. - - // TODO: Can we use `createAtom` here while also re-using the symbol - // from `createSyntheticSymbol`. - const atom_index = @intCast(Atom.Index, wasm.managed_atoms.items.len); - const atom = try wasm.managed_atoms.addOne(wasm.base.allocator); - atom.* = Atom.empty; - atom.sym_index = loc.index; - atom.alignment = 1; - - try wasm.parseAtom(atom_index, .{ .data = .synthetic }); - try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index); } if (wasm.undefs.fetchSwapRemove("__heap_end")) |kv| { const loc = try wasm.createSyntheticSymbol("__heap_end", .data); try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc); _ = wasm.resolved_symbols.swapRemove(loc); - - const atom_index = @intCast(Atom.Index, wasm.managed_atoms.items.len); - const atom = try wasm.managed_atoms.addOne(wasm.base.allocator); - atom.* = Atom.empty; - atom.sym_index = loc.index; - atom.alignment = 1; - - try wasm.parseAtom(atom_index, .{ .data = .synthetic }); - try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index); } } @@ -1614,7 +1594,6 @@ const Kind = union(enum) { read_only, uninitialized, initialized, - synthetic, }, function: void, @@ -1625,7 +1604,6 @@ const Kind = union(enum) { .read_only => return ".rodata.", .uninitialized => return ".bss.", .initialized => return ".data.", - .synthetic => return ".synthetic", } } }; @@ -1833,7 +1811,6 @@ fn sortDataSegments(wasm: *Wasm) !void { if (mem.startsWith(u8, name, ".rodata")) return 0; if (mem.startsWith(u8, name, ".data")) return 1; if (mem.startsWith(u8, name, ".text")) return 2; - if (mem.startsWith(u8, name, ".synthetic")) return 100; // always at end return 3; } }; @@ -2245,10 +2222,6 @@ fn setupMemory(wasm: *Wasm) !void { var offset: u32 = @intCast(u32, memory_ptr); var data_seg_it = wasm.data_segments.iterator(); while (data_seg_it.next()) |entry| { - if (mem.eql(u8, entry.key_ptr.*, ".synthetic")) { - // do not update synthetic segments as they are not part of the output - continue; - } const segment = &wasm.segments.items[entry.value_ptr.*]; memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment); memory_ptr += segment.size; @@ -3447,8 +3420,6 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem // bss section is not emitted when this condition holds true, so we also // do not output a name for it. if (!wasm.base.options.import_memory and std.mem.eql(u8, key, ".bss")) continue; - // Synthetic segments are not emitted - if (std.mem.eql(u8, key, ".synthetic")) continue; segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key }); data_segment_index += 1; } From 023753b4693317055e8094059f6195d041311b5d Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 10 Mar 2023 00:42:56 +0000 Subject: [PATCH 074/294] Sema: correctly detect use of undefined within slices in @Type Resolves: #14712 --- src/Sema.zig | 2 +- src/value.zig | 31 +++++++++++++++---- .../reify_type_with_undefined.zig | 13 ++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 840e8a4c6c..41685890f7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -18373,7 +18373,7 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in const union_val = val.cast(Value.Payload.Union).?.data; const target = mod.getTarget(); const tag_index = type_info_ty.unionTagFieldIndex(union_val.tag, mod).?; - if (union_val.val.anyUndef()) return sema.failWithUseOfUndef(block, src); + if (union_val.val.anyUndef(mod)) return sema.failWithUseOfUndef(block, src); switch (@intToEnum(std.builtin.TypeId, tag_index)) { .Type => return Air.Inst.Ref.type_type, .Void => return Air.Inst.Ref.void_type, diff --git a/src/value.zig b/src/value.zig index 00bf59ca38..b6d27d620d 100644 --- a/src/value.zig +++ b/src/value.zig @@ -3144,13 +3144,32 @@ pub const Value = extern union { /// TODO: check for cases such as array that is not marked undef but all the element /// values are marked undef, or struct that is not marked undef but all fields are marked /// undef, etc. - pub fn anyUndef(self: Value) bool { - if (self.castTag(.aggregate)) |aggregate| { - for (aggregate.data) |val| { - if (val.anyUndef()) return true; - } + pub fn anyUndef(self: Value, mod: *Module) bool { + switch (self.tag()) { + .slice => { + const payload = self.castTag(.slice).?; + const len = payload.data.len.toUnsignedInt(mod.getTarget()); + + var elem_value_buf: ElemValueBuffer = undefined; + var i: usize = 0; + while (i < len) : (i += 1) { + const elem_val = payload.data.ptr.elemValueBuffer(mod, i, &elem_value_buf); + if (elem_val.anyUndef(mod)) return true; + } + }, + + .aggregate => { + const payload = self.castTag(.aggregate).?; + for (payload.data) |val| { + if (val.anyUndef(mod)) return true; + } + }, + + .undef => return true, + else => {}, } - return self.isUndef(); + + return false; } /// Asserts the value is not undefined and not unreachable. diff --git a/test/cases/compile_errors/reify_type_with_undefined.zig b/test/cases/compile_errors/reify_type_with_undefined.zig index e5753fa420..59c0314773 100644 --- a/test/cases/compile_errors/reify_type_with_undefined.zig +++ b/test/cases/compile_errors/reify_type_with_undefined.zig @@ -11,6 +11,18 @@ comptime { }, }); } +comptime { + const std = @import("std"); + const fields: [1]std.builtin.Type.StructField = undefined; + _ = @Type(.{ + .Struct = .{ + .layout = .Auto, + .fields = &fields, + .decls = &.{}, + .is_tuple = false, + }, + }); +} // error // backend=stage2 @@ -18,3 +30,4 @@ comptime { // // :2:9: error: use of undefined value here causes undefined behavior // :5:9: error: use of undefined value here causes undefined behavior +// :17:9: error: use of undefined value here causes undefined behavior From 5a26d1b4268b2e4598e0c39d3703d184921cfa6d Mon Sep 17 00:00:00 2001 From: Lavt Niveau Date: Fri, 10 Mar 2023 06:36:43 -0700 Subject: [PATCH 075/294] Include `signal.h` to define SIGTRAP in Stage 1 compiler (#14867) copy lib/zig.h to stage1/zig.h In this case, it looks safe to backport over with no changes. Co-authored-by: Andrew Kelley --- stage1/zig.h | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/stage1/zig.h b/stage1/zig.h index 65fb21f99a..59c3ddd695 100644 --- a/stage1/zig.h +++ b/stage1/zig.h @@ -190,10 +190,17 @@ typedef char bool; #if zig_has_builtin(trap) #define zig_trap() __builtin_trap() +#elif _MSC_VER && (_M_IX86 || _M_X64) +#define zig_trap() __ud2() +#elif _MSC_VER +#define zig_trap() __fastfail(0) #elif defined(__i386__) || defined(__x86_64__) #define zig_trap() __asm__ volatile("ud2"); +#elif defined(__arm__) || defined(__aarch64__) +#define zig_breakpoint() __asm__ volatile("udf #0"); #else -#define zig_trap() raise(SIGTRAP) +#include +#define zig_trap() abort() #endif #if zig_has_builtin(debugtrap) @@ -202,8 +209,17 @@ typedef char bool; #define zig_breakpoint() __debugbreak() #elif defined(__i386__) || defined(__x86_64__) #define zig_breakpoint() __asm__ volatile("int $0x03"); +#elif defined(__arm__) +#define zig_breakpoint() __asm__ volatile("bkpt #0"); +#elif defined(__aarch64__) +#define zig_breakpoint() __asm__ volatile("brk #0"); #else +#include +#if defined(SIGTRAP) #define zig_breakpoint() raise(SIGTRAP) +#else +#define zig_breakpoint() zig_breakpoint_unavailable +#endif #endif #if zig_has_builtin(return_address) || defined(zig_gnuc) @@ -2384,6 +2400,44 @@ static inline void zig_subw_big(void *res, const void *lhs, const void *rhs, boo (void)zig_subo_big(res, lhs, rhs, is_signed, bits); } +zig_extern void __udivei4(uint32_t *res, const uint32_t *lhs, const uint32_t *rhs, uintptr_t bits); +static inline void zig_div_trunc_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + __udivei4(res, lhs, rhs, bits); + return; + } + + zig_trap(); +} + +static inline void zig_div_floor_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + zig_div_trunc_big(res, lhs, rhs, is_signed, bits); + return; + } + + zig_trap(); +} + +zig_extern void __umodei4(uint32_t *res, const uint32_t *lhs, const uint32_t *rhs, uintptr_t bits); +static inline void zig_rem_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + __umodei4(res, lhs, rhs, bits); + return; + } + + zig_trap(); +} + +static inline void zig_mod_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) { + if (!is_signed) { + zig_rem_big(res, lhs, rhs, is_signed, bits); + return; + } + + zig_trap(); +} + static inline uint16_t zig_clz_big(const void *val, bool is_signed, uint16_t bits) { const uint8_t *val_bytes = val; uint16_t byte_offset = 0; From 3169f0529b22fca2a387a841a62fec29b3d2096a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 19:51:58 -0700 Subject: [PATCH 076/294] eliminate posix_spawn from the standard library Today I found out that posix_spawn is trash. It's actually implemented on top of fork/exec inside of libc (or libSystem in the case of macOS). So, anything posix_spawn can do, we can do better. In particular, what we can do better is handle spawning of child processes that are potentially foreign binaries. If you try to spawn a wasm binary, for example, posix spawn does the following: * Goes ahead and creates a child process. * The child process writes "foo.wasm: foo.wasm: cannot execute binary file" to stderr (yes, it prints the filename twice). * The child process then exits with code 126. This behavior is indistinguishable from the binary being successfully spawned, and then printing to stderr, and exiting with a failure - something that is an extremely common occurrence. Meanwhile, using the lower level fork/exec will simply return ENOEXEC code from the execve syscall (which is mapped to zig error.InvalidExe). The posix_spawn behavior means the zig build runner can't tell the difference between a failure to run a foreign binary, and a binary that did run, but failed in some other fashion. This is unacceptable, because attempting to excecve is the proper way to support things like Rosetta. --- CMakeLists.txt | 1 - lib/std/Build/CompileStep.zig | 2 - lib/std/child_process.zig | 127 +-------------- lib/std/os.zig | 5 +- lib/std/os/posix_spawn.zig | 290 ---------------------------------- 5 files changed, 3 insertions(+), 422 deletions(-) delete mode 100644 lib/std/os/posix_spawn.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 501d12889f..46d9c701b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,7 +303,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/io_uring.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig" - "${CMAKE_SOURCE_DIR}/lib/std/os/posix_spawn.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/windows.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index ea2320cc89..49d2fae68d 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -742,7 +742,6 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u error.ExecNotSupported => return error.PkgConfigFailed, error.ExitCodeFailure => return error.PkgConfigFailed, error.FileNotFound => return error.PkgConfigNotInstalled, - error.ChildExecFailed => return error.PkgConfigFailed, else => return err, }; @@ -2042,7 +2041,6 @@ fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg { error.FileNotFound => error.PkgConfigNotInstalled, error.InvalidName => error.PkgConfigNotInstalled, error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, - error.ChildExecFailed => error.PkgConfigFailed, else => return err, }; self.pkg_config_pkg_list = result; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index dba92ab998..3bdef3177a 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -90,8 +90,7 @@ pub const ChildProcess = struct { os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError || - windows.WaitForSingleObjectError || - os.posix_spawn.Error; + windows.WaitForSingleObjectError; pub const Term = union(enum) { Exited: u8, @@ -143,10 +142,6 @@ pub const ChildProcess = struct { @compileError("the target operating system cannot spawn processes"); } - if (comptime builtin.target.isDarwin()) { - return self.spawnMacos(); - } - if (builtin.os.tag == .windows) { return self.spawnWindows(); } else { @@ -337,10 +332,7 @@ pub const ChildProcess = struct { } fn waitUnwrapped(self: *ChildProcess) !void { - const res: os.WaitPidResult = if (comptime builtin.target.isDarwin()) - try os.posix_spawn.waitpid(self.id, 0) - else - os.waitpid(self.id, 0); + const res: os.WaitPidResult = os.waitpid(self.id, 0); const status = res.status; self.cleanupStreams(); self.handleWaitResult(status); @@ -416,121 +408,6 @@ pub const ChildProcess = struct { Term{ .Unknown = status }; } - fn spawnMacos(self: *ChildProcess) SpawnError!void { - const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; - const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; - errdefer if (self.stdin_behavior == StdIo.Pipe) destroyPipe(stdin_pipe); - - const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; - errdefer if (self.stdout_behavior == StdIo.Pipe) destroyPipe(stdout_pipe); - - const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; - errdefer if (self.stderr_behavior == StdIo.Pipe) destroyPipe(stderr_pipe); - - const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); - const dev_null_fd = if (any_ignore) - os.openZ("/dev/null", os.O.RDWR, 0) catch |err| switch (err) { - error.PathAlreadyExists => unreachable, - error.NoSpaceLeft => unreachable, - error.FileTooBig => unreachable, - error.DeviceBusy => unreachable, - error.FileLocksNotSupported => unreachable, - error.BadPathName => unreachable, // Windows-only - error.InvalidHandle => unreachable, // WASI-only - error.WouldBlock => unreachable, - else => |e| return e, - } - else - undefined; - defer if (any_ignore) os.close(dev_null_fd); - - var attr = try os.posix_spawn.Attr.init(); - defer attr.deinit(); - var flags: u16 = os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK; - if (self.disable_aslr) { - flags |= os.darwin._POSIX_SPAWN_DISABLE_ASLR; - } - if (self.start_suspended) { - flags |= os.darwin.POSIX_SPAWN_START_SUSPENDED; - } - try attr.set(flags); - - var actions = try os.posix_spawn.Actions.init(); - defer actions.deinit(); - - try setUpChildIoPosixSpawn(self.stdin_behavior, &actions, stdin_pipe, os.STDIN_FILENO, dev_null_fd); - try setUpChildIoPosixSpawn(self.stdout_behavior, &actions, stdout_pipe, os.STDOUT_FILENO, dev_null_fd); - try setUpChildIoPosixSpawn(self.stderr_behavior, &actions, stderr_pipe, os.STDERR_FILENO, dev_null_fd); - - if (self.cwd_dir) |cwd| { - try actions.fchdir(cwd.fd); - } else if (self.cwd) |cwd| { - try actions.chdir(cwd); - } - - var arena_allocator = std.heap.ArenaAllocator.init(self.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const argv_buf = try arena.allocSentinel(?[*:0]u8, self.argv.len, null); - for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; - - const envp = if (self.env_map) |env_map| m: { - const envp_buf = try createNullDelimitedEnvMap(arena, env_map); - break :m envp_buf.ptr; - } else std.c.environ; - - const pid = try os.posix_spawn.spawnp(self.argv[0], actions, attr, argv_buf, envp); - - if (self.stdin_behavior == StdIo.Pipe) { - self.stdin = File{ .handle = stdin_pipe[1] }; - } else { - self.stdin = null; - } - if (self.stdout_behavior == StdIo.Pipe) { - self.stdout = File{ .handle = stdout_pipe[0] }; - } else { - self.stdout = null; - } - if (self.stderr_behavior == StdIo.Pipe) { - self.stderr = File{ .handle = stderr_pipe[0] }; - } else { - self.stderr = null; - } - - self.id = pid; - self.term = null; - - if (self.stdin_behavior == StdIo.Pipe) { - os.close(stdin_pipe[0]); - } - if (self.stdout_behavior == StdIo.Pipe) { - os.close(stdout_pipe[1]); - } - if (self.stderr_behavior == StdIo.Pipe) { - os.close(stderr_pipe[1]); - } - } - - fn setUpChildIoPosixSpawn( - stdio: StdIo, - actions: *os.posix_spawn.Actions, - pipe_fd: [2]i32, - std_fileno: i32, - dev_null_fd: i32, - ) !void { - switch (stdio) { - .Pipe => { - const idx: usize = if (std_fileno == 0) 0 else 1; - try actions.dup2(pipe_fd[idx], std_fileno); - try actions.close(pipe_fd[1 - idx]); - }, - .Close => try actions.close(std_fileno), - .Inherit => {}, - .Ignore => try actions.dup2(dev_null_fd, std_fileno), - } - } - fn spawnPosix(self: *ChildProcess) SpawnError!void { const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; diff --git a/lib/std/os.zig b/lib/std/os.zig index 6c680e9a38..069a910408 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -41,7 +41,6 @@ pub const plan9 = @import("os/plan9.zig"); pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); pub const windows = @import("os/windows.zig"); -pub const posix_spawn = @import("os/posix_spawn.zig"); pub const ptrace = @import("os/ptrace.zig"); comptime { @@ -56,7 +55,6 @@ test { } _ = wasi; _ = windows; - _ = posix_spawn; _ = @import("os/test.zig"); } @@ -3998,8 +3996,7 @@ pub const WaitPidResult = struct { }; /// Use this version of the `waitpid` wrapper if you spawned your child process using explicit -/// `fork` and `execve` method. If you spawned your child process using `posix_spawn` method, -/// use `std.os.posix_spawn.waitpid` instead. +/// `fork` and `execve` method. pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { const Status = if (builtin.link_libc) c_int else u32; var status: Status = undefined; diff --git a/lib/std/os/posix_spawn.zig b/lib/std/os/posix_spawn.zig deleted file mode 100644 index 32904a9423..0000000000 --- a/lib/std/os/posix_spawn.zig +++ /dev/null @@ -1,290 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const os = @import("../os.zig"); -const system = os.system; -const errno = system.getErrno; -const fd_t = system.fd_t; -const mode_t = system.mode_t; -const pid_t = system.pid_t; -const unexpectedErrno = os.unexpectedErrno; -const UnexpectedError = os.UnexpectedError; -const toPosixPath = os.toPosixPath; -const WaitPidResult = os.WaitPidResult; - -pub usingnamespace posix_spawn; - -pub const Error = error{ - SystemResources, - InvalidFileDescriptor, - NameTooLong, - TooBig, - PermissionDenied, - InputOutput, - FileSystem, - FileNotFound, - InvalidExe, - NotDir, - FileBusy, - - /// Returned when the child fails to execute either in the pre-exec() initialization step, or - /// when exec(3) is invoked. - ChildExecFailed, -} || UnexpectedError; - -const posix_spawn = if (builtin.target.isDarwin()) struct { - pub const Attr = struct { - attr: system.posix_spawnattr_t, - - pub fn init() Error!Attr { - var attr: system.posix_spawnattr_t = undefined; - switch (errno(system.posix_spawnattr_init(&attr))) { - .SUCCESS => return Attr{ .attr = attr }, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn deinit(self: *Attr) void { - defer self.* = undefined; - switch (errno(system.posix_spawnattr_destroy(&self.attr))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } - } - - pub fn get(self: Attr) Error!u16 { - var flags: c_short = undefined; - switch (errno(system.posix_spawnattr_getflags(&self.attr, &flags))) { - .SUCCESS => return @bitCast(u16, flags), - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn set(self: *Attr, flags: u16) Error!void { - switch (errno(system.posix_spawnattr_setflags(&self.attr, @bitCast(c_short, flags)))) { - .SUCCESS => return, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - }; - - pub const Actions = struct { - actions: system.posix_spawn_file_actions_t, - - pub fn init() Error!Actions { - var actions: system.posix_spawn_file_actions_t = undefined; - switch (errno(system.posix_spawn_file_actions_init(&actions))) { - .SUCCESS => return Actions{ .actions = actions }, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn deinit(self: *Actions) void { - defer self.* = undefined; - switch (errno(system.posix_spawn_file_actions_destroy(&self.actions))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } - } - - pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) Error!void { - const posix_path = try toPosixPath(path); - return self.openZ(fd, &posix_path, flags, mode); - } - - pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) Error!void { - switch (errno(system.posix_spawn_file_actions_addopen(&self.actions, fd, path, @bitCast(c_int, flags), mode))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => unreachable, // the value of file actions is invalid - else => |err| return unexpectedErrno(err), - } - } - - pub fn close(self: *Actions, fd: fd_t) Error!void { - switch (errno(system.posix_spawn_file_actions_addclose(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) Error!void { - switch (errno(system.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn inherit(self: *Actions, fd: fd_t) Error!void { - switch (errno(system.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn chdir(self: *Actions, path: []const u8) Error!void { - const posix_path = try toPosixPath(path); - return self.chdirZ(&posix_path); - } - - pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void { - switch (errno(system.posix_spawn_file_actions_addchdir_np(&self.actions, path))) { - .SUCCESS => return, - .NOMEM => return error.SystemResources, - .NAMETOOLONG => return error.NameTooLong, - .BADF => unreachable, - .INVAL => unreachable, // the value of file actions is invalid - else => |err| return unexpectedErrno(err), - } - } - - pub fn fchdir(self: *Actions, fd: fd_t) Error!void { - switch (errno(system.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - }; - - pub fn spawn( - path: []const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, - ) Error!pid_t { - const posix_path = try toPosixPath(path); - return spawnZ(&posix_path, actions, attr, argv, envp); - } - - pub fn spawnZ( - path: [*:0]const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, - ) Error!pid_t { - var pid: pid_t = undefined; - switch (errno(system.posix_spawn( - &pid, - path, - if (actions) |a| &a.actions else null, - if (attr) |a| &a.attr else null, - argv, - envp, - ))) { - .SUCCESS => return pid, - .@"2BIG" => return error.TooBig, - .NOMEM => return error.SystemResources, - .BADF => return error.InvalidFileDescriptor, - .ACCES => return error.PermissionDenied, - .IO => return error.InputOutput, - .LOOP => return error.FileSystem, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOEXEC => return error.InvalidExe, - .NOTDIR => return error.NotDir, - .TXTBSY => return error.FileBusy, - .BADARCH => return error.InvalidExe, - .BADEXEC => return error.InvalidExe, - .FAULT => unreachable, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn spawnp( - file: []const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, - ) Error!pid_t { - const posix_file = try toPosixPath(file); - return spawnpZ(&posix_file, actions, attr, argv, envp); - } - - pub fn spawnpZ( - file: [*:0]const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]?[*:0]const u8, - envp: [*:null]?[*:0]const u8, - ) Error!pid_t { - var pid: pid_t = undefined; - switch (errno(system.posix_spawnp( - &pid, - file, - if (actions) |a| &a.actions else null, - if (attr) |a| &a.attr else null, - argv, - envp, - ))) { - .SUCCESS => return pid, - .@"2BIG" => return error.TooBig, - .NOMEM => return error.SystemResources, - .BADF => return error.InvalidFileDescriptor, - .ACCES => return error.PermissionDenied, - .IO => return error.InputOutput, - .LOOP => return error.FileSystem, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOEXEC => return error.InvalidExe, - .NOTDIR => return error.NotDir, - .TXTBSY => return error.FileBusy, - .BADARCH => return error.InvalidExe, - .BADEXEC => return error.InvalidExe, - .FAULT => unreachable, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - /// Use this version of the `waitpid` wrapper if you spawned your child process using `posix_spawn` - /// or `posix_spawnp` syscalls. - /// See also `std.os.waitpid` for an alternative if your child process was spawned via `fork` and - /// `execve` method. - pub fn waitpid(pid: pid_t, flags: u32) Error!WaitPidResult { - const Status = if (builtin.link_libc) c_int else u32; - var status: Status = undefined; - while (true) { - const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags); - switch (errno(rc)) { - .SUCCESS => return WaitPidResult{ - .pid = @intCast(pid_t, rc), - .status = @bitCast(u32, status), - }, - .INTR => continue, - .CHILD => return error.ChildExecFailed, - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } - } -} else struct {}; From 4ea2f441df36cec61e1017f4d795d4037326c98c Mon Sep 17 00:00:00 2001 From: Andrius Bentkus Date: Sat, 16 Jul 2022 15:46:13 +0300 Subject: [PATCH 077/294] Module: retry ZIR cache file creation There are no dir components, so you would think that this was unreachable, however we have observed on macOS two processes racing to do openat() with O_CREAT manifest in ENOENT. closes #12138 --- src/Module.zig | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index f3f1aa44e2..8c52176edd 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3569,20 +3569,25 @@ pub fn astGenFile(mod: *Module, file: *File) !void { // If another process is already working on this file, we will get the cached // version. Likewise if we're working on AstGen and another process asks for // the cached file, they'll get it. - const cache_file = zir_dir.createFile(&digest, .{ - .read = true, - .truncate = false, - .lock = lock, - }) catch |err| switch (err) { - error.NotDir => unreachable, // no dir components - error.InvalidUtf8 => unreachable, // it's a hex encoded name - error.BadPathName => unreachable, // it's a hex encoded name - error.NameTooLong => unreachable, // it's a fixed size name - error.PipeBusy => unreachable, // it's not a pipe - error.WouldBlock => unreachable, // not asking for non-blocking I/O - error.FileNotFound => unreachable, // no dir components + const cache_file = while (true) { + break zir_dir.createFile(&digest, .{ + .read = true, + .truncate = false, + .lock = lock, + }) catch |err| switch (err) { + error.NotDir => unreachable, // no dir components + error.InvalidUtf8 => unreachable, // it's a hex encoded name + error.BadPathName => unreachable, // it's a hex encoded name + error.NameTooLong => unreachable, // it's a fixed size name + error.PipeBusy => unreachable, // it's not a pipe + error.WouldBlock => unreachable, // not asking for non-blocking I/O + // There are no dir components, so you would think that this was + // unreachable, however we have observed on macOS two processes racing + // to do openat() with O_CREAT manifest in ENOENT. + error.FileNotFound => continue, - else => |e| return e, // Retryable errors are handled at callsite. + else => |e| return e, // Retryable errors are handled at callsite. + }; }; defer cache_file.close(); From 817fb263b533f0a24476cabe43a6ee5826113d8d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 5 Mar 2023 17:40:53 +0100 Subject: [PATCH 078/294] x86_64: downstream table-driven instruction encoder --- src/arch/x86_64/CodeGen.zig | 175 +-- src/arch/x86_64/Emit.zig | 2639 +++++---------------------------- src/arch/x86_64/Encoding.zig | 521 +++++++ src/arch/x86_64/Mir.zig | 48 +- src/arch/x86_64/bits.zig | 1165 ++++----------- src/arch/x86_64/encoder.zig | 794 ++++++++++ src/arch/x86_64/encodings.zig | 542 +++++++ 7 files changed, 2622 insertions(+), 3262 deletions(-) create mode 100644 src/arch/x86_64/Encoding.zig create mode 100644 src/arch/x86_64/encoder.zig create mode 100644 src/arch/x86_64/encodings.zig diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f8f6a773fa..c108ad6f32 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -303,7 +303,12 @@ pub fn generate( var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, error.OutOfRegisters => return Result{ - .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), + .fail = try ErrorMsg.create( + bin_file.allocator, + src_loc, + "CodeGen ran out of registers. This is a bug in the Zig compiler.", + .{}, + ), }, else => |e| return e, }; @@ -342,6 +347,20 @@ pub fn generate( defer emit.deinit(); emit.lowerMir() catch |err| switch (err) { error.EmitFail => return Result{ .fail = emit.err_msg.? }, + error.InvalidInstruction, error.CannotEncode => |e| { + const msg = switch (e) { + error.InvalidInstruction => "CodeGen failed to find a viable instruction.", + error.CannotEncode => "CodeGen failed to encode the instruction.", + }; + return Result{ + .fail = try ErrorMsg.create( + bin_file.allocator, + src_loc, + "{s} This is a bug in the Zig compiler.", + .{msg}, + ), + }; + }, else => |e| return e, }; @@ -1687,7 +1706,7 @@ fn genIntMulDivOpMir( else => unreachable, }, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, else => unreachable, @@ -2191,7 +2210,7 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue { .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -@intCast(i32, off)) }, + .data = .{ .disp = -@intCast(i32, off) }, }); }, else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}), @@ -2275,7 +2294,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg1 = addr_reg.to64(), .reg2 = .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .stack_offset => |off| { @@ -2286,7 +2305,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg1 = addr_reg.to64(), .reg2 = .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .memory, .linker_load => { @@ -2352,7 +2371,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg2 = dst_mcv.register, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) }; } @@ -2615,7 +2634,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .reg2 = reg, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); }, .stack_offset => |off| { @@ -2842,7 +2861,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .reg2 = addr_reg.to64(), .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); const new_ptr = MCValue{ .register = addr_reg.to64() }; @@ -2903,7 +2922,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .reg2 = tmp_reg, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } @@ -3542,25 +3561,13 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu if (intrinsicsAllowed(self.target.*, dst_ty)) { const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { .f32 => switch (mir_tag) { - .add => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.add_f32_avx - else - Mir.Inst.Tag.add_f32_sse, - .cmp => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.cmp_f32_avx - else - Mir.Inst.Tag.cmp_f32_sse, + .add => Mir.Inst.Tag.add_f32, + .cmp => Mir.Inst.Tag.cmp_f32, else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), }, .f64 => switch (mir_tag) { - .add => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.add_f64_avx - else - Mir.Inst.Tag.add_f64_sse, - .cmp => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.cmp_f64_avx - else - Mir.Inst.Tag.cmp_f64_sse, + .add => Mir.Inst.Tag.add_f64, + .cmp => Mir.Inst.Tag.cmp_f64, else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), }, else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), @@ -3618,7 +3625,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, } @@ -3644,7 +3651,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .reg2 = registerAlias(src_reg, abi_size), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .immediate => |imm| { @@ -3665,7 +3672,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu else => unreachable, }; const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -off), + .dest_off = -off, .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ @@ -3756,7 +3763,7 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .memory => { @@ -5360,7 +5367,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ @@ -5400,14 +5407,8 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -5421,7 +5422,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .reg2 = reg.to128(), .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); return; } @@ -5436,7 +5437,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .reg2 = registerAlias(reg, @intCast(u32, abi_size)), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); }, } @@ -5516,7 +5517,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl 0 => { assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -5530,7 +5531,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl }, 1, 2, 4 => { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -5552,7 +5553,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl // insted just use two 32 bit writes to avoid register allocation { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset + 4), + .dest_off = -stack_offset + 4, .operand = @truncate(u32, x_big >> 32), }); _ = try self.addInst(.{ @@ -5566,7 +5567,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl } { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -5595,14 +5596,8 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -5616,7 +5611,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl .reg2 = reg.to128(), .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); return; } @@ -5691,7 +5686,7 @@ fn genInlineMemcpyRegisterRegister( .reg2 = registerAlias(tmp_reg, nearest_power_of_two), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -next_offset) }, + .data = .{ .disp = -next_offset }, }); if (nearest_power_of_two > 1) { @@ -5711,7 +5706,7 @@ fn genInlineMemcpyRegisterRegister( .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -offset) }, + .data = .{ .disp = -offset }, }); } } @@ -5758,7 +5753,7 @@ fn genInlineMemcpy( .reg1 = dst_addr_reg.to64(), .reg2 = opts.dest_stack_base orelse .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .register => |reg| { @@ -5787,7 +5782,7 @@ fn genInlineMemcpy( .reg1 = src_addr_reg.to64(), .reg2 = opts.source_stack_base orelse .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .register => |reg| { @@ -5911,7 +5906,7 @@ fn genInlineMemset( .reg1 = addr_reg.to64(), .reg2 = opts.dest_stack_base orelse .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .register => |reg| { @@ -5998,7 +5993,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg1 = registerAlias(reg, abi_size), .reg2 = .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .unreach, .none => return, // Nothing to do. @@ -6097,14 +6092,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -6141,14 +6130,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), }; @@ -6162,7 +6145,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, }, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); return; } @@ -6178,7 +6161,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = reg.to64(), .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); }, } @@ -6190,14 +6173,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), }; @@ -6211,7 +6188,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, }, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); return; } @@ -6255,7 +6232,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = reg.to64(), .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); } } @@ -6283,7 +6260,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = .rbp, .flags = flags, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); return; } @@ -6302,7 +6279,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = .rbp, .flags = flags, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); return; } @@ -6311,14 +6288,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -6331,7 +6302,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, }, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); return; } @@ -6347,7 +6318,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, } @@ -6436,7 +6407,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), }, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); // convert @@ -6452,7 +6423,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), }, }), - .data = .{ .imm = @bitCast(u32, -stack_dst.stack_offset) }, + .data = .{ .disp = -stack_dst.stack_offset }, }); return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none }); @@ -6551,7 +6522,7 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { .reg2 = reg, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); break :blk MCValue{ .register = reg }; }, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e521de4bd4..1c540adc9d 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1,3 +1,4 @@ +//! //! This file contains the functionality for lowering x86_64 MIR into //! machine code @@ -7,6 +8,7 @@ const std = @import("std"); const assert = std.debug.assert; const bits = @import("bits.zig"); const abi = @import("abi.zig"); +const encoder = @import("encoder.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.codegen); const math = std.math; @@ -19,12 +21,13 @@ const CodeGen = @import("CodeGen.zig"); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const Encoder = bits.Encoder; const ErrorMsg = Module.ErrorMsg; +const Instruction = encoder.Instruction; const MCValue = @import("CodeGen.zig").MCValue; +const Memory = bits.Memory; const Mir = @import("Mir.zig"); const Module = @import("../../Module.zig"); -const Instruction = bits.Instruction; -const Type = @import("../../type.zig").Type; const Register = bits.Register; +const Type = @import("../../type.zig").Type; mir: Mir, bin_file: *link.File, @@ -45,6 +48,8 @@ relocs: std.ArrayListUnmanaged(Reloc) = .{}, const InnerError = error{ OutOfMemory, EmitFail, + InvalidInstruction, + CannotEncode, }; const Reloc = struct { @@ -153,8 +158,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push => try emit.mirPushPop(.push, inst), .pop => try emit.mirPushPop(.pop, inst), - .jmp => try emit.mirJmpCall(.jmp_near, inst), - .call => try emit.mirJmpCall(.call_near, inst), + .jmp => try emit.mirJmpCall(.jmp, inst), + .call => try emit.mirJmpCall(.call, inst), .cond_jmp => try emit.mirCondJmp(inst), .cond_set_byte => try emit.mirCondSetByte(inst), @@ -170,25 +175,15 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .interrupt => try emit.mirInterrupt(inst), .nop => {}, // just skip it - // SSE instructions - .mov_f64_sse => try emit.mirMovFloatSse(.movsd, inst), - .mov_f32_sse => try emit.mirMovFloatSse(.movss, inst), + // SSE/AVX instructions + .mov_f64 => try emit.mirMovFloat(.movsd, inst), + .mov_f32 => try emit.mirMovFloat(.movss, inst), - .add_f64_sse => try emit.mirAddFloatSse(.addsd, inst), - .add_f32_sse => try emit.mirAddFloatSse(.addss, inst), + .add_f64 => try emit.mirAddFloat(.addsd, inst), + .add_f32 => try emit.mirAddFloat(.addss, inst), - .cmp_f64_sse => try emit.mirCmpFloatSse(.ucomisd, inst), - .cmp_f32_sse => try emit.mirCmpFloatSse(.ucomiss, inst), - - // AVX instructions - .mov_f64_avx => try emit.mirMovFloatAvx(.vmovsd, inst), - .mov_f32_avx => try emit.mirMovFloatAvx(.vmovss, inst), - - .add_f64_avx => try emit.mirAddFloatAvx(.vaddsd, inst), - .add_f32_avx => try emit.mirAddFloatAvx(.vaddss, inst), - - .cmp_f64_avx => try emit.mirCmpFloatAvx(.vucomisd, inst), - .cmp_f32_avx => try emit.mirCmpFloatAvx(.vucomiss, inst), + .cmp_f64 => try emit.mirCmpFloat(.ucomisd, inst), + .cmp_f32 => try emit.mirCmpFloat(.ucomiss, inst), // Pseudo-instructions .call_extern => try emit.mirCallExtern(inst), @@ -235,8 +230,23 @@ fn fixupRelocs(emit: *Emit) InnerError!void { } } +fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) InnerError!void { + const inst = try Instruction.new(mnemonic, .{ + .op1 = ops.op1, + .op2 = ops.op2, + .op3 = ops.op3, + .op4 = ops.op4, + }); + return inst.encode(emit.code.writer()); +} + fn mirUndefinedInstruction(emit: *Emit) InnerError!void { - return lowerToZoEnc(.ud2, emit.code); + return emit.encode(.ud2, .{}); } fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -244,45 +254,43 @@ fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .interrupt); const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { - 0b00 => return lowerToZoEnc(.int3, emit.code), + 0b00 => return emit.encode(.int3, .{}), else => return emit.fail("TODO handle variant 0b{b} of interrupt instruction", .{ops.flags}), } } fn mirSyscall(emit: *Emit) InnerError!void { - return lowerToZoEnc(.syscall, emit.code); + return emit.encode(.syscall, .{}); } -fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - // PUSH/POP reg - return lowerToOEnc(tag, ops.reg1, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + }); }, 0b01 => { - // PUSH/POP r/m64 - const imm = emit.mir.instructions.items(.data)[inst].imm; - const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { - 16 => .word_ptr, - else => .qword_ptr, - }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm, - .base = ops.reg1, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = disp, + }) }, + }); }, 0b10 => { - // PUSH imm32 - assert(tag == .push); const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.push, imm, emit.code); + return emit.encode(.push, .{ + .op1 = .{ .imm = imm }, + }); }, 0b11 => unreachable, } } -fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; @@ -291,15 +299,20 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerErro const callee_preserved_regs = abi.getCalleePreservedRegs(emit.target.*); for (callee_preserved_regs) |reg| { if (reg_list.isSet(callee_preserved_regs, reg)) { - switch (tag) { - .push => try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, disp), - .base = ops.reg1, - }), reg, emit.code), - .pop => try lowerToRmEnc(.mov, reg, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, disp), - .base = ops.reg1, - }), emit.code), + const op1: Instruction.Operand = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = disp, + }) }; + const op2: Instruction.Operand = .{ .reg = reg }; + switch (mnemonic) { + .push => try emit.encode(.mov, .{ + .op1 = op1, + .op2 = op2, + }), + .pop => try emit.encode(.mov, .{ + .op1 = op2, + .op2 = op1, + }), else => unreachable, } disp += 8; @@ -307,13 +320,17 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerErro } } -fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; - try lowerToDEnc(tag, 0, emit.code); + try emit.encode(mnemonic, .{ + .op1 = .{ + .imm = 0, + }, + }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = target, @@ -323,34 +340,33 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { }, 0b01 => { if (ops.reg1 == .none) { - // JMP/CALL [imm] const imm = emit.mir.instructions.items(.data)[inst].imm; - const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { - 16 => .word_ptr, - else => .qword_ptr, - }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .imm = imm }, + }); } - // JMP/CALL reg - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + }); }, 0b10 => { - // JMP/CALL r/m64 - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = ops.reg1, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = disp, + }) }, + }); }, 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), } } fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .cond_jmp); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .cond_jmp); const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; - const tag: Tag = switch (inst_cc.cc) { + const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { .a => .ja, .ae => .jae, .b => .jb, @@ -383,7 +399,9 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .z => .jz, }; const source = emit.code.items.len; - try lowerToDEnc(tag, 0, emit.code); + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = 0 }, + }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = inst_cc.inst, @@ -393,11 +411,11 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .cond_set_byte); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .cond_set_byte); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const cc = emit.mir.instructions.items(.data)[inst].cc; - const tag: Tag = switch (cc) { + const mnemonic: Instruction.Mnemonic = switch (cc) { .a => .seta, .ae => .setae, .b => .setb, @@ -429,15 +447,15 @@ fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .s => .sets, .z => .setz, }; - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code); + return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); } fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .cond_mov); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .cond_mov); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const cc = emit.mir.instructions.items(.data)[inst].cc; - const tag: Tag = switch (cc) { + const mnemonic: Instruction.Mnemonic = switch (cc) { .a => .cmova, .ae => .cmovae, .b => .cmovb, @@ -469,21 +487,28 @@ fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .s => .cmovs, .z => .cmovz, }; + const op1: Instruction.Operand = .{ .reg = ops.reg1 }; if (ops.flags == 0b00) { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = op1, + .op2 = .{ .reg = ops.reg2 }, + }); } - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const ptr_size: Memory.PtrSize = switch (ops.flags) { 0b00 => unreachable, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = op1, + .op2 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg2, + .disp = disp, + }) }, + }); } fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -493,18 +518,16 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { switch (ops.flags) { 0b00 => { if (ops.reg2 == .none) { - // TEST r/m64, imm32 - // MI const imm = emit.mir.instructions.items(.data)[inst].imm; - if (ops.reg1.to64() == .rax) { - // TEST rax, imm32 - // I - return lowerToIEnc(.@"test", imm, emit.code); - } - return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code); + return emit.encode(.@"test", .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = imm }, + }); } - // TEST r/m64, r64 - return lowerToMrEnc(.@"test", RegisterOrMemory.reg(ops.reg1), ops.reg2, emit.code); + return emit.encode(.@"test", .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, else => return emit.fail("TODO more TEST alternatives", .{}), } @@ -515,62 +538,59 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .ret); const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { - 0b00 => { - // RETF imm16 - // I - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_far, imm, emit.code); - }, - 0b01 => { - return lowerToZoEnc(.ret_far, emit.code); - }, + 0b00 => unreachable, + 0b01 => unreachable, 0b10 => { - // RET imm16 - // I const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_near, imm, emit.code); + return emit.encode(.ret, .{ + .op1 = .{ .imm = imm }, + }); }, 0b11 => { - return lowerToZoEnc(.ret_near, emit.code); + return emit.encode(.ret, .{}); }, } } -fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { if (ops.reg2 == .none) { - // mov reg1, imm32 - // MI const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = imm }, + }); } - // mov reg1, reg2 - // RM - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b01 => { - // mov reg1, [reg2 + imm32] - // RM - const imm = emit.mir.instructions.items(.data)[inst].imm; - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = src_reg, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .base = base, + .disp = disp, + }) }, + }); }, 0b10 => { if (ops.reg2 == .none) { return emit.fail("TODO unused variant: mov reg1, none, 0b10", .{}); } - // mov [reg1 + imm32], reg2 - // MR - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = imm, - .base = ops.reg1, - }), ops.reg2, emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .base = ops.reg1, + .disp = disp, + }) }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b11 => { return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); @@ -578,169 +598,165 @@ fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { } } -fn mirArithMemImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); assert(ops.reg2 == .none); const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b00 => .byte, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm_pair.dest_off, - .base = ops.reg1, - }), imm_pair.operand, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = imm_pair.dest_off, + .base = ops.reg1, + }) }, + .op2 = .{ .imm = imm_pair.operand }, + }); } -inline fn setRexWRegister(reg: Register) bool { - if (reg.size() > 64) return false; - if (reg.size() == 64) return true; - return switch (reg) { - .ah, .ch, .dh, .bh => true, - else => false, - }; -} - -inline fn immOpSize(u_imm: u32) u6 { - const imm = @bitCast(i32, u_imm); - if (math.minInt(i8) <= imm and imm <= math.maxInt(i8)) { - return 8; - } - if (math.minInt(i16) <= imm and imm <= math.maxInt(i16)) { - return 16; - } - return 32; -} - -fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - // OP reg1, [reg2 + scale*index + imm32] - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = scale, .index = index_reg_disp.index, }; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = index_reg_disp.disp, - .base = ops.reg2, - .scale_index = scale_index, - }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .base = ops.reg2, + .scale_index = scale_index, + .disp = index_reg_disp.disp, + }) }, + }); } -fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = scale, .index = index_reg_disp.index, }; assert(ops.reg2 != .none); - // OP [reg1 + scale*index + imm32], reg2 - return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = index_reg_disp.disp, - .base = ops.reg1, - .scale_index = scale_index, - }), ops.reg2, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .base = ops.reg1, + .scale_index = scale_index, + .disp = index_reg_disp.disp, + }) }, + .op2 = .{ .reg = ops.reg2 }, + }); } -fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = scale, .index = index_reg_disp_imm.index, }; - // OP qword ptr [reg1 + scale*index + imm32], imm32 - return lowerToMiEnc(tag, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = index_reg_disp_imm.disp, - .base = ops.reg1, - .scale_index = scale_index, - }), index_reg_disp_imm.imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = index_reg_disp_imm.disp, + .scale_index = scale_index, + }) }, + .op2 = .{ .imm = index_reg_disp_imm.imm }, + }); } -fn mirArithMemIndexImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); assert(ops.reg2 == .none); const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b00 => .byte, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = 0, .index = index_reg_disp_imm.index, }; - // OP ptr [reg1 + index + imm32], imm32 - return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = index_reg_disp_imm.disp, - .base = ops.reg1, - .scale_index = scale_index, - }), index_reg_disp_imm.imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = index_reg_disp_imm.disp, + .base = ops.reg1, + .scale_index = scale_index, + }) }, + .op2 = .{ .imm = index_reg_disp_imm.imm }, + }); } fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .mov_sign_extend); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .mov_sign_extend); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined; + const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; switch (ops.flags) { 0b00 => { - const tag: Tag = if (ops.reg2.size() == 32) .movsxd else .movsx; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + const mnemonic: Instruction.Mnemonic = if (ops.reg2.size() == 32) .movsxd else .movsx; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - 0b01 => { - return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b10 => { - return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b11 => { - return lowerToRmEnc(.movsxd, ops.reg1, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + else => { + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b01 => .byte, + 0b10 => .word, + 0b11 => .qword, + else => unreachable, + }; + return emit.encode(.movsx, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = disp, + .base = ops.reg2, + }) }, + }); }, } } fn mirMovZeroExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .mov_zero_extend); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .mov_zero_extend); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined; + const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; switch (ops.flags) { 0b00 => { - return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(.movzx, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - 0b01 => { - return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b10 => { - return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + 0b01, 0b10 => { + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b01 => .byte, + 0b10 => .word, + else => unreachable, + }; + return emit.encode(.movzx, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = disp, + .base = ops.reg2, + }) }, + }); }, 0b11 => { return emit.fail("TODO unused variant: movzx 0b11", .{}); @@ -759,9 +775,10 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; - // movabs reg, imm64 - // OI - return lowerToOiEnc(.mov, ops.reg1, imm, emit.code); + return emit.encode(.mov, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = @bitCast(i64, imm) }, + }); }, 0b01 => { if (ops.reg1 == .none) { @@ -770,18 +787,20 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; - // movabs moffs64, rax - // TD - return lowerToTdEnc(.mov, imm, ops.reg2, emit.code); + return emit.encode(.mov, .{ + .op1 = .{ .mem = Memory.moffs(ops.reg2, imm) }, + .op2 = .{ .reg = .rax }, + }); } const imm: u64 = if (ops.reg1.size() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; - // movabs rax, moffs64 - // FD - return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); + return emit.encode(.mov, .{ + .op1 = .{ .reg = .rax }, + .op2 = .{ .mem = Memory.moffs(ops.reg1, imm) }, + }); }, else => return emit.fail("TODO unused movabs variant", .{}), } @@ -791,63 +810,58 @@ fn mirFisttp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .fisttp); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - - // the selecting between operand sizes for this particular `fisttp` instruction - // is done via opcode instead of the usual prefixes. - - const opcode: Tag = switch (ops.flags) { - 0b00 => .fisttp16, - 0b01 => .fisttp32, - 0b10 => .fisttp64, + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b00 => .word, + 0b01 => .dword, + 0b10 => .qword, else => unreachable, }; - const mem_or_reg = Memory{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].imm, - .ptr_size = Memory.PtrSize.dword_ptr, // to prevent any prefix from being used - }; - return lowerToMEnc(opcode, .{ .memory = mem_or_reg }, emit.code); + return emit.encode(.fisttp, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg1, + .disp = emit.mir.instructions.items(.data)[inst].disp, + }) }, + }); } fn mirFld(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .fld); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - - // the selecting between operand sizes for this particular `fisttp` instruction - // is done via opcode instead of the usual prefixes. - - const opcode: Tag = switch (ops.flags) { - 0b01 => .fld32, - 0b10 => .fld64, + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b01 => .dword, + 0b10 => .qword, else => unreachable, }; - const mem_or_reg = Memory{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].imm, - .ptr_size = Memory.PtrSize.dword_ptr, // to prevent any prefix from being used - }; - return lowerToMEnc(opcode, .{ .memory = mem_or_reg }, emit.code); + return emit.encode(.fld, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg1, + .disp = emit.mir.instructions.items(.data)[inst].disp, + }) }, + }); } -fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - // sal reg1, 1 - // M1 - return lowerToM1Enc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = 1 }, + }); }, 0b01 => { - // sal reg1, .cl - // MC - return lowerToMcEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = .cl }, + }); }, 0b10 => { - // sal reg1, imm8 - // MI const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); - return lowerToMiImm8Enc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = imm }, + }); }, 0b11 => { return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{}); @@ -855,24 +869,28 @@ fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { } } -fn mirMulDiv(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirMulDiv(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); if (ops.reg1 != .none) { assert(ops.reg2 == .none); - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + }); } assert(ops.reg2 != .none); - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b00 => .byte, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg2, + .disp = disp, + }) }, + }); } fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -881,40 +899,54 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b01 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = imm, - .base = src_reg, - }), emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = src_reg, + .disp = disp, + }) }, + }); }, 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + .op3 = .{ .imm = imm }, + }); }, 0b11 => { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = imm_pair.dest_off, - .base = ops.reg2, - }), imm_pair.operand, emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg2, + .disp = imm_pair.dest_off, + }) }, + .op3 = .{ .imm = imm_pair.operand }, + }); }, } } fn mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const tag: Tag = switch (ops.flags) { + const mnemonic: Instruction.Mnemonic = switch (ops.flags) { 0b00 => .cbw, 0b01 => .cwd, 0b10 => .cdq, 0b11 => .cqo, }; - return lowerToZoEnc(tag, emit.code); + return emit.encode(mnemonic, .{}); } fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -923,30 +955,22 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - // lea reg1, [reg2 + imm32] - // RM - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, + return emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ .base = src_reg, - }), - emit.code, - ); + .disp = disp, + }) }, + }); }, 0b01 => { - // lea reg1, [rip + imm32] - // RM const start_offset = emit.code.items.len; - try lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0), - emit.code, - ); + try emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + }); const end_offset = emit.code.items.len; // Backpatch the displacement const payload = emit.mir.instructions.items(.data)[inst].payload; @@ -955,24 +979,21 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); }, 0b10 => { - // lea reg, [rbp + index + imm32] const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = 0, .index = index_reg_disp.index, }; - return lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = index_reg_disp.disp, + return emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ .base = src_reg, .scale_index = scale_index, - }), - emit.code, - ); + .disp = index_reg_disp.disp, + }) }, + }); }, 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), } @@ -989,14 +1010,10 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), } - // lea reg1, [rip + reloc] - // RM - try lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0), - emit.code, - ); + try emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + }); const end_offset = emit.code.items.len; @@ -1039,94 +1056,64 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -// SSE instructions +// SSE/AVX instructions -fn mirMovFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .base = ops.reg2, + .disp = disp, + }) }, + }); }, 0b01 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = ops.reg1, - }), ops.reg2, emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .base = ops.reg1, + .disp = disp, + }) }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b10 => { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), + else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), } } -fn mirAddFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirAddFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), + else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), } } -fn mirCmpFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirCmpFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), - } -} -// AVX instructions - -fn mirMovFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToVmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b01 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMvEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = ops.reg1, - }), ops.reg2, emit.code); - }, - 0b10 => { - return lowerToRvmEnc(tag, ops.reg1, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), - } -} - -fn mirAddFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return lowerToRvmEnc(tag, ops.reg1, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), - } -} - -fn mirCmpFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return lowerToVmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); - }, - else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{ops.flags}), + else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), } } @@ -1139,7 +1126,9 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const offset = blk: { // callq - try lowerToDEnc(.call_near, 0, emit.code); + try emit.encode(.call, .{ + .op1 = .{ .imm = 0 }, + }); break :blk @intCast(u32, emit.code.items.len) - 4; }; @@ -1264,1841 +1253,3 @@ fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .none => {}, } } - -const Tag = enum { - adc, - add, - sub, - xor, - @"and", - @"or", - sbb, - cmp, - mov, - movsx, - movsxd, - movzx, - lea, - jmp_near, - call_near, - push, - pop, - @"test", - ud2, - int3, - nop, - imul, - mul, - idiv, - div, - syscall, - ret_near, - ret_far, - fisttp16, - fisttp32, - fisttp64, - fld32, - fld64, - jo, - jno, - jb, - jbe, - jc, - jnae, - jnc, - jae, - je, - jz, - jne, - jnz, - jna, - jnb, - jnbe, - ja, - js, - jns, - jpe, - jp, - jpo, - jnp, - jnge, - jl, - jge, - jnl, - jle, - jng, - jg, - jnle, - seto, - setno, - setb, - setc, - setnae, - setnb, - setnc, - setae, - sete, - setz, - setne, - setnz, - setbe, - setna, - seta, - setnbe, - sets, - setns, - setp, - setpe, - setnp, - setpo, - setl, - setnge, - setnl, - setge, - setle, - setng, - setnle, - setg, - cmovo, - cmovno, - cmovb, - cmovc, - cmovnae, - cmovnb, - cmovnc, - cmovae, - cmove, - cmovz, - cmovne, - cmovnz, - cmovbe, - cmovna, - cmova, - cmovnbe, - cmovs, - cmovns, - cmovp, - cmovpe, - cmovnp, - cmovpo, - cmovl, - cmovnge, - cmovnl, - cmovge, - cmovle, - cmovng, - cmovnle, - cmovg, - shl, - sal, - shr, - sar, - cbw, - cwd, - cdq, - cqo, - movsd, - movss, - addsd, - addss, - cmpsd, - cmpss, - ucomisd, - ucomiss, - vmovsd, - vmovss, - vaddsd, - vaddss, - vcmpsd, - vcmpss, - vucomisd, - vucomiss, - - fn isSse(tag: Tag) bool { - return switch (tag) { - .movsd, - .movss, - .addsd, - .addss, - .cmpsd, - .cmpss, - .ucomisd, - .ucomiss, - => true, - - else => false, - }; - } - - fn isAvx(tag: Tag) bool { - return switch (tag) { - .vmovsd, - .vmovss, - .vaddsd, - .vaddss, - .vcmpsd, - .vcmpss, - .vucomisd, - .vucomiss, - => true, - - else => false, - }; - } - - fn isSetCC(tag: Tag) bool { - return switch (tag) { - .seto, - .setno, - .setb, - .setc, - .setnae, - .setnb, - .setnc, - .setae, - .sete, - .setz, - .setne, - .setnz, - .setbe, - .setna, - .seta, - .setnbe, - .sets, - .setns, - .setp, - .setpe, - .setnp, - .setpo, - .setl, - .setnge, - .setnl, - .setge, - .setle, - .setng, - .setnle, - .setg, - => true, - else => false, - }; - } -}; - -const Encoding = enum { - /// OP - zo, - - /// OP rel32 - d, - - /// OP r/m64 - m, - - /// OP r64 - o, - - /// OP imm32 - i, - - /// OP r/m64, 1 - m1, - - /// OP r/m64, .cl - mc, - - /// OP r/m64, imm32 - mi, - - /// OP r/m64, imm8 - mi8, - - /// OP r/m64, r64 - mr, - - /// OP r64, r/m64 - rm, - - /// OP r64, imm64 - oi, - - /// OP al/ax/eax/rax, moffs - fd, - - /// OP moffs, al/ax/eax/rax - td, - - /// OP r64, r/m64, imm32 - rmi, - - /// OP xmm1, xmm2/m64 - vm, - - /// OP m64, xmm1 - mv, - - /// OP xmm1, xmm2, xmm3/m64 - rvm, - - /// OP xmm1, xmm2, xmm3/m64, imm8 - rvmi, -}; - -const OpCode = struct { - bytes: [3]u8, - count: usize, - - fn init(comptime in_bytes: []const u8) OpCode { - comptime assert(in_bytes.len <= 3); - comptime var bytes: [3]u8 = undefined; - inline for (in_bytes, 0..) |x, i| { - bytes[i] = x; - } - return .{ .bytes = bytes, .count = in_bytes.len }; - } - - fn encode(opc: OpCode, encoder: Encoder) void { - switch (opc.count) { - 1 => encoder.opcode_1byte(opc.bytes[0]), - 2 => encoder.opcode_2byte(opc.bytes[0], opc.bytes[1]), - 3 => encoder.opcode_3byte(opc.bytes[0], opc.bytes[1], opc.bytes[2]), - else => unreachable, - } - } - - fn encodeWithReg(opc: OpCode, encoder: Encoder, reg: Register) void { - assert(opc.count == 1); - encoder.opcode_withReg(opc.bytes[0], reg.lowEnc()); - } -}; - -inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) OpCode { - // zig fmt: off - switch (enc) { - .zo => return switch (tag) { - .ret_near => OpCode.init(&.{0xc3}), - .ret_far => OpCode.init(&.{0xcb}), - .ud2 => OpCode.init(&.{ 0x0F, 0x0B }), - .int3 => OpCode.init(&.{0xcc}), - .nop => OpCode.init(&.{0x90}), - .syscall => OpCode.init(&.{ 0x0f, 0x05 }), - .cbw => OpCode.init(&.{0x98}), - .cwd, - .cdq, - .cqo => OpCode.init(&.{0x99}), - else => unreachable, - }, - .d => return switch (tag) { - .jmp_near => OpCode.init(&.{0xe9}), - .call_near => OpCode.init(&.{0xe8}), - - .jo => if (is_one_byte) OpCode.init(&.{0x70}) else OpCode.init(&.{0x0f,0x80}), - - .jno => if (is_one_byte) OpCode.init(&.{0x71}) else OpCode.init(&.{0x0f,0x81}), - - .jb, - .jc, - .jnae => if (is_one_byte) OpCode.init(&.{0x72}) else OpCode.init(&.{0x0f,0x82}), - - .jnb, - .jnc, - .jae => if (is_one_byte) OpCode.init(&.{0x73}) else OpCode.init(&.{0x0f,0x83}), - - .je, - .jz => if (is_one_byte) OpCode.init(&.{0x74}) else OpCode.init(&.{0x0f,0x84}), - - .jne, - .jnz => if (is_one_byte) OpCode.init(&.{0x75}) else OpCode.init(&.{0x0f,0x85}), - - .jna, - .jbe => if (is_one_byte) OpCode.init(&.{0x76}) else OpCode.init(&.{0x0f,0x86}), - - .jnbe, - .ja => if (is_one_byte) OpCode.init(&.{0x77}) else OpCode.init(&.{0x0f,0x87}), - - .js => if (is_one_byte) OpCode.init(&.{0x78}) else OpCode.init(&.{0x0f,0x88}), - - .jns => if (is_one_byte) OpCode.init(&.{0x79}) else OpCode.init(&.{0x0f,0x89}), - - .jpe, - .jp => if (is_one_byte) OpCode.init(&.{0x7a}) else OpCode.init(&.{0x0f,0x8a}), - - .jpo, - .jnp => if (is_one_byte) OpCode.init(&.{0x7b}) else OpCode.init(&.{0x0f,0x8b}), - - .jnge, - .jl => if (is_one_byte) OpCode.init(&.{0x7c}) else OpCode.init(&.{0x0f,0x8c}), - - .jge, - .jnl => if (is_one_byte) OpCode.init(&.{0x7d}) else OpCode.init(&.{0x0f,0x8d}), - - .jle, - .jng => if (is_one_byte) OpCode.init(&.{0x7e}) else OpCode.init(&.{0x0f,0x8e}), - - .jg, - .jnle => if (is_one_byte) OpCode.init(&.{0x7f}) else OpCode.init(&.{0x0f,0x8f}), - - else => unreachable, - }, - .m => return switch (tag) { - .jmp_near, - .call_near, - .push => OpCode.init(&.{0xff}), - - .pop => OpCode.init(&.{0x8f}), - .seto => OpCode.init(&.{0x0f,0x90}), - .setno => OpCode.init(&.{0x0f,0x91}), - - .setb, - .setc, - .setnae => OpCode.init(&.{0x0f,0x92}), - - .setnb, - .setnc, - .setae => OpCode.init(&.{0x0f,0x93}), - - .sete, - .setz => OpCode.init(&.{0x0f,0x94}), - - .setne, - .setnz => OpCode.init(&.{0x0f,0x95}), - - .setbe, - .setna => OpCode.init(&.{0x0f,0x96}), - - .seta, - .setnbe => OpCode.init(&.{0x0f,0x97}), - - .sets => OpCode.init(&.{0x0f,0x98}), - .setns => OpCode.init(&.{0x0f,0x99}), - - .setp, - .setpe => OpCode.init(&.{0x0f,0x9a}), - - .setnp, - .setpo => OpCode.init(&.{0x0f,0x9b}), - - .setl, - .setnge => OpCode.init(&.{0x0f,0x9c}), - - .setnl, - .setge => OpCode.init(&.{0x0f,0x9d}), - - .setle, - .setng => OpCode.init(&.{0x0f,0x9e}), - - .setnle, - .setg => OpCode.init(&.{0x0f,0x9f}), - - .idiv, - .div, - .imul, - .mul => if (is_one_byte) OpCode.init(&.{0xf6}) else OpCode.init(&.{0xf7}), - - .fisttp16 => OpCode.init(&.{0xdf}), - .fisttp32 => OpCode.init(&.{0xdb}), - .fisttp64 => OpCode.init(&.{0xdd}), - .fld32 => OpCode.init(&.{0xd9}), - .fld64 => OpCode.init(&.{0xdd}), - else => unreachable, - }, - .o => return switch (tag) { - .push => OpCode.init(&.{0x50}), - .pop => OpCode.init(&.{0x58}), - else => unreachable, - }, - .i => return switch (tag) { - .push => if (is_one_byte) OpCode.init(&.{0x6a}) else OpCode.init(&.{0x68}), - .@"test" => if (is_one_byte) OpCode.init(&.{0xa8}) else OpCode.init(&.{0xa9}), - .ret_near => OpCode.init(&.{0xc2}), - .ret_far => OpCode.init(&.{0xca}), - else => unreachable, - }, - .m1 => return switch (tag) { - .shl, .sal, - .shr, .sar => if (is_one_byte) OpCode.init(&.{0xd0}) else OpCode.init(&.{0xd1}), - else => unreachable, - }, - .mc => return switch (tag) { - .shl, .sal, - .shr, .sar => if (is_one_byte) OpCode.init(&.{0xd2}) else OpCode.init(&.{0xd3}), - else => unreachable, - }, - .mi => return switch (tag) { - .adc, .add, - .sub, .xor, - .@"and", .@"or", - .sbb, .cmp => if (is_one_byte) OpCode.init(&.{0x80}) else OpCode.init(&.{0x81}), - .mov => if (is_one_byte) OpCode.init(&.{0xc6}) else OpCode.init(&.{0xc7}), - .@"test" => if (is_one_byte) OpCode.init(&.{0xf6}) else OpCode.init(&.{0xf7}), - else => unreachable, - }, - .mi8 => return switch (tag) { - .adc, .add, - .sub, .xor, - .@"and", .@"or", - .sbb, .cmp => OpCode.init(&.{0x83}), - .shl, .sal, - .shr, .sar => if (is_one_byte) OpCode.init(&.{0xc0}) else OpCode.init(&.{0xc1}), - else => unreachable, - }, - .mr => return switch (tag) { - .adc => if (is_one_byte) OpCode.init(&.{0x10}) else OpCode.init(&.{0x11}), - .add => if (is_one_byte) OpCode.init(&.{0x00}) else OpCode.init(&.{0x01}), - .sub => if (is_one_byte) OpCode.init(&.{0x28}) else OpCode.init(&.{0x29}), - .xor => if (is_one_byte) OpCode.init(&.{0x30}) else OpCode.init(&.{0x31}), - .@"and" => if (is_one_byte) OpCode.init(&.{0x20}) else OpCode.init(&.{0x21}), - .@"or" => if (is_one_byte) OpCode.init(&.{0x08}) else OpCode.init(&.{0x09}), - .sbb => if (is_one_byte) OpCode.init(&.{0x18}) else OpCode.init(&.{0x19}), - .cmp => if (is_one_byte) OpCode.init(&.{0x38}) else OpCode.init(&.{0x39}), - .mov => if (is_one_byte) OpCode.init(&.{0x88}) else OpCode.init(&.{0x89}), - .@"test" => if (is_one_byte) OpCode.init(&.{0x84}) else OpCode.init(&.{0x85}), - .movsd => OpCode.init(&.{0xf2,0x0f,0x11}), - .movss => OpCode.init(&.{0xf3,0x0f,0x11}), - else => unreachable, - }, - .rm => return switch (tag) { - .adc => if (is_one_byte) OpCode.init(&.{0x12}) else OpCode.init(&.{0x13}), - .add => if (is_one_byte) OpCode.init(&.{0x02}) else OpCode.init(&.{0x03}), - .sub => if (is_one_byte) OpCode.init(&.{0x2a}) else OpCode.init(&.{0x2b}), - .xor => if (is_one_byte) OpCode.init(&.{0x32}) else OpCode.init(&.{0x33}), - .@"and" => if (is_one_byte) OpCode.init(&.{0x22}) else OpCode.init(&.{0x23}), - .@"or" => if (is_one_byte) OpCode.init(&.{0x0a}) else OpCode.init(&.{0x0b}), - .sbb => if (is_one_byte) OpCode.init(&.{0x1a}) else OpCode.init(&.{0x1b}), - .cmp => if (is_one_byte) OpCode.init(&.{0x3a}) else OpCode.init(&.{0x3b}), - .mov => if (is_one_byte) OpCode.init(&.{0x8a}) else OpCode.init(&.{0x8b}), - .movsx => if (is_one_byte) OpCode.init(&.{0x0f,0xbe}) else OpCode.init(&.{0x0f,0xbf}), - .movsxd => OpCode.init(&.{0x63}), - .movzx => if (is_one_byte) OpCode.init(&.{0x0f,0xb6}) else OpCode.init(&.{0x0f,0xb7}), - .lea => if (is_one_byte) OpCode.init(&.{0x8c}) else OpCode.init(&.{0x8d}), - .imul => OpCode.init(&.{0x0f,0xaf}), - - .cmova, - .cmovnbe, => OpCode.init(&.{0x0f,0x47}), - - .cmovae, - .cmovnb, => OpCode.init(&.{0x0f,0x43}), - - .cmovb, - .cmovc, - .cmovnae => OpCode.init(&.{0x0f,0x42}), - - .cmovbe, - .cmovna, => OpCode.init(&.{0x0f,0x46}), - - .cmove, - .cmovz, => OpCode.init(&.{0x0f,0x44}), - - .cmovg, - .cmovnle, => OpCode.init(&.{0x0f,0x4f}), - - .cmovge, - .cmovnl, => OpCode.init(&.{0x0f,0x4d}), - - .cmovl, - .cmovnge, => OpCode.init(&.{0x0f,0x4c}), - - .cmovle, - .cmovng, => OpCode.init(&.{0x0f,0x4e}), - - .cmovne, - .cmovnz, => OpCode.init(&.{0x0f,0x45}), - - .cmovno => OpCode.init(&.{0x0f,0x41}), - - .cmovnp, - .cmovpo, => OpCode.init(&.{0x0f,0x4b}), - - .cmovns => OpCode.init(&.{0x0f,0x49}), - - .cmovo => OpCode.init(&.{0x0f,0x40}), - - .cmovp, - .cmovpe, => OpCode.init(&.{0x0f,0x4a}), - - .cmovs => OpCode.init(&.{0x0f,0x48}), - - .movsd => OpCode.init(&.{0xf2,0x0f,0x10}), - .movss => OpCode.init(&.{0xf3,0x0f,0x10}), - .addsd => OpCode.init(&.{0xf2,0x0f,0x58}), - .addss => OpCode.init(&.{0xf3,0x0f,0x58}), - .ucomisd => OpCode.init(&.{0x66,0x0f,0x2e}), - .ucomiss => OpCode.init(&.{0x0f,0x2e}), - else => unreachable, - }, - .oi => return switch (tag) { - .mov => if (is_one_byte) OpCode.init(&.{0xb0}) else OpCode.init(&.{0xb8}), - else => unreachable, - }, - .fd => return switch (tag) { - .mov => if (is_one_byte) OpCode.init(&.{0xa0}) else OpCode.init(&.{0xa1}), - else => unreachable, - }, - .td => return switch (tag) { - .mov => if (is_one_byte) OpCode.init(&.{0xa2}) else OpCode.init(&.{0xa3}), - else => unreachable, - }, - .rmi => return switch (tag) { - .imul => if (is_one_byte) OpCode.init(&.{0x6b}) else OpCode.init(&.{0x69}), - else => unreachable, - }, - .mv => return switch (tag) { - .vmovsd, - .vmovss => OpCode.init(&.{0x11}), - else => unreachable, - }, - .vm => return switch (tag) { - .vmovsd, - .vmovss => OpCode.init(&.{0x10}), - .vucomisd, - .vucomiss => OpCode.init(&.{0x2e}), - else => unreachable, - }, - .rvm => return switch (tag) { - .vaddsd, - .vaddss => OpCode.init(&.{0x58}), - .vmovsd, - .vmovss => OpCode.init(&.{0x10}), - else => unreachable, - }, - .rvmi => return switch (tag) { - .vcmpsd, - .vcmpss => OpCode.init(&.{0xc2}), - else => unreachable, - }, - } - // zig fmt: on -} - -inline fn getModRmExt(tag: Tag) u3 { - return switch (tag) { - .adc => 0x2, - .add => 0x0, - .sub => 0x5, - .xor => 0x6, - .@"and" => 0x4, - .@"or" => 0x1, - .sbb => 0x3, - .cmp => 0x7, - .mov => 0x0, - .jmp_near => 0x4, - .call_near => 0x2, - .push => 0x6, - .pop => 0x0, - .@"test" => 0x0, - .seto, - .setno, - .setb, - .setc, - .setnae, - .setnb, - .setnc, - .setae, - .sete, - .setz, - .setne, - .setnz, - .setbe, - .setna, - .seta, - .setnbe, - .sets, - .setns, - .setp, - .setpe, - .setnp, - .setpo, - .setl, - .setnge, - .setnl, - .setge, - .setle, - .setng, - .setnle, - .setg, - => 0x0, - .shl, - .sal, - => 0x4, - .shr => 0x5, - .sar => 0x7, - .mul => 0x4, - .imul => 0x5, - .div => 0x6, - .idiv => 0x7, - .fisttp16 => 0x1, - .fisttp32 => 0x1, - .fisttp64 => 0x1, - .fld32 => 0x0, - .fld64 => 0x0, - else => unreachable, - }; -} - -const VexEncoding = struct { - prefix: Encoder.Vex, - reg: ?enum { - ndd, - nds, - dds, - }, -}; - -inline fn getVexEncoding(tag: Tag, enc: Encoding) VexEncoding { - const desc: struct { - reg: enum { - none, - ndd, - nds, - dds, - } = .none, - len_256: bool = false, - wig: bool = false, - lig: bool = false, - lz: bool = false, - lead_opc: enum { - l_0f, - l_0f_3a, - l_0f_38, - } = .l_0f, - simd_prefix: enum { - none, - p_66, - p_f2, - p_f3, - } = .none, - } = blk: { - switch (enc) { - .mv => switch (tag) { - .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vmovss => break :blk .{ .lig = true, .simd_prefix = .p_f3, .wig = true }, - else => unreachable, - }, - .vm => switch (tag) { - .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vmovss => break :blk .{ .lig = true, .simd_prefix = .p_f3, .wig = true }, - .vucomisd => break :blk .{ .lig = true, .simd_prefix = .p_66, .wig = true }, - .vucomiss => break :blk .{ .lig = true, .wig = true }, - else => unreachable, - }, - .rvm => switch (tag) { - .vaddsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vaddss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true }, - .vmovsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vmovss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true }, - else => unreachable, - }, - .rvmi => switch (tag) { - .vcmpsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vcmpss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true }, - else => unreachable, - }, - else => unreachable, - } - }; - - var vex: Encoder.Vex = .{}; - - if (desc.len_256) vex.len_256(); - if (desc.wig) vex.wig(); - if (desc.lig) vex.lig(); - if (desc.lz) vex.lz(); - - switch (desc.lead_opc) { - .l_0f => {}, - .l_0f_3a => vex.lead_opc_0f_3a(), - .l_0f_38 => vex.lead_opc_0f_38(), - } - - switch (desc.simd_prefix) { - .none => {}, - .p_66 => vex.simd_prefix_66(), - .p_f2 => vex.simd_prefix_f2(), - .p_f3 => vex.simd_prefix_f3(), - } - - return VexEncoding{ .prefix = vex, .reg = switch (desc.reg) { - .none => null, - .nds => .nds, - .dds => .dds, - .ndd => .ndd, - } }; -} - -const ScaleIndex = packed struct { - scale: u2, - index: Register, -}; - -const Memory = struct { - base: ?Register, - rip: bool = false, - disp: u32, - ptr_size: PtrSize, - scale_index: ?ScaleIndex = null, - - const PtrSize = enum(u2) { - byte_ptr = 0b00, - word_ptr = 0b01, - dword_ptr = 0b10, - qword_ptr = 0b11, - - fn new(bit_size: u64) PtrSize { - return @intToEnum(PtrSize, math.log2_int(u4, @intCast(u4, @divExact(bit_size, 8)))); - } - - /// Returns size in bits. - fn size(ptr_size: PtrSize) u64 { - return 8 * (math.powi(u8, 2, @enumToInt(ptr_size)) catch unreachable); - } - }; - - fn encode(mem_op: Memory, encoder: Encoder, operand: u3) void { - if (mem_op.base) |base| { - const dst = base.lowEnc(); - const src = operand; - if (dst == 4 or mem_op.scale_index != null) { - if (mem_op.disp == 0 and dst != 5) { - encoder.modRm_SIBDisp0(src); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexBase(si.scale, si.index.lowEnc(), dst); - } else { - encoder.sib_base(dst); - } - } else if (immOpSize(mem_op.disp) == 8) { - encoder.modRm_SIBDisp8(src); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexBaseDisp8(si.scale, si.index.lowEnc(), dst); - } else { - encoder.sib_baseDisp8(dst); - } - encoder.disp8(@bitCast(i8, @truncate(u8, mem_op.disp))); - } else { - encoder.modRm_SIBDisp32(src); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexBaseDisp32(si.scale, si.index.lowEnc(), dst); - } else { - encoder.sib_baseDisp32(dst); - } - encoder.disp32(@bitCast(i32, mem_op.disp)); - } - } else { - if (mem_op.disp == 0 and dst != 5) { - encoder.modRm_indirectDisp0(src, dst); - } else if (immOpSize(mem_op.disp) == 8) { - encoder.modRm_indirectDisp8(src, dst); - encoder.disp8(@bitCast(i8, @truncate(u8, mem_op.disp))); - } else { - encoder.modRm_indirectDisp32(src, dst); - encoder.disp32(@bitCast(i32, mem_op.disp)); - } - } - } else { - if (mem_op.rip) { - encoder.modRm_RIPDisp32(operand); - } else { - encoder.modRm_SIBDisp0(operand); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexDisp32(si.scale, si.index.lowEnc()); - } else { - encoder.sib_disp32(); - } - } - encoder.disp32(@bitCast(i32, mem_op.disp)); - } - } - - /// Returns size in bits. - fn size(memory: Memory) u64 { - return memory.ptr_size.size(); - } -}; - -fn encodeImm(encoder: Encoder, imm: u32, size: u64) void { - switch (size) { - 8 => encoder.imm8(@bitCast(i8, @truncate(u8, imm))), - 16 => encoder.imm16(@bitCast(i16, @truncate(u16, imm))), - 32, 64 => encoder.imm32(@bitCast(i32, imm)), - else => unreachable, - } -} - -const RegisterOrMemory = union(enum) { - register: Register, - memory: Memory, - - fn reg(register: Register) RegisterOrMemory { - return .{ .register = register }; - } - - fn mem(ptr_size: Memory.PtrSize, args: struct { - disp: u32, - base: ?Register = null, - scale_index: ?ScaleIndex = null, - }) RegisterOrMemory { - return .{ - .memory = .{ - .base = args.base, - .disp = args.disp, - .ptr_size = ptr_size, - .scale_index = args.scale_index, - }, - }; - } - - fn rip(ptr_size: Memory.PtrSize, disp: u32) RegisterOrMemory { - return .{ - .memory = .{ - .base = null, - .rip = true, - .disp = disp, - .ptr_size = ptr_size, - }, - }; - } - - /// Returns size in bits. - fn size(reg_or_mem: RegisterOrMemory) u64 { - return switch (reg_or_mem) { - .register => |register| register.size(), - .memory => |memory| memory.size(), - }; - } -}; - -fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .zo, false); - const encoder = try Encoder.init(code, 2); - switch (tag) { - .cqo => { - encoder.rex(.{ - .w = true, - }); - }, - else => {}, - } - opc.encode(encoder); -} - -fn lowerToIEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - if (tag == .ret_far or tag == .ret_near) { - const encoder = try Encoder.init(code, 3); - const opc = getOpCode(tag, .i, false); - opc.encode(encoder); - encoder.imm16(@bitCast(i16, @truncate(u16, imm))); - return; - } - const opc = getOpCode(tag, .i, immOpSize(imm) == 8); - const encoder = try Encoder.init(code, 5); - if (immOpSize(imm) == 16) { - encoder.prefix16BitMode(); - } - opc.encode(encoder); - encodeImm(encoder, imm, immOpSize(imm)); -} - -fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .o, false); - const encoder = try Encoder.init(code, 3); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = false, - .b = reg.isExtended(), - }); - opc.encodeWithReg(encoder, reg); -} - -fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .d, false); - const encoder = try Encoder.init(code, 6); - opc.encode(encoder); - encoder.imm32(@bitCast(i32, imm)); -} - -fn lowerToMxEnc(tag: Tag, reg_or_mem: RegisterOrMemory, enc: Encoding, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, enc, reg_or_mem.size() == 8); - const modrm_ext = getModRmExt(tag); - switch (reg_or_mem) { - .register => |reg| { - const encoder = try Encoder.init(code, 4); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - const wide = if (tag == .jmp_near) false else setRexWRegister(reg); - encoder.rex(.{ - .w = wide, - .b = reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(modrm_ext, reg.lowEnc()); - }, - .memory => |mem_op| { - const encoder = try Encoder.init(code, 8); - if (mem_op.ptr_size == .word_ptr) { - encoder.prefix16BitMode(); - } - if (mem_op.base) |base| { - const wide = if (tag == .jmp_near) false else mem_op.ptr_size == .qword_ptr; - encoder.rex(.{ - .w = wide, - .b = base.isExtended(), - .x = if (mem_op.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - mem_op.encode(encoder, modrm_ext); - }, - } -} - -fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMxEnc(tag, reg_or_mem, .m, code); -} - -fn lowerToM1Enc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMxEnc(tag, reg_or_mem, .m1, code); -} - -fn lowerToMcEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMxEnc(tag, reg_or_mem, .mc, code); -} - -fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { - return lowerToTdFdEnc(tag, reg, moffs, code, true); -} - -fn lowerToFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8)) InnerError!void { - return lowerToTdFdEnc(tag, reg, moffs, code, false); -} - -fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8), td: bool) InnerError!void { - assert(!tag.isAvx()); - const opc = if (td) getOpCode(tag, .td, reg.size() == 8) else getOpCode(tag, .fd, reg.size() == 8); - const encoder = try Encoder.init(code, 10); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(reg), - }); - opc.encode(encoder); - switch (reg.size()) { - 8 => encoder.imm8(@bitCast(i8, @truncate(u8, moffs))), - 16 => encoder.imm16(@bitCast(i16, @truncate(u16, moffs))), - 32 => encoder.imm32(@bitCast(i32, @truncate(u32, moffs))), - 64 => encoder.imm64(moffs), - else => unreachable, - } -} - -fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .oi, reg.size() == 8); - const encoder = try Encoder.init(code, 10); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(reg), - .b = reg.isExtended(), - }); - opc.encodeWithReg(encoder, reg); - switch (reg.size()) { - 8 => encoder.imm8(@bitCast(i8, @truncate(u8, imm))), - 16 => encoder.imm16(@bitCast(i16, @truncate(u16, imm))), - 32 => encoder.imm32(@bitCast(i32, @truncate(u32, imm))), - 64 => encoder.imm64(imm), - else => unreachable, - } -} - -fn lowerToMiXEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - imm: u32, - enc: Encoding, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const modrm_ext = getModRmExt(tag); - const opc = getOpCode(tag, enc, reg_or_mem.size() == 8); - switch (reg_or_mem) { - .register => |dst_reg| { - const encoder = try Encoder.init(code, 7); - if (dst_reg.size() == 16) { - // 0x66 prefix switches to the non-default size; here we assume a switch from - // the default 32bits to 16bits operand-size. - // More info: https://www.cs.uni-potsdam.de/desn/lehre/ss15/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf#page=32&zoom=auto,-159,773 - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(dst_reg), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(modrm_ext, dst_reg.lowEnc()); - encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_reg.size()); - }, - .memory => |dst_mem| { - const encoder = try Encoder.init(code, 12); - if (dst_mem.ptr_size == .word_ptr) { - encoder.prefix16BitMode(); - } - if (dst_mem.base) |base| { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr, - .b = base.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr, - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - dst_mem.encode(encoder, modrm_ext); - encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_mem.ptr_size.size()); - }, - } -} - -fn lowerToMiImm8Enc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u8, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMiXEnc(tag, reg_or_mem, imm, .mi8, code); -} - -fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMiXEnc(tag, reg_or_mem, imm, .mi, code); -} - -fn lowerToRmEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .rm, reg.size() == 8 or reg_or_mem.size() == 8); - switch (reg_or_mem) { - .register => |src_reg| { - const encoder = try Encoder.init(code, 5); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(reg) or setRexWRegister(src_reg), - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc()); - }, - .memory => |src_mem| { - const encoder = try Encoder.init(code, 9); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - if (src_mem.base) |base| { - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - src_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -fn lowerToMrEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - reg: Register, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .mr, reg.size() == 8 or reg_or_mem.size() == 8); - switch (reg_or_mem) { - .register => |dst_reg| { - const encoder = try Encoder.init(code, 4); - if (dst_reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(dst_reg) or setRexWRegister(reg), - .r = reg.isExtended(), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), dst_reg.lowEnc()); - }, - .memory => |dst_mem| { - const encoder = try Encoder.init(code, 9); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - if (dst_mem.base) |base| { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), - .r = reg.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - dst_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -fn lowerToRmiEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - imm: u32, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .rmi, false); - const encoder = try Encoder.init(code, 13); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - switch (reg_or_mem) { - .register => |src_reg| { - encoder.rex(.{ - .w = setRexWRegister(reg) or setRexWRegister(src_reg), - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc()); - }, - .memory => |src_mem| { - if (src_mem.base) |base| { - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - src_mem.encode(encoder, reg.lowEnc()); - }, - } - encodeImm(encoder, imm, reg.size()); -} - -/// Also referred to as XM encoding in Intel manual. -fn lowerToVmEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .vm, false); - var enc = getVexEncoding(tag, .vm); - const vex = &enc.prefix; - switch (reg_or_mem) { - .register => |src_reg| { - const encoder = try Encoder.init(code, 5); - vex.rex(.{ - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc()); - }, - .memory => |src_mem| { - const encoder = try Encoder.init(code, 10); - if (src_mem.base) |base| { - vex.rex(.{ - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - vex.rex(.{ - .r = reg.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - encoder.vex(enc.prefix); - opc.encode(encoder); - src_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -/// Usually referred to as MR encoding with V/V in Intel manual. -fn lowerToMvEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - reg: Register, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .mv, false); - var enc = getVexEncoding(tag, .mv); - const vex = &enc.prefix; - switch (reg_or_mem) { - .register => |dst_reg| { - const encoder = try Encoder.init(code, 4); - vex.rex(.{ - .r = reg.isExtended(), - .b = dst_reg.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), dst_reg.lowEnc()); - }, - .memory => |dst_mem| { - const encoder = try Encoder.init(code, 10); - if (dst_mem.base) |base| { - vex.rex(.{ - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - vex.rex(.{ - .r = reg.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - encoder.vex(enc.prefix); - opc.encode(encoder); - dst_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -fn lowerToRvmEnc( - tag: Tag, - reg1: Register, - reg2: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .rvm, false); - var enc = getVexEncoding(tag, .rvm); - const vex = &enc.prefix; - switch (reg_or_mem) { - .register => |reg3| { - if (enc.reg) |vvvv| { - switch (vvvv) { - .nds => vex.reg(reg2.enc()), - else => unreachable, // TODO - } - } - const encoder = try Encoder.init(code, 5); - vex.rex(.{ - .r = reg1.isExtended(), - .b = reg3.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg1.lowEnc(), reg3.lowEnc()); - }, - .memory => |dst_mem| { - _ = dst_mem; - unreachable; // TODO - }, - } -} - -fn lowerToRvmiEnc( - tag: Tag, - reg1: Register, - reg2: Register, - reg_or_mem: RegisterOrMemory, - imm: u32, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .rvmi, false); - var enc = getVexEncoding(tag, .rvmi); - const vex = &enc.prefix; - const encoder: Encoder = blk: { - switch (reg_or_mem) { - .register => |reg3| { - if (enc.reg) |vvvv| { - switch (vvvv) { - .nds => vex.reg(reg2.enc()), - else => unreachable, // TODO - } - } - const encoder = try Encoder.init(code, 5); - vex.rex(.{ - .r = reg1.isExtended(), - .b = reg3.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg1.lowEnc(), reg3.lowEnc()); - break :blk encoder; - }, - .memory => |dst_mem| { - _ = dst_mem; - unreachable; // TODO - }, - } - }; - encodeImm(encoder, imm, 8); // TODO -} - -fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { - assert(expected.len > 0); - if (mem.eql(u8, expected, given)) return; - const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); - defer testing.allocator.free(expected_fmt); - const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); - defer testing.allocator.free(given_fmt); - const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?; - var padding = try testing.allocator.alloc(u8, idx + 5); - defer testing.allocator.free(padding); - mem.set(u8, padding, ' '); - std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ - assembly, - expected_fmt, - given_fmt, - padding, - }); - return error.TestFailed; -} - -const TestEmit = struct { - code_buffer: std.ArrayList(u8), - next: usize = 0, - - fn init() TestEmit { - return .{ - .code_buffer = std.ArrayList(u8).init(testing.allocator), - }; - } - - fn deinit(emit: *TestEmit) void { - emit.code_buffer.deinit(); - emit.next = undefined; - } - - fn code(emit: *TestEmit) *std.ArrayList(u8) { - emit.next = emit.code_buffer.items.len; - return &emit.code_buffer; - } - - fn lowered(emit: TestEmit) []const u8 { - return emit.code_buffer.items[emit.next..]; - } -}; - -test "lower MI encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, emit.code()); - try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", emit.lowered(), "mov rax, 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0, .base = .r11 }), 0x10, emit.code()); - try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", emit.lowered(), "mov dword ptr [r11 + 0], 0x10"); - try lowerToMiEnc(.add, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rdx, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", emit.lowered(), "add dword ptr [rdx - 8], 0x10"); - try lowerToMiEnc(.sub, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = 0x10000000, - .base = .r11, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "sub dword ptr [r11 + 0x10000000], 0x10", - ); - try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0x10000000 }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "and dword ptr [ds:0x10000000], 0x10", - ); - try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.dword_ptr, .{ - .disp = 0x10000000, - .base = .r12, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "and dword ptr [r12 + 0x10000000], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.rip(.qword_ptr, 0x10), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", - emit.lowered(), - "mov qword ptr [rip + 0x10], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\xc7\x45\xf8\x10\x00\x00\x00", - emit.lowered(), - "mov qword ptr [rbp - 8], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.word_ptr, .{ - .disp = @bitCast(u32, @as(i32, -2)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x66\xC7\x45\xFE\x10\x00", emit.lowered(), "mov word ptr [rbp - 2], 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = @bitCast(u32, @as(i32, -1)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\xC6\x45\xFF\x10", emit.lowered(), "mov byte ptr [rbp - 1], 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .scale_index = .{ - .scale = 1, - .index = .rcx, - }, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "mov qword ptr [rcx*2 + 0x10000000], 0x10", - ); - - try lowerToMiImm8Enc(.add, RegisterOrMemory.reg(.rax), 0x10, emit.code()); - try expectEqualHexStrings("\x48\x83\xC0\x10", emit.lowered(), "add rax, 0x10"); -} - -test "lower RM encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), emit.code()); - try expectEqualHexStrings("\x48\x8b\xc3", emit.lowered(), "mov rax, rbx"); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0, .base = .r11 }), emit.code()); - try expectEqualHexStrings("\x49\x8b\x03", emit.lowered(), "mov rax, qword ptr [r11 + 0]"); - try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10000000 }), emit.code()); - try expectEqualHexStrings( - "\x4C\x03\x1C\x25\x00\x00\x00\x10", - emit.lowered(), - "add r11, qword ptr [ds:0x10000000]", - ); - try lowerToRmEnc(.add, .r12b, RegisterOrMemory.mem(.byte_ptr, .{ .disp = 0x10000000 }), emit.code()); - try expectEqualHexStrings( - "\x44\x02\x24\x25\x00\x00\x00\x10", - emit.lowered(), - "add r11b, byte ptr [ds:0x10000000]", - ); - try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .base = .r13, - }), emit.code()); - try expectEqualHexStrings( - "\x4D\x2B\x9D\x00\x00\x00\x10", - emit.lowered(), - "sub r11, qword ptr [r13 + 0x10000000]", - ); - try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .base = .r12, - }), emit.code()); - try expectEqualHexStrings( - "\x4D\x2B\x9C\x24\x00\x00\x00\x10", - emit.lowered(), - "sub r11, qword ptr [r12 + 0x10000000]", - ); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - }), emit.code()); - try expectEqualHexStrings("\x48\x8B\x45\xFC", emit.lowered(), "mov rax, qword ptr [rbp - 4]"); - try lowerToRmEnc(.lea, .rax, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code()); - try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", emit.lowered(), "lea rax, [rip + 0x10]"); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - .scale_index = .{ - .scale = 0, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x48\x8B\x44\x0D\xF8", emit.lowered(), "mov rax, qword ptr [rbp + rcx*1 - 8]"); - try lowerToRmEnc(.mov, .eax, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - .scale_index = .{ - .scale = 2, - .index = .rdx, - }, - }), emit.code()); - try expectEqualHexStrings("\x8B\x44\x95\xFC", emit.lowered(), "mov eax, dword ptr [rbp + rdx*4 - 4]"); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - .scale_index = .{ - .scale = 3, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x48\x8B\x44\xCD\xF8", emit.lowered(), "mov rax, qword ptr [rbp + rcx*8 - 8]"); - try lowerToRmEnc(.mov, .r8b, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = @bitCast(u32, @as(i32, -24)), - .base = .rsi, - .scale_index = .{ - .scale = 0, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x44\x8A\x44\x0E\xE8", emit.lowered(), "mov r8b, byte ptr [rsi + rcx*1 - 24]"); - try lowerToRmEnc(.lea, .rsi, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0, - .base = .rbp, - .scale_index = .{ - .scale = 0, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x48\x8D\x74\x0D\x00", emit.lowered(), "lea rsi, qword ptr [rbp + rcx*1 + 0]"); -} - -test "lower MR encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, emit.code()); - try expectEqualHexStrings("\x48\x89\xd8", emit.lowered(), "mov rax, rbx"); - try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - }), .r11, emit.code()); - try expectEqualHexStrings("\x4c\x89\x5d\xfc", emit.lowered(), "mov qword ptr [rbp - 4], r11"); - try lowerToMrEnc(.add, RegisterOrMemory.mem(.byte_ptr, .{ .disp = 0x10000000 }), .r12b, emit.code()); - try expectEqualHexStrings( - "\x44\x00\x24\x25\x00\x00\x00\x10", - emit.lowered(), - "add byte ptr [ds:0x10000000], r12b", - ); - try lowerToMrEnc(.add, RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0x10000000 }), .r12d, emit.code()); - try expectEqualHexStrings( - "\x44\x01\x24\x25\x00\x00\x00\x10", - emit.lowered(), - "add dword ptr [ds:0x10000000], r12d", - ); - try lowerToMrEnc(.sub, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .base = .r11, - }), .r12, emit.code()); - try expectEqualHexStrings( - "\x4D\x29\xA3\x00\x00\x00\x10", - emit.lowered(), - "sub qword ptr [r11 + 0x10000000], r12", - ); - try lowerToMrEnc(.mov, RegisterOrMemory.rip(.qword_ptr, 0x10), .r12, emit.code()); - try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", emit.lowered(), "mov qword ptr [rip + 0x10], r12"); -} - -test "lower OI encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToOiEnc(.mov, .rax, 0x1000000000000000, emit.code()); - try expectEqualHexStrings( - "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", - emit.lowered(), - "movabs rax, 0x1000000000000000", - ); - try lowerToOiEnc(.mov, .r11, 0x1000000000000000, emit.code()); - try expectEqualHexStrings( - "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", - emit.lowered(), - "movabs r11, 0x1000000000000000", - ); - try lowerToOiEnc(.mov, .r11d, 0x10000000, emit.code()); - try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", emit.lowered(), "mov r11d, 0x10000000"); - try lowerToOiEnc(.mov, .r11w, 0x1000, emit.code()); - try expectEqualHexStrings("\x66\x41\xBB\x00\x10", emit.lowered(), "mov r11w, 0x1000"); - try lowerToOiEnc(.mov, .r11b, 0x10, emit.code()); - try expectEqualHexStrings("\x41\xB3\x10", emit.lowered(), "mov r11b, 0x10"); -} - -test "lower FD/TD encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToFdEnc(.mov, .rax, 0x1000000000000000, emit.code()); - try expectEqualHexStrings( - "\x48\xa1\x00\x00\x00\x00\x00\x00\x00\x10", - emit.lowered(), - "mov rax, ds:0x1000000000000000", - ); - try lowerToFdEnc(.mov, .eax, 0x10000000, emit.code()); - try expectEqualHexStrings("\xa1\x00\x00\x00\x10", emit.lowered(), "mov eax, ds:0x10000000"); - try lowerToFdEnc(.mov, .ax, 0x1000, emit.code()); - try expectEqualHexStrings("\x66\xa1\x00\x10", emit.lowered(), "mov ax, ds:0x1000"); - try lowerToFdEnc(.mov, .al, 0x10, emit.code()); - try expectEqualHexStrings("\xa0\x10", emit.lowered(), "mov al, ds:0x10"); -} - -test "lower M encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12), emit.code()); - try expectEqualHexStrings("\x41\xFF\xE4", emit.lowered(), "jmp r12"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12w), emit.code()); - try expectEqualHexStrings("\x66\x41\xFF\xE4", emit.lowered(), "jmp r12w"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0, .base = .r12 }), emit.code()); - try expectEqualHexStrings("\x41\xFF\x24\x24", emit.lowered(), "jmp qword ptr [r12]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.word_ptr, .{ .disp = 0, .base = .r12 }), emit.code()); - try expectEqualHexStrings("\x66\x41\xFF\x24\x24", emit.lowered(), "jmp word ptr [r12]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10, .base = .r12 }), emit.code()); - try expectEqualHexStrings("\x41\xFF\x64\x24\x10", emit.lowered(), "jmp qword ptr [r12 + 0x10]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x1000, - .base = .r12, - }), emit.code()); - try expectEqualHexStrings( - "\x41\xFF\xA4\x24\x00\x10\x00\x00", - emit.lowered(), - "jmp qword ptr [r12 + 0x1000]", - ); - try lowerToMEnc(.jmp_near, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code()); - try expectEqualHexStrings("\xFF\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [rip + 0x10]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10 }), emit.code()); - try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [ds:0x10]"); - try lowerToMEnc(.seta, RegisterOrMemory.reg(.r11b), emit.code()); - try expectEqualHexStrings("\x41\x0F\x97\xC3", emit.lowered(), "seta r11b"); - try lowerToMEnc(.idiv, RegisterOrMemory.reg(.rax), emit.code()); - try expectEqualHexStrings("\x48\xF7\xF8", emit.lowered(), "idiv rax"); - try lowerToMEnc(.imul, RegisterOrMemory.reg(.al), emit.code()); - try expectEqualHexStrings("\xF6\xE8", emit.lowered(), "imul al"); -} - -test "lower M1 and MC encodings" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12), emit.code()); - try expectEqualHexStrings("\x49\xD1\xE4", emit.lowered(), "sal r12, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12d), emit.code()); - try expectEqualHexStrings("\x41\xD1\xE4", emit.lowered(), "sal r12d, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12w), emit.code()); - try expectEqualHexStrings("\x66\x41\xD1\xE4", emit.lowered(), "sal r12w, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12b), emit.code()); - try expectEqualHexStrings("\x41\xD0\xE4", emit.lowered(), "sal r12b, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.rax), emit.code()); - try expectEqualHexStrings("\x48\xD1\xE0", emit.lowered(), "sal rax, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.eax), emit.code()); - try expectEqualHexStrings("\xD1\xE0", emit.lowered(), "sal eax, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -0x10)), - .base = .rbp, - }), emit.code()); - try expectEqualHexStrings("\x48\xD1\x65\xF0", emit.lowered(), "sal qword ptr [rbp - 0x10], 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -0x10)), - .base = .rbp, - }), emit.code()); - try expectEqualHexStrings("\xD1\x65\xF0", emit.lowered(), "sal dword ptr [rbp - 0x10], 1"); - - try lowerToMcEnc(.shr, RegisterOrMemory.reg(.r12), emit.code()); - try expectEqualHexStrings("\x49\xD3\xEC", emit.lowered(), "shr r12, cl"); - try lowerToMcEnc(.shr, RegisterOrMemory.reg(.rax), emit.code()); - try expectEqualHexStrings("\x48\xD3\xE8", emit.lowered(), "shr rax, cl"); - - try lowerToMcEnc(.sar, RegisterOrMemory.reg(.rsi), emit.code()); - try expectEqualHexStrings("\x48\xD3\xFE", emit.lowered(), "sar rsi, cl"); -} - -test "lower O encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToOEnc(.pop, .r12, emit.code()); - try expectEqualHexStrings("\x41\x5c", emit.lowered(), "pop r12"); - try lowerToOEnc(.push, .r12w, emit.code()); - try expectEqualHexStrings("\x66\x41\x54", emit.lowered(), "push r12w"); -} - -test "lower RMI encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToRmiEnc(.imul, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\x69\x45\xF8\x10\x00\x00\x00", - emit.lowered(), - "imul rax, qword ptr [rbp - 8], 0x10", - ); - try lowerToRmiEnc(.imul, .eax, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x69\x45\xFC\x10\x00\x00\x00", emit.lowered(), "imul eax, dword ptr [rbp - 4], 0x10"); - try lowerToRmiEnc(.imul, .ax, RegisterOrMemory.mem(.word_ptr, .{ - .disp = @bitCast(u32, @as(i32, -2)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x66\x69\x45\xFE\x10\x00", emit.lowered(), "imul ax, word ptr [rbp - 2], 0x10"); - try lowerToRmiEnc(.imul, .r12, RegisterOrMemory.reg(.r12), 0x10, emit.code()); - try expectEqualHexStrings("\x4D\x69\xE4\x10\x00\x00\x00", emit.lowered(), "imul r12, r12, 0x10"); - try lowerToRmiEnc(.imul, .r12w, RegisterOrMemory.reg(.r12w), 0x10, emit.code()); - try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", emit.lowered(), "imul r12w, r12w, 0x10"); -} - -test "lower MV encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMvEnc(.vmovsd, RegisterOrMemory.rip(.qword_ptr, 0x10), .xmm1, emit.code()); - try expectEqualHexStrings( - "\xC5\xFB\x11\x0D\x10\x00\x00\x00", - emit.lowered(), - "vmovsd qword ptr [rip + 0x10], xmm1", - ); -} - -test "lower VM encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToVmEnc(.vmovsd, .xmm1, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code()); - try expectEqualHexStrings( - "\xC5\xFB\x10\x0D\x10\x00\x00\x00", - emit.lowered(), - "vmovsd xmm1, qword ptr [rip + 0x10]", - ); -} - -test "lower to RVM encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToRvmEnc(.vaddsd, .xmm0, .xmm1, RegisterOrMemory.reg(.xmm2), emit.code()); - try expectEqualHexStrings("\xC5\xF3\x58\xC2", emit.lowered(), "vaddsd xmm0, xmm1, xmm2"); - try lowerToRvmEnc(.vaddsd, .xmm0, .xmm0, RegisterOrMemory.reg(.xmm1), emit.code()); - try expectEqualHexStrings("\xC5\xFB\x58\xC1", emit.lowered(), "vaddsd xmm0, xmm0, xmm1"); -} diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig new file mode 100644 index 0000000000..2cccded7ec --- /dev/null +++ b/src/arch/x86_64/Encoding.zig @@ -0,0 +1,521 @@ +const Encoding = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const math = std.math; + +const bits = @import("bits.zig"); +const encoder = @import("encoder.zig"); +const Instruction = encoder.Instruction; +const Register = bits.Register; +const Rex = encoder.Rex; +const LegacyPrefixes = encoder.LegacyPrefixes; + +const table = @import("encodings.zig").table; + +mnemonic: Mnemonic, +op_en: OpEn, +op1: Op, +op2: Op, +op3: Op, +op4: Op, +opc_len: u2, +opc: [3]u8, +modrm_ext: u3, +mode: Mode, + +pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { + op1: Instruction.Operand, + op2: Instruction.Operand, + op3: Instruction.Operand, + op4: Instruction.Operand, +}) ?Encoding { + const input_op1 = Op.fromOperand(args.op1); + const input_op2 = Op.fromOperand(args.op2); + const input_op3 = Op.fromOperand(args.op3); + const input_op4 = Op.fromOperand(args.op4); + + // TODO work out what is the maximum number of variants we can actually find in one swoop. + var candidates: [10]Encoding = undefined; + var count: usize = 0; + inline for (table) |entry| { + const enc = Encoding{ + .mnemonic = entry[0], + .op_en = entry[1], + .op1 = entry[2], + .op2 = entry[3], + .op3 = entry[4], + .op4 = entry[5], + .opc_len = entry[6], + .opc = .{ entry[7], entry[8], entry[9] }, + .modrm_ext = entry[10], + .mode = entry[11], + }; + if (enc.mnemonic == mnemonic and + input_op1.isSubset(enc.op1, enc.mode) and + input_op2.isSubset(enc.op2, enc.mode) and + input_op3.isSubset(enc.op3, enc.mode) and + input_op4.isSubset(enc.op4, enc.mode)) + { + candidates[count] = enc; + count += 1; + } + } + + if (count == 0) return null; + if (count == 1) return candidates[0]; + + const EncodingLength = struct { + fn estimate(encoding: Encoding, params: struct { + op1: Instruction.Operand, + op2: Instruction.Operand, + op3: Instruction.Operand, + op4: Instruction.Operand, + }) usize { + var inst = Instruction{ + .op1 = params.op1, + .op2 = params.op2, + .op3 = params.op3, + .op4 = params.op4, + .encoding = encoding, + }; + var cwriter = std.io.countingWriter(std.io.null_writer); + inst.encode(cwriter.writer()) catch unreachable; + return cwriter.bytes_written; + } + }; + + var shortest_encoding: ?struct { + index: usize, + len: usize, + } = null; + var i: usize = 0; + while (i < count) : (i += 1) { + const len = EncodingLength.estimate(candidates[i], .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }); + const current = shortest_encoding orelse { + shortest_encoding = .{ .index = i, .len = len }; + continue; + }; + if (len < current.len) { + shortest_encoding = .{ .index = i, .len = len }; + } + } + + return candidates[shortest_encoding.?.index]; +} + +/// Returns first matching encoding by opcode. +pub fn findByOpcode(opc: []const u8, prefixes: struct { + legacy: LegacyPrefixes, + rex: Rex, +}, modrm_ext: ?u3) ?Encoding { + inline for (table) |entry| { + const enc = Encoding{ + .mnemonic = entry[0], + .op_en = entry[1], + .op1 = entry[2], + .op2 = entry[3], + .op3 = entry[4], + .op4 = entry[5], + .opc_len = entry[6], + .opc = .{ entry[7], entry[8], entry[9] }, + .modrm_ext = entry[10], + .mode = entry[11], + }; + const match = match: { + if (modrm_ext) |ext| { + break :match ext == enc.modrm_ext and std.mem.eql(u8, enc.opcode(), opc); + } + break :match std.mem.eql(u8, enc.opcode(), opc); + }; + if (match) { + if (prefixes.rex.w) { + switch (enc.mode) { + .fpu, .sse, .sse2 => {}, + .long => return enc, + .none => { + // TODO this is a hack to allow parsing of instructions which contain + // spurious prefix bytes such as + // rex.W mov dil, 0x1 + // Here, rex.W is not needed. + const rex_w_allowed = blk: { + const bit_size = enc.operandSize(); + break :blk bit_size == 64 or bit_size == 8; + }; + if (rex_w_allowed) return enc; + }, + } + } else if (prefixes.legacy.prefix_66) { + switch (enc.operandSize()) { + 16 => return enc, + else => {}, + } + } else { + if (enc.mode == .none) { + switch (enc.operandSize()) { + 16 => {}, + else => return enc, + } + } + } + } + } + return null; +} + +pub fn opcode(encoding: *const Encoding) []const u8 { + return encoding.opc[0..encoding.opc_len]; +} + +pub fn mandatoryPrefix(encoding: *const Encoding) ?u8 { + const prefix = encoding.opc[0]; + return switch (prefix) { + 0x66, 0xf2, 0xf3 => prefix, + else => null, + }; +} + +pub fn modRmExt(encoding: Encoding) u3 { + return switch (encoding.op_en) { + .m, .mi, .m1, .mc => encoding.modrm_ext, + else => unreachable, + }; +} + +pub fn operandSize(encoding: Encoding) u32 { + if (encoding.mode == .long) return 64; + const bit_size: u32 = switch (encoding.op_en) { + .np => switch (encoding.op1) { + .o16 => 16, + .o32 => 32, + .o64 => 64, + else => 32, + }, + .td => encoding.op2.size(), + else => encoding.op1.size(), + }; + return bit_size; +} + +pub fn format( + encoding: Encoding, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = options; + _ = fmt; + switch (encoding.mode) { + .long => try writer.writeAll("REX.W + "), + else => {}, + } + + for (encoding.opcode()) |byte| { + try writer.print("{x:0>2} ", .{byte}); + } + + switch (encoding.op_en) { + .np, .fd, .td, .i, .zi, .d => {}, + .o, .oi => { + const tag = switch (encoding.op1) { + .r8 => "rb", + .r16 => "rw", + .r32 => "rd", + .r64 => "rd", + else => unreachable, + }; + try writer.print("+{s} ", .{tag}); + }, + .m, .mi, .m1, .mc => try writer.print("/{d} ", .{encoding.modRmExt()}), + .mr, .rm, .rmi => try writer.writeAll("/r "), + } + + switch (encoding.op_en) { + .i, .d, .zi, .oi, .mi, .rmi => { + const op = switch (encoding.op_en) { + .i, .d => encoding.op1, + .zi, .oi, .mi => encoding.op2, + .rmi => encoding.op3, + else => unreachable, + }; + const tag = switch (op) { + .imm8 => "ib", + .imm16 => "iw", + .imm32 => "id", + .imm64 => "io", + .rel8 => "cb", + .rel16 => "cw", + .rel32 => "cd", + else => unreachable, + }; + try writer.print("{s} ", .{tag}); + }, + .np, .fd, .td, .o, .m, .m1, .mc, .mr, .rm => {}, + } + + try writer.print("{s} ", .{@tagName(encoding.mnemonic)}); + + const ops = &[_]Op{ encoding.op1, encoding.op2, encoding.op3, encoding.op4 }; + for (ops) |op| switch (op) { + .none, .o16, .o32, .o64 => break, + else => try writer.print("{s} ", .{@tagName(op)}), + }; + + const op_en = switch (encoding.op_en) { + .zi => .i, + else => |op_en| op_en, + }; + try writer.print("{s}", .{@tagName(op_en)}); +} + +pub const Mnemonic = enum { + // zig fmt: off + // General-purpose + adc, add, @"and", + call, cbw, cwde, cdqe, cwd, cdq, cqo, cmp, + cmova, cmovae, cmovb, cmovbe, cmovc, cmove, cmovg, cmovge, cmovl, cmovle, cmovna, + cmovnae, cmovnb, cmovnbe, cmovnc, cmovne, cmovng, cmovnge, cmovnl, cmovnle, cmovno, + cmovnp, cmovns, cmovnz, cmovo, cmovp, cmovpe, cmovpo, cmovs, cmovz, + div, + fisttp, fld, + idiv, imul, int3, + ja, jae, jb, jbe, jc, jrcxz, je, jg, jge, jl, jle, jna, jnae, jnb, jnbe, + jnc, jne, jng, jnge, jnl, jnle, jno, jnp, jns, jnz, jo, jp, jpe, jpo, js, jz, + jmp, + lea, + mov, movsx, movsxd, movzx, mul, + nop, + @"or", + pop, push, + ret, + sal, sar, sbb, shl, shr, sub, syscall, + seta, setae, setb, setbe, setc, sete, setg, setge, setl, setle, setna, setnae, + setnb, setnbe, setnc, setne, setng, setnge, setnl, setnle, setno, setnp, setns, + setnz, seto, setp, setpe, setpo, sets, setz, + @"test", + ud2, + xor, + // SSE + addss, + cmpss, + movss, + ucomiss, + // SSE2 + addsd, + cmpsd, + movq, movsd, + ucomisd, + // zig fmt: on +}; + +pub const OpEn = enum { + // zig fmt: off + np, + o, oi, + i, zi, + d, m, + fd, td, + m1, mc, mi, mr, rm, rmi, + // zig fmt: on +}; + +pub const Op = enum { + // zig fmt: off + none, + o16, o32, o64, + unity, + imm8, imm16, imm32, imm64, + al, ax, eax, rax, + cl, + r8, r16, r32, r64, + rm8, rm16, rm32, rm64, + m8, m16, m32, m64, m80, + rel8, rel16, rel32, + m, + moffs, + sreg, + xmm, xmm_m32, xmm_m64, + // zig fmt: on + + pub fn fromOperand(operand: Instruction.Operand) Op { + switch (operand) { + .none => return .none, + + .reg => |reg| { + switch (reg.class()) { + .segment => return .sreg, + .floating_point => return switch (reg.size()) { + 128 => .xmm, + else => unreachable, + }, + .general_purpose => { + if (reg.to64() == .rax) return switch (reg) { + .al => .al, + .ax => .ax, + .eax => .eax, + .rax => .rax, + else => unreachable, + }; + if (reg == .cl) return .cl; + return switch (reg.size()) { + 8 => .r8, + 16 => .r16, + 32 => .r32, + 64 => .r64, + else => unreachable, + }; + }, + } + }, + + .mem => |mem| switch (mem) { + .moffs => return .moffs, + .sib, .rip => { + const bit_size = mem.size(); + return switch (bit_size) { + 8 => .m8, + 16 => .m16, + 32 => .m32, + 64 => .m64, + 80 => .m80, + else => unreachable, + }; + }, + }, + + .imm => |imm| { + if (imm == 1) return .unity; + if (math.cast(i8, imm)) |_| return .imm8; + if (math.cast(i16, imm)) |_| return .imm16; + if (math.cast(i32, imm)) |_| return .imm32; + return .imm64; + }, + } + } + + pub fn size(op: Op) u32 { + return switch (op) { + .none, .o16, .o32, .o64, .moffs, .m, .sreg, .unity => unreachable, + .imm8, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, + .imm16, .ax, .r16, .m16, .rm16, .rel16 => 16, + .imm32, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, + .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64, + .m80 => 80, + .xmm => 128, + }; + } + + pub fn isRegister(op: Op) bool { + // zig fmt: off + return switch (op) { + .cl, + .al, .ax, .eax, .rax, + .r8, .r16, .r32, .r64, + .rm8, .rm16, .rm32, .rm64, + .xmm, .xmm_m32, .xmm_m64, + => true, + else => false, + }; + // zig fmt: on + } + + pub fn isImmediate(op: Op) bool { + // zig fmt: off + return switch (op) { + .imm8, .imm16, .imm32, .imm64, + .rel8, .rel16, .rel32, + .unity, + => true, + else => false, + }; + // zig fmt: on + } + + pub fn isMemory(op: Op) bool { + // zig fmt: off + return switch (op) { + .rm8, .rm16, .rm32, .rm64, + .m8, .m16, .m32, .m64, .m80, + .m, + .xmm_m32, .xmm_m64, + => true, + else => false, + }; + // zig fmt: on + } + + pub fn isSegmentRegister(op: Op) bool { + return switch (op) { + .moffs, .sreg => true, + else => false, + }; + } + + pub fn isFloatingPointRegister(op: Op) bool { + return switch (op) { + .xmm, .xmm_m32, .xmm_m64 => true, + else => false, + }; + } + + /// Given an operand `op` checks if `target` is a subset for the purposes + /// of the encoding. + pub fn isSubset(op: Op, target: Op, mode: Mode) bool { + switch (op) { + .m, .o16, .o32, .o64 => unreachable, + .moffs, .sreg => return op == target, + .none => switch (target) { + .o16, .o32, .o64, .none => return true, + else => return false, + }, + else => { + if (op.isRegister() and target.isRegister()) { + switch (mode) { + .sse, .sse2 => return op.isFloatingPointRegister() and target.isFloatingPointRegister(), + else => switch (target) { + .cl, .al, .ax, .eax, .rax => return op == target, + else => return op.size() == target.size(), + }, + } + } + if (op.isMemory() and target.isMemory()) { + switch (target) { + .m => return true, + else => return op.size() == target.size(), + } + } + if (op.isImmediate() and target.isImmediate()) { + switch (target) { + .imm32, .rel32 => switch (op) { + .unity, .imm8, .imm16, .imm32 => return true, + else => return op == target, + }, + .imm16, .rel16 => switch (op) { + .unity, .imm8, .imm16 => return true, + else => return op == target, + }, + .imm8, .rel8 => switch (op) { + .unity, .imm8 => return true, + else => return op == target, + }, + else => return op == target, + } + } + return false; + }, + } + } +}; + +pub const Mode = enum { + none, + fpu, + long, + sse, + sse2, +}; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index ba71f4cddd..b3be08e86b 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -339,41 +339,23 @@ pub const Inst = struct { /// Nop nop, - /// SSE instructions + /// SSE/AVX instructions /// ops flags: form: /// 0b00 reg1, qword ptr [reg2 + imm32] /// 0b01 qword ptr [reg1 + imm32], reg2 /// 0b10 reg1, reg2 - mov_f64_sse, - mov_f32_sse, + mov_f64, + mov_f32, /// ops flags: form: /// 0b00 reg1, reg2 - add_f64_sse, - add_f32_sse, + add_f64, + add_f32, /// ops flags: form: /// 0b00 reg1, reg2 - cmp_f64_sse, - cmp_f32_sse, - - /// AVX instructions - /// ops flags: form: - /// 0b00 reg1, qword ptr [reg2 + imm32] - /// 0b01 qword ptr [reg1 + imm32], reg2 - /// 0b10 reg1, reg1, reg2 - mov_f64_avx, - mov_f32_avx, - - /// ops flags: form: - /// 0b00 reg1, reg1, reg2 - add_f64_avx, - add_f32_avx, - - /// ops flags: form: - /// 0b00 reg1, reg1, reg2 - cmp_f64_avx, - cmp_f32_avx, + cmp_f64, + cmp_f32, /// Pseudo-instructions /// call extern function @@ -439,6 +421,8 @@ pub const Inst = struct { inst: Index, /// A 32-bit immediate value. imm: u32, + /// A 32-bit signed displacement value. + disp: i32, /// A condition code for use with EFLAGS register. cc: bits.Condition, /// Another instruction with condition code. @@ -476,9 +460,9 @@ pub const IndexRegisterDisp = struct { index: u32, /// Displacement value - disp: u32, + disp: i32, - pub fn encode(index: Register, disp: u32) IndexRegisterDisp { + pub fn encode(index: Register, disp: i32) IndexRegisterDisp { return .{ .index = @enumToInt(index), .disp = disp, @@ -487,7 +471,7 @@ pub const IndexRegisterDisp = struct { pub fn decode(this: IndexRegisterDisp) struct { index: Register, - disp: u32, + disp: i32, } { return .{ .index = @intToEnum(Register, this.index), @@ -503,12 +487,12 @@ pub const IndexRegisterDispImm = struct { index: u32, /// Displacement value - disp: u32, + disp: i32, /// Immediate imm: u32, - pub fn encode(index: Register, disp: u32, imm: u32) IndexRegisterDispImm { + pub fn encode(index: Register, disp: i32, imm: u32) IndexRegisterDispImm { return .{ .index = @enumToInt(index), .disp = disp, @@ -518,7 +502,7 @@ pub const IndexRegisterDispImm = struct { pub fn decode(this: IndexRegisterDispImm) struct { index: Register, - disp: u32, + disp: i32, imm: u32, } { return .{ @@ -576,7 +560,7 @@ pub const SaveRegisterList = struct { }; pub const ImmPair = struct { - dest_off: u32, + dest_off: i32, operand: u32, }; diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index cc123b96b6..9166550f16 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -1,9 +1,9 @@ const std = @import("std"); -const testing = std.testing; -const mem = std.mem; const assert = std.debug.assert; -const ArrayList = std.ArrayList; +const expect = std.testing.expect; + const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; const DW = std.dwarf; /// EFLAGS condition codes @@ -135,960 +135,357 @@ pub const Condition = enum(u5) { } }; -/// Definitions of all of the general purpose x64 registers. The order is semantically meaningful. -/// The registers are defined such that IDs go in descending order of 64-bit, -/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen -/// registers. This results in some useful properties: -/// -/// Any 64-bit register can be turned into its 32-bit form by adding 16, and -/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it -/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit -/// form. -/// -/// If (register & 8) is set, the register is extended. -/// -/// The ID can be easily determined by figuring out what range the register is -/// in, and then subtracting the base. pub const Register = enum(u7) { // zig fmt: off - // 0 through 15, 64-bit registers. 8-15 are extended. - // id is just the int value. rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15, - // 16 through 31, 32-bit registers. 24-31 are extended. - // id is int value - 16. eax, ecx, edx, ebx, esp, ebp, esi, edi, r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d, - // 32-47, 16-bit registers. 40-47 are extended. - // id is int value - 32. ax, cx, dx, bx, sp, bp, si, di, r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w, - // 48-63, 8-bit registers. 56-63 are extended. - // id is int value - 48. - al, cl, dl, bl, ah, ch, dh, bh, + al, cl, dl, bl, spl, bpl, sil, dil, r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b, - // 64-79, 256-bit registers. - // id is int value - 64. + ah, ch, dh, bh, + ymm0, ymm1, ymm2, ymm3, ymm4, ymm5, ymm6, ymm7, ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15, - // 80-95, 128-bit registers. - // id is int value - 80. xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, - // Pseudo-value for MIR instructions. + es, cs, ss, ds, fs, gs, + none, // zig fmt: on - pub fn id(self: Register) u7 { - return switch (@enumToInt(self)) { - 0...63 => @as(u7, @truncate(u4, @enumToInt(self))), - 64...79 => @enumToInt(self), + pub const Class = enum(u2) { + general_purpose, + floating_point, + segment, + }; + + pub fn class(reg: Register) Class { + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => .general_purpose, + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => .general_purpose, + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => .general_purpose, + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => .general_purpose, + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => .general_purpose, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => .floating_point, + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => .floating_point, + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => .segment, + + else => unreachable, + // zig fmt: on + }; + } + + pub fn id(reg: Register) u6 { + const base = switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => @enumToInt(Register.rax), + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => @enumToInt(Register.eax), + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => @enumToInt(Register.ax), + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => @enumToInt(Register.al), + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => @enumToInt(Register.ah) - 4, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => @enumToInt(Register.ymm0) - 16, + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => @enumToInt(Register.xmm0) - 16, + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => @enumToInt(Register.es) - 32, + + else => unreachable, + // zig fmt: on + }; + return @intCast(u6, @enumToInt(reg) - base); + } + + pub fn size(reg: Register) u32 { + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => 64, + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => 32, + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => 16, + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => 8, + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => 8, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => 256, + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => 128, + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => 16, + + else => unreachable, + // zig fmt: on + }; + } + + pub fn isExtended(reg: Register) bool { + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.r8) ... @enumToInt(Register.r15) => true, + @enumToInt(Register.r8d) ... @enumToInt(Register.r15d) => true, + @enumToInt(Register.r8w) ... @enumToInt(Register.r15w) => true, + @enumToInt(Register.r8b) ... @enumToInt(Register.r15b) => true, + + @enumToInt(Register.ymm8) ... @enumToInt(Register.ymm15) => true, + @enumToInt(Register.xmm8) ... @enumToInt(Register.xmm15) => true, + + else => false, + // zig fmt: on + }; + } + + pub fn isRexInvalid(reg: Register) bool { + return switch (@enumToInt(reg)) { + @enumToInt(Register.ah)...@enumToInt(Register.bh) => true, + else => false, + }; + } + + pub fn enc(reg: Register) u4 { + const base = switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => @enumToInt(Register.rax), + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => @enumToInt(Register.eax), + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => @enumToInt(Register.ax), + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => @enumToInt(Register.al), + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => @enumToInt(Register.ah) - 4, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => @enumToInt(Register.ymm0), + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => @enumToInt(Register.xmm0), + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => @enumToInt(Register.es), + + else => unreachable, + // zig fmt: on + }; + return @truncate(u4, @enumToInt(reg) - base); + } + + pub fn lowEnc(reg: Register) u3 { + return @truncate(u3, reg.enc()); + } + + pub fn toSize(reg: Register, bit_size: u32) Register { + return switch (bit_size) { + 8 => reg.to8(), + 16 => reg.to16(), + 32 => reg.to32(), + 64 => reg.to64(), + 128 => reg.to128(), + 256 => reg.to256(), else => unreachable, }; } - /// Returns the bit-width of the register. - pub fn size(self: Register) u9 { - return switch (@enumToInt(self)) { - 0...15 => 64, - 16...31 => 32, - 32...47 => 16, - 48...63 => 8, - 64...79 => 256, - 80...95 => 128, + fn gpBase(reg: Register) u7 { + assert(reg.class() == .general_purpose); + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => @enumToInt(Register.rax), + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => @enumToInt(Register.eax), + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => @enumToInt(Register.ax), + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => @enumToInt(Register.al), + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => @enumToInt(Register.ah) - 4, + else => unreachable, + // zig fmt: on + }; + } + + pub fn to64(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.rax)); + } + + pub fn to32(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.eax)); + } + + pub fn to16(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.ax)); + } + + pub fn to8(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.al)); + } + + fn fpBase(reg: Register) u7 { + assert(reg.class() == .floating_point); + return switch (@enumToInt(reg)) { + @enumToInt(Register.ymm0)...@enumToInt(Register.ymm15) => @enumToInt(Register.ymm0), + @enumToInt(Register.xmm0)...@enumToInt(Register.xmm15) => @enumToInt(Register.xmm0), else => unreachable, }; } - /// Returns whether the register is *extended*. Extended registers are the - /// new registers added with amd64, r8 through r15. This also includes any - /// other variant of access to those registers, such as r8b, r15d, and so - /// on. This is needed because access to these registers requires special - /// handling via the REX prefix, via the B or R bits, depending on context. - pub fn isExtended(self: Register) bool { - return @enumToInt(self) & 0x08 != 0; + pub fn to256(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.ymm0)); } - /// This returns the 4-bit register ID, which is used in practically every - /// opcode. Note that bit 3 (the highest bit) is *never* used directly in - /// an instruction (@see isExtended), and requires special handling. The - /// lower three bits are often embedded directly in instructions (such as - /// the B8 variant of moves), or used in R/M bytes. - pub fn enc(self: Register) u4 { - return @truncate(u4, @enumToInt(self)); + pub fn to128(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.xmm0)); } - /// Like enc, but only returns the lower 3 bits. - pub fn lowEnc(self: Register) u3 { - return @truncate(u3, @enumToInt(self)); - } - - pub fn to256(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 64); - } - - pub fn to128(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 80); - } - - /// Convert from any register to its 64 bit alias. - pub fn to64(self: Register) Register { - return @intToEnum(Register, self.enc()); - } - - /// Convert from any register to its 32 bit alias. - pub fn to32(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 16); - } - - /// Convert from any register to its 16 bit alias. - pub fn to16(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 32); - } - - /// Convert from any register to its 8 bit alias. - pub fn to8(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 48); - } - - pub fn dwarfLocOp(self: Register) u8 { - switch (@enumToInt(self)) { - 0...63 => return switch (self.to64()) { - .rax => DW.OP.reg0, - .rdx => DW.OP.reg1, - .rcx => DW.OP.reg2, - .rbx => DW.OP.reg3, - .rsi => DW.OP.reg4, - .rdi => DW.OP.reg5, - .rbp => DW.OP.reg6, - .rsp => DW.OP.reg7, - - .r8 => DW.OP.reg8, - .r9 => DW.OP.reg9, - .r10 => DW.OP.reg10, - .r11 => DW.OP.reg11, - .r12 => DW.OP.reg12, - .r13 => DW.OP.reg13, - .r14 => DW.OP.reg14, - .r15 => DW.OP.reg15, - - else => unreachable, - }, - - 64...79 => return @as(u8, self.enc()) + DW.OP.reg17, - + pub fn dwarfLocOp(reg: Register) u8 { + return switch (reg.class()) { + .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0, + .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.reg17, else => unreachable, - } + }; } /// DWARF encodings that push a value onto the DWARF stack that is either /// the contents of a register or the result of adding the contents a given /// register to a given signed offset. - pub fn dwarfLocOpDeref(self: Register) u8 { - switch (@enumToInt(self)) { - 0...63 => return switch (self.to64()) { - .rax => DW.OP.breg0, - .rdx => DW.OP.breg1, - .rcx => DW.OP.breg2, - .rbx => DW.OP.breg3, - .rsi => DW.OP.breg4, - .rdi => DW.OP.breg5, - .rbp => DW.OP.breg6, - .rsp => DW.OP.fbreg, - - .r8 => DW.OP.breg8, - .r9 => DW.OP.breg9, - .r10 => DW.OP.breg10, - .r11 => DW.OP.breg11, - .r12 => DW.OP.breg12, - .r13 => DW.OP.breg13, - .r14 => DW.OP.breg14, - .r15 => DW.OP.breg15, - - else => unreachable, - }, - - 64...79 => return @as(u8, self.enc()) + DW.OP.breg17, - + pub fn dwarfLocOpDeref(reg: Register) u8 { + return switch (reg.class()) { + .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0, + .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.breg17, else => unreachable, - } + }; } }; -// zig fmt: on +test "Register id - different classes" { + try expect(Register.al.id() == Register.ax.id()); + try expect(Register.ah.id() == Register.spl.id()); + try expect(Register.ax.id() == Register.eax.id()); + try expect(Register.eax.id() == Register.rax.id()); -/// Encoding helper functions for x86_64 instructions -/// -/// Many of these helpers do very little, but they can help make things -/// slightly more readable with more descriptive field names / function names. -/// -/// Some of them also have asserts to ensure that we aren't doing dumb things. -/// For example, trying to use register 4 (esp) in an indirect modr/m byte is illegal, -/// you need to encode it with an SIB byte. -/// -/// Note that ALL of these helper functions will assume capacity, -/// so ensure that the `code` has sufficient capacity before using them. -/// The `init` method is the recommended way to ensure capacity. -pub const Encoder = struct { - /// Non-owning reference to the code array - code: *ArrayList(u8), + try expect(Register.ymm0.id() == 0b10000); + try expect(Register.ymm0.id() != Register.rax.id()); + try expect(Register.xmm0.id() == Register.ymm0.id()); - const Self = @This(); + try expect(Register.es.id() == 0b100000); +} - /// Wrap `code` in Encoder to make it easier to call these helper functions - /// - /// maximum_inst_size should contain the maximum number of bytes - /// that the encoded instruction will take. - /// This is because the helper functions will assume capacity - /// in order to avoid bounds checking. - pub fn init(code: *ArrayList(u8), maximum_inst_size: u8) !Self { - try code.ensureUnusedCapacity(maximum_inst_size); - return Self{ .code = code }; - } +test "Register enc - different classes" { + try expect(Register.al.enc() == Register.ax.enc()); + try expect(Register.ax.enc() == Register.eax.enc()); + try expect(Register.eax.enc() == Register.rax.enc()); + try expect(Register.ymm0.enc() == Register.rax.enc()); + try expect(Register.xmm0.enc() == Register.ymm0.enc()); + try expect(Register.es.enc() == Register.rax.enc()); +} - /// Directly write a number to the code array with big endianness - pub fn writeIntBig(self: Self, comptime T: type, value: T) void { - mem.writeIntBig( - T, - self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), - value, - ); - } +test "Register classes" { + try expect(Register.r11.class() == .general_purpose); + try expect(Register.ymm11.class() == .floating_point); + try expect(Register.fs.class() == .segment); +} - /// Directly write a number to the code array with little endianness - pub fn writeIntLittle(self: Self, comptime T: type, value: T) void { - mem.writeIntLittle( - T, - self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), - value, - ); - } +pub const Memory = union(enum) { + sib: Sib, + rip: Rip, + moffs: Moffs, - // -------- - // Prefixes - // -------- - - pub const LegacyPrefixes = packed struct { - /// LOCK - prefix_f0: bool = false, - /// REPNZ, REPNE, REP, Scalar Double-precision - prefix_f2: bool = false, - /// REPZ, REPE, REP, Scalar Single-precision - prefix_f3: bool = false, - - /// CS segment override or Branch not taken - prefix_2e: bool = false, - /// DS segment override - prefix_36: bool = false, - /// ES segment override - prefix_26: bool = false, - /// FS segment override - prefix_64: bool = false, - /// GS segment override - prefix_65: bool = false, - - /// Branch taken - prefix_3e: bool = false, - - /// Operand size override (enables 16 bit operation) - prefix_66: bool = false, - - /// Address size override (enables 16 bit address size) - prefix_67: bool = false, - - padding: u5 = 0, + pub const ScaleIndex = packed struct { + scale: u4, + index: Register, }; - /// Encodes legacy prefixes - pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) void { - if (@bitCast(u16, prefixes) != 0) { - // Hopefully this path isn't taken very often, so we'll do it the slow way for now + pub const PtrSize = enum { + byte, + word, + dword, + qword, + tbyte, - // LOCK - if (prefixes.prefix_f0) self.code.appendAssumeCapacity(0xf0); - // REPNZ, REPNE, REP, Scalar Double-precision - if (prefixes.prefix_f2) self.code.appendAssumeCapacity(0xf2); - // REPZ, REPE, REP, Scalar Single-precision - if (prefixes.prefix_f3) self.code.appendAssumeCapacity(0xf3); - - // CS segment override or Branch not taken - if (prefixes.prefix_2e) self.code.appendAssumeCapacity(0x2e); - // DS segment override - if (prefixes.prefix_36) self.code.appendAssumeCapacity(0x36); - // ES segment override - if (prefixes.prefix_26) self.code.appendAssumeCapacity(0x26); - // FS segment override - if (prefixes.prefix_64) self.code.appendAssumeCapacity(0x64); - // GS segment override - if (prefixes.prefix_65) self.code.appendAssumeCapacity(0x65); - - // Branch taken - if (prefixes.prefix_3e) self.code.appendAssumeCapacity(0x3e); - - // Operand size override - if (prefixes.prefix_66) self.code.appendAssumeCapacity(0x66); - - // Address size override - if (prefixes.prefix_67) self.code.appendAssumeCapacity(0x67); - } - } - - /// Use 16 bit operand size - /// - /// Note that this flag is overridden by REX.W, if both are present. - pub fn prefix16BitMode(self: Self) void { - self.code.appendAssumeCapacity(0x66); - } - - pub const Vex = struct { - rex_prefix: Rex = .{}, - lead_opc: u5 = 0b0_0001, - register: u4 = 0b1111, - length: u1 = 0b0, - simd_prefix: u2 = 0b00, - wig_desc: bool = false, - lig_desc: bool = false, - lz_desc: bool = false, - - pub fn rex(self: *Vex, r: Rex) void { - self.rex_prefix = r; - } - - pub fn lead_opc_0f(self: *Vex) void { - self.lead_opc = 0b0_0001; - } - - pub fn lead_opc_0f_38(self: *Vex) void { - self.lead_opc = 0b0_0010; - } - - pub fn lead_opc_0f_3a(self: *Vex) void { - self.lead_opc = 0b0_0011; - } - - pub fn reg(self: *Vex, register: u4) void { - self.register = ~register; - } - - pub fn len_128(self: *Vex) void { - self.length = 0; - } - - pub fn len_256(self: *Vex) void { - assert(!self.lz_desc); - self.length = 1; - } - - pub fn simd_prefix_66(self: *Vex) void { - self.simd_prefix = 0b01; - } - - pub fn simd_prefix_f3(self: *Vex) void { - self.simd_prefix = 0b10; - } - - pub fn simd_prefix_f2(self: *Vex) void { - self.simd_prefix = 0b11; - } - - pub fn wig(self: *Vex) void { - self.wig_desc = true; - } - - pub fn lig(self: *Vex) void { - self.lig_desc = true; - } - - pub fn lz(self: *Vex) void { - self.lz_desc = true; - } - - pub fn write(self: Vex, writer: anytype) usize { - var buf: [3]u8 = .{0} ** 3; - const form_3byte: bool = blk: { - if (self.rex_prefix.w and !self.wig_desc) break :blk true; - if (self.rex_prefix.x or self.rex_prefix.b) break :blk true; - break :blk self.lead_opc != 0b0_0001; + pub fn fromSize(bit_size: u32) PtrSize { + return switch (bit_size) { + 8 => .byte, + 16 => .word, + 32 => .dword, + 64 => .qword, + 80 => .tbyte, + else => unreachable, }; + } - if (self.lz_desc) { - assert(self.length == 0); - } - - if (form_3byte) { - // First byte - buf[0] = 0xc4; - // Second byte - const rxb_mask: u3 = @intCast(u3, @boolToInt(!self.rex_prefix.r)) << 2 | - @intCast(u2, @boolToInt(!self.rex_prefix.x)) << 1 | - @boolToInt(!self.rex_prefix.b); - buf[1] |= @intCast(u8, rxb_mask) << 5; - buf[1] |= self.lead_opc; - // Third byte - buf[2] |= @intCast(u8, @boolToInt(!self.rex_prefix.w)) << 7; - buf[2] |= @intCast(u7, self.register) << 3; - buf[2] |= @intCast(u3, self.length) << 2; - buf[2] |= self.simd_prefix; - } else { - // First byte - buf[0] = 0xc5; - // Second byte - buf[1] |= @intCast(u8, @boolToInt(!self.rex_prefix.r)) << 7; - buf[1] |= @intCast(u7, self.register) << 3; - buf[1] |= @intCast(u3, self.length) << 2; - buf[1] |= self.simd_prefix; - } - - const count: usize = if (form_3byte) 3 else 2; - _ = writer.writeAll(buf[0..count]) catch unreachable; - return count; + pub fn size(s: PtrSize) u32 { + return switch (s) { + .byte => 8, + .word => 16, + .dword => 32, + .qword => 64, + .tbyte => 80, + }; } }; - pub fn vex(self: Self, prefix: Vex) void { - _ = prefix.write(self.code.writer()); - } - - /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB - pub const Rex = struct { - /// Wide, enables 64-bit operation - w: bool = false, - /// Extends the reg field in the ModR/M byte - r: bool = false, - /// Extends the index field in the SIB byte - x: bool = false, - /// Extends the r/m field in the ModR/M byte, - /// or the base field in the SIB byte, - /// or the reg field in the Opcode byte - b: bool = false, + pub const Sib = struct { + ptr_size: PtrSize, + base: ?Register, + scale_index: ?ScaleIndex, + disp: i32, }; - /// Encodes a REX prefix byte given all the fields - /// - /// Use this byte whenever you need 64 bit operation, - /// or one of reg, index, r/m, base, or opcode-reg might be extended. - /// - /// See struct `Rex` for a description of each field. - /// - /// Does not add a prefix byte if none of the fields are set! - pub fn rex(self: Self, byte: Rex) void { - var value: u8 = 0b0100_0000; + pub const Rip = struct { + ptr_size: PtrSize, + disp: i32, + }; - if (byte.w) value |= 0b1000; - if (byte.r) value |= 0b0100; - if (byte.x) value |= 0b0010; - if (byte.b) value |= 0b0001; + pub const Moffs = struct { + seg: Register, + offset: u64, + }; - if (value != 0b0100_0000) { - self.code.appendAssumeCapacity(value); - } + pub fn moffs(reg: Register, offset: u64) Memory { + assert(reg.class() == .segment); + return .{ .moffs = .{ .seg = reg, .offset = offset } }; } - // ------ - // Opcode - // ------ - - /// Encodes a 1 byte opcode - pub fn opcode_1byte(self: Self, opcode: u8) void { - self.code.appendAssumeCapacity(opcode); + pub fn sib(ptr_size: PtrSize, args: struct { + disp: i32, + base: ?Register = null, + scale_index: ?ScaleIndex = null, + }) Memory { + return .{ .sib = .{ + .base = args.base, + .disp = args.disp, + .ptr_size = ptr_size, + .scale_index = args.scale_index, + } }; } - /// Encodes a 2 byte opcode - /// - /// e.g. IMUL has the opcode 0x0f 0xaf, so you use - /// - /// encoder.opcode_2byte(0x0f, 0xaf); - pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) void { - self.code.appendAssumeCapacity(prefix); - self.code.appendAssumeCapacity(opcode); + pub fn rip(ptr_size: PtrSize, disp: i32) Memory { + return .{ .rip = .{ .ptr_size = ptr_size, .disp = disp } }; } - /// Encodes a 3 byte opcode - /// - /// e.g. MOVSD has the opcode 0xf2 0x0f 0x10 - /// - /// encoder.opcode_3byte(0xf2, 0x0f, 0x10); - pub fn opcode_3byte(self: Self, prefix_1: u8, prefix_2: u8, opcode: u8) void { - self.code.appendAssumeCapacity(prefix_1); - self.code.appendAssumeCapacity(prefix_2); - self.code.appendAssumeCapacity(opcode); + pub fn isSegmentRegister(mem: Memory) bool { + return switch (mem) { + .moffs => true, + .rip => false, + .sib => |s| if (s.base) |r| r.class() == .segment else false, + }; } - /// Encodes a 1 byte opcode with a reg field - /// - /// Remember to add a REX prefix byte if reg is extended! - pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) void { - assert(opcode & 0b111 == 0); - self.code.appendAssumeCapacity(opcode | reg); + pub fn base(mem: Memory) ?Register { + return switch (mem) { + .moffs => |m| m.seg, + .sib => |s| s.base, + .rip => null, + }; } - // ------ - // ModR/M - // ------ - - /// Construct a ModR/M byte given all the fields - /// - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) void { - self.code.appendAssumeCapacity( - @as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm, - ); + pub fn scaleIndex(mem: Memory) ?ScaleIndex { + return switch (mem) { + .moffs, .rip => null, + .sib => |s| s.scale_index, + }; } - /// Construct a ModR/M byte using direct r/m addressing - /// r/m effective address: r/m - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) void { - self.modRm(0b11, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect r/m addressing - /// r/m effective address: [r/m] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4 and rm != 5); - self.modRm(0b00, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB addressing - /// r/m effective address: [SIB] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) void { - self.modRm(0b00, reg_or_opx, 0b100); - } - - /// Construct a ModR/M byte using RIP-relative addressing - /// r/m effective address: [RIP + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) void { - self.modRm(0b00, reg_or_opx, 0b101); - } - - /// Construct a ModR/M byte using indirect r/m with a 8bit displacement - /// r/m effective address: [r/m + disp8] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4); - self.modRm(0b01, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB with a 8bit displacement - /// r/m effective address: [SIB + disp8] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) void { - self.modRm(0b01, reg_or_opx, 0b100); - } - - /// Construct a ModR/M byte using indirect r/m with a 32bit displacement - /// r/m effective address: [r/m + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4); - self.modRm(0b10, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB with a 32bit displacement - /// r/m effective address: [SIB + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) void { - self.modRm(0b10, reg_or_opx, 0b100); - } - - // --- - // SIB - // --- - - /// Construct a SIB byte given all the fields - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib(self: Self, scale: u2, index: u3, base: u3) void { - self.code.appendAssumeCapacity( - @as(u8, scale) << 6 | @as(u8, index) << 3 | base, - ); - } - - /// Construct a SIB byte with scale * index + base, no frills. - /// r/m effective address: [base + scale * index] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) void { - assert(base != 5); - - self.sib(scale, index, base); - } - - /// Construct a SIB byte with scale * index + disp32 - /// r/m effective address: [scale * index + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) void { - assert(index != 4); - - // scale is actually ignored - // index = 4 means no index - // base = 5 means no base, if mod == 0. - self.sib(scale, index, 5); - } - - /// Construct a SIB byte with just base - /// r/m effective address: [base] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_base(self: Self, base: u3) void { - assert(base != 5); - - // scale is actually ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - /// Construct a SIB byte with just disp32 - /// r/m effective address: [disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_disp32(self: Self) void { - // scale is actually ignored - // index = 4 means no index - // base = 5 means no base, if mod == 0. - self.sib(0, 4, 5); - } - - /// Construct a SIB byte with scale * index + base + disp8 - /// r/m effective address: [base + scale * index + disp8] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) void { - self.sib(scale, index, base); - } - - /// Construct a SIB byte with base + disp8, no index - /// r/m effective address: [base + disp8] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_baseDisp8(self: Self, base: u3) void { - // scale is ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - /// Construct a SIB byte with scale * index + base + disp32 - /// r/m effective address: [base + scale * index + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) void { - self.sib(scale, index, base); - } - - /// Construct a SIB byte with base + disp32, no index - /// r/m effective address: [base + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_baseDisp32(self: Self, base: u3) void { - // scale is ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - // ------------------------- - // Trivial (no bit fiddling) - // ------------------------- - - /// Encode an 8 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm8(self: Self, imm: i8) void { - self.code.appendAssumeCapacity(@bitCast(u8, imm)); - } - - /// Encode an 8 bit displacement - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn disp8(self: Self, disp: i8) void { - self.code.appendAssumeCapacity(@bitCast(u8, disp)); - } - - /// Encode an 16 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm16(self: Self, imm: i16) void { - self.writeIntLittle(i16, imm); - } - - /// Encode an 32 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm32(self: Self, imm: i32) void { - self.writeIntLittle(i32, imm); - } - - /// Encode an 32 bit displacement - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn disp32(self: Self, disp: i32) void { - self.writeIntLittle(i32, disp); - } - - /// Encode an 64 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm64(self: Self, imm: u64) void { - self.writeIntLittle(u64, imm); + pub fn size(mem: Memory) u32 { + return switch (mem) { + .rip => |r| r.ptr_size.size(), + .sib => |s| s.ptr_size.size(), + .moffs => unreachable, + }; } }; - -test "Encoder helpers - general purpose registers" { - var code = ArrayList(u8).init(testing.allocator); - defer code.deinit(); - - // simple integer multiplication - - // imul eax,edi - // 0faf c7 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 4); - encoder.rex(.{ - .r = Register.eax.isExtended(), - .b = Register.edi.isExtended(), - }); - encoder.opcode_2byte(0x0f, 0xaf); - encoder.modRm_direct( - Register.eax.lowEnc(), - Register.edi.lowEnc(), - ); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x0f, 0xaf, 0xc7 }, code.items); - } - - // simple mov - - // mov eax,edi - // 89 f8 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 3); - encoder.rex(.{ - .r = Register.edi.isExtended(), - .b = Register.eax.isExtended(), - }); - encoder.opcode_1byte(0x89); - encoder.modRm_direct( - Register.edi.lowEnc(), - Register.eax.lowEnc(), - ); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x89, 0xf8 }, code.items); - } - - // signed integer addition of 32-bit sign extended immediate to 64 bit register - - // add rcx, 2147483647 - // - // Using the following opcode: REX.W + 81 /0 id, we expect the following encoding - // - // 48 : REX.W set for 64 bit operand (*r*cx) - // 81 : opcode for " with immediate" - // c1 : id = rcx, - // : c1 = 11 <-- mod = 11 indicates r/m is register (rcx) - // : 000 <-- opcode_extension = 0 because opcode extension is /0. /0 specifies ADD - // : 001 <-- 001 is rcx - // ffffff7f : 2147483647 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 7); - encoder.rex(.{ .w = true }); // use 64 bit operation - encoder.opcode_1byte(0x81); - encoder.modRm_direct( - 0, - Register.rcx.lowEnc(), - ); - encoder.imm32(2147483647); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x48, 0x81, 0xc1, 0xff, 0xff, 0xff, 0x7f }, code.items); - } -} - -test "Encoder helpers - Vex prefix" { - var buf: [3]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - - { - var vex_prefix = Encoder.Vex{}; - vex_prefix.rex(.{ - .r = true, - }); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x78 }, buf[0..nwritten]); - } - - { - stream.reset(); - var vex_prefix = Encoder.Vex{}; - vex_prefix.reg(Register.xmm15.enc()); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x80 }, buf[0..nwritten]); - } - - { - stream.reset(); - var vex_prefix = Encoder.Vex{}; - vex_prefix.rex(.{ - .w = true, - .x = true, - }); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b101_0_0001, 0b0_1111_0_00 }, buf[0..nwritten]); - } - - { - stream.reset(); - var vex_prefix = Encoder.Vex{}; - vex_prefix.rex(.{ - .w = true, - .r = true, - }); - vex_prefix.len_256(); - vex_prefix.lead_opc_0f(); - vex_prefix.simd_prefix_66(); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b011_0_0001, 0b0_1111_1_01 }, buf[0..nwritten]); - } - - var code = ArrayList(u8).init(testing.allocator); - defer code.deinit(); - - { - // vmovapd xmm1, xmm2 - const encoder = try Encoder.init(&code, 4); - var vex = Encoder.Vex{}; - vex.simd_prefix_66(); - encoder.vex(vex); // use 64 bit operation - encoder.opcode_1byte(0x28); - encoder.modRm_direct(0, Register.xmm1.lowEnc()); - try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0xF9, 0x28, 0xC1 }, code.items); - } - - { - try code.resize(0); - - // vmovhpd xmm13, xmm1, qword ptr [rip] - const encoder = try Encoder.init(&code, 9); - var vex = Encoder.Vex{}; - vex.len_128(); - vex.simd_prefix_66(); - vex.lead_opc_0f(); - vex.rex(.{ .r = true }); - vex.reg(Register.xmm1.enc()); - encoder.vex(vex); - encoder.opcode_1byte(0x16); - encoder.modRm_RIPDisp32(Register.xmm13.lowEnc()); - encoder.disp32(0); - try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0x71, 0x16, 0x2D, 0x00, 0x00, 0x00, 0x00 }, code.items); - } -} - -// TODO add these registers to the enum and populate dwarfLocOp -// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register. -// RA = (16, "RA"), -// -// XMM0 = (17, "xmm0"), -// XMM1 = (18, "xmm1"), -// XMM2 = (19, "xmm2"), -// XMM3 = (20, "xmm3"), -// XMM4 = (21, "xmm4"), -// XMM5 = (22, "xmm5"), -// XMM6 = (23, "xmm6"), -// XMM7 = (24, "xmm7"), -// -// XMM8 = (25, "xmm8"), -// XMM9 = (26, "xmm9"), -// XMM10 = (27, "xmm10"), -// XMM11 = (28, "xmm11"), -// XMM12 = (29, "xmm12"), -// XMM13 = (30, "xmm13"), -// XMM14 = (31, "xmm14"), -// XMM15 = (32, "xmm15"), -// -// ST0 = (33, "st0"), -// ST1 = (34, "st1"), -// ST2 = (35, "st2"), -// ST3 = (36, "st3"), -// ST4 = (37, "st4"), -// ST5 = (38, "st5"), -// ST6 = (39, "st6"), -// ST7 = (40, "st7"), -// -// MM0 = (41, "mm0"), -// MM1 = (42, "mm1"), -// MM2 = (43, "mm2"), -// MM3 = (44, "mm3"), -// MM4 = (45, "mm4"), -// MM5 = (46, "mm5"), -// MM6 = (47, "mm6"), -// MM7 = (48, "mm7"), -// -// RFLAGS = (49, "rFLAGS"), -// ES = (50, "es"), -// CS = (51, "cs"), -// SS = (52, "ss"), -// DS = (53, "ds"), -// FS = (54, "fs"), -// GS = (55, "gs"), -// -// FS_BASE = (58, "fs.base"), -// GS_BASE = (59, "gs.base"), -// -// TR = (62, "tr"), -// LDTR = (63, "ldtr"), -// MXCSR = (64, "mxcsr"), -// FCW = (65, "fcw"), -// FSW = (66, "fsw"), -// -// XMM16 = (67, "xmm16"), -// XMM17 = (68, "xmm17"), -// XMM18 = (69, "xmm18"), -// XMM19 = (70, "xmm19"), -// XMM20 = (71, "xmm20"), -// XMM21 = (72, "xmm21"), -// XMM22 = (73, "xmm22"), -// XMM23 = (74, "xmm23"), -// XMM24 = (75, "xmm24"), -// XMM25 = (76, "xmm25"), -// XMM26 = (77, "xmm26"), -// XMM27 = (78, "xmm27"), -// XMM28 = (79, "xmm28"), -// XMM29 = (80, "xmm29"), -// XMM30 = (81, "xmm30"), -// XMM31 = (82, "xmm31"), -// -// K0 = (118, "k0"), -// K1 = (119, "k1"), -// K2 = (120, "k2"), -// K3 = (121, "k3"), -// K4 = (122, "k4"), -// K5 = (123, "k5"), -// K6 = (124, "k6"), -// K7 = (125, "k7"), diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig new file mode 100644 index 0000000000..3daffc7ad2 --- /dev/null +++ b/src/arch/x86_64/encoder.zig @@ -0,0 +1,794 @@ +const std = @import("std"); +const assert = std.debug.assert; +const math = std.math; + +const bits = @import("bits.zig"); +const Encoding = @import("Encoding.zig"); +const Memory = bits.Memory; +const Moffs = bits.Moffs; +const PtrSize = bits.PtrSize; +const Register = bits.Register; + +pub const Instruction = struct { + op1: Operand = .none, + op2: Operand = .none, + op3: Operand = .none, + op4: Operand = .none, + encoding: Encoding, + + pub const Mnemonic = Encoding.Mnemonic; + + pub const Operand = union(enum) { + none, + reg: Register, + mem: Memory, + imm: i64, + + /// Returns the bitsize of the operand. + /// Asserts the operand is either register or memory. + pub fn size(op: Operand) u64 { + return switch (op) { + .none => unreachable, + .reg => |reg| reg.size(), + .mem => |mem| mem.size(), + .imm => unreachable, + }; + } + + /// Returns true if the operand is a segment register. + /// Asserts the operand is either register or memory. + pub fn isSegmentRegister(op: Operand) bool { + return switch (op) { + .none => unreachable, + .reg => |reg| reg.class() == .segment, + .mem => |mem| mem.isSegmentRegister(), + .imm => unreachable, + }; + } + + pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void { + switch (op) { + .none => {}, + .reg => |reg| try writer.writeAll(@tagName(reg)), + .mem => |mem| switch (mem) { + .rip => |rip| { + try writer.print("{s} ptr [rip", .{@tagName(rip.ptr_size)}); + if (rip.disp != 0) { + const sign_bit = if (sign(rip.disp) < 0) "-" else "+"; + const disp_abs = try std.math.absInt(rip.disp); + try writer.print(" {s} 0x{x}", .{ sign_bit, disp_abs }); + } + try writer.writeByte(']'); + }, + .sib => |sib| { + try writer.print("{s} ptr ", .{@tagName(sib.ptr_size)}); + + if (mem.isSegmentRegister()) { + return writer.print("{s}:0x{x}", .{ @tagName(sib.base.?), sib.disp }); + } + + try writer.writeByte('['); + + if (sib.base) |base| { + try writer.print("{s}", .{@tagName(base)}); + } + if (sib.scale_index) |si| { + if (sib.base != null) { + try writer.writeAll(" + "); + } + try writer.print("{s} * {d}", .{ @tagName(si.index), si.scale }); + } + if (sib.disp != 0) { + if (sib.base != null or sib.scale_index != null) { + try writer.writeByte(' '); + } + try writer.writeByte(if (sign(sib.disp) < 0) '-' else '+'); + const disp_abs = try std.math.absInt(sib.disp); + try writer.print(" 0x{x}", .{disp_abs}); + } + + try writer.writeByte(']'); + }, + .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }), + }, + .imm => |imm| { + if (enc_op == .imm64) { + return writer.print("0x{x}", .{@bitCast(u64, imm)}); + } + const imm_abs = try std.math.absInt(imm); + if (sign(imm) < 0) { + try writer.writeByte('-'); + } + try writer.print("0x{x}", .{imm_abs}); + }, + } + } + }; + + pub fn new(mnemonic: Mnemonic, args: struct { + op1: Operand = .none, + op2: Operand = .none, + op3: Operand = .none, + op4: Operand = .none, + }) !Instruction { + const encoding = Encoding.findByMnemonic(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }) orelse return error.InvalidInstruction; + std.log.debug("{}", .{encoding}); + return .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + .encoding = encoding, + }; + } + + pub fn fmtPrint(inst: Instruction, writer: anytype) !void { + try writer.print("{s}", .{@tagName(inst.encoding.mnemonic)}); + const ops = [_]struct { Operand, Encoding.Op }{ + .{ inst.op1, inst.encoding.op1 }, + .{ inst.op2, inst.encoding.op2 }, + .{ inst.op3, inst.encoding.op3 }, + .{ inst.op4, inst.encoding.op4 }, + }; + for (&ops, 0..) |op, i| { + if (op[0] == .none) break; + if (i > 0) { + try writer.writeByte(','); + } + try writer.writeByte(' '); + try op[0].fmtPrint(op[1], writer); + } + } + + pub fn encode(inst: Instruction, writer: anytype) !void { + const encoder = Encoder(@TypeOf(writer)){ .writer = writer }; + const encoding = inst.encoding; + + try inst.encodeLegacyPrefixes(encoder); + try inst.encodeMandatoryPrefix(encoder); + try inst.encodeRexPrefix(encoder); + try inst.encodeOpcode(encoder); + + switch (encoding.op_en) { + .np, .o => {}, + .i, .d => try encodeImm(inst.op1.imm, encoding.op1, encoder), + .zi, .oi => try encodeImm(inst.op2.imm, encoding.op2, encoder), + .fd => try encoder.imm64(inst.op2.mem.moffs.offset), + .td => try encoder.imm64(inst.op1.mem.moffs.offset), + else => { + const mem_op = switch (encoding.op_en) { + .m, .mi, .m1, .mc, .mr => inst.op1, + .rm, .rmi => inst.op2, + else => unreachable, + }; + switch (mem_op) { + .reg => |reg| { + const rm = switch (encoding.op_en) { + .m, .mi, .m1, .mc => encoding.modRmExt(), + .mr => inst.op2.reg.lowEnc(), + .rm, .rmi => inst.op1.reg.lowEnc(), + else => unreachable, + }; + try encoder.modRm_direct(rm, reg.lowEnc()); + }, + .mem => |mem| { + const op = switch (encoding.op_en) { + .m, .mi, .m1, .mc => .none, + .mr => inst.op2, + .rm, .rmi => inst.op1, + else => unreachable, + }; + try encodeMemory(encoding, mem, op, encoder); + }, + else => unreachable, + } + + switch (encoding.op_en) { + .mi => try encodeImm(inst.op2.imm, encoding.op2, encoder), + .rmi => try encodeImm(inst.op3.imm, encoding.op3, encoder), + else => {}, + } + }, + } + } + + fn encodeOpcode(inst: Instruction, encoder: anytype) !void { + const opcode = inst.encoding.opcode(); + switch (inst.encoding.op_en) { + .o, .oi => try encoder.opcode_withReg(opcode[0], inst.op1.reg.lowEnc()), + else => { + const index: usize = if (inst.encoding.mandatoryPrefix()) |_| 1 else 0; + for (opcode[index..]) |byte| { + try encoder.opcode_1byte(byte); + } + }, + } + } + + fn encodeLegacyPrefixes(inst: Instruction, encoder: anytype) !void { + const enc = inst.encoding; + const op_en = enc.op_en; + + var legacy = LegacyPrefixes{}; + if (enc.mode == .none) { + const bit_size = enc.operandSize(); + if (bit_size == 16) { + legacy.set16BitOverride(); + } + } + + const segment_override: ?Register = switch (op_en) { + .i, .zi, .o, .oi, .d, .np => null, + .fd => inst.op2.mem.base().?, + .td => inst.op1.mem.base().?, + .rm, .rmi => if (inst.op2.isSegmentRegister()) blk: { + break :blk switch (inst.op2) { + .reg => |r| r, + .mem => |m| m.base().?, + else => unreachable, + }; + } else null, + .m, .mi, .m1, .mc, .mr => if (inst.op1.isSegmentRegister()) blk: { + break :blk switch (inst.op1) { + .reg => |r| r, + .mem => |m| m.base().?, + else => unreachable, + }; + } else null, + }; + if (segment_override) |seg| { + legacy.setSegmentOverride(seg); + } + + try encoder.legacyPrefixes(legacy); + } + + fn encodeRexPrefix(inst: Instruction, encoder: anytype) !void { + const op_en = inst.encoding.op_en; + + // Check if we need REX and can actually encode it + const is_rex_invalid = for (&[_]Operand{ inst.op1, inst.op2, inst.op3, inst.op4 }) |op| switch (op) { + .reg => |r| if (r.isRexInvalid()) break true, + else => {}, + } else false; + + var rex = Rex{}; + rex.w = inst.encoding.mode == .long; + + switch (op_en) { + .np, .i, .zi, .fd, .td, .d => {}, + .o, .oi => { + rex.b = inst.op1.reg.isExtended(); + }, + .m, .mi, .m1, .mc, .mr, .rm, .rmi => { + const r_op = switch (op_en) { + .rm, .rmi => inst.op1, + .mr => inst.op2, + else => null, + }; + if (r_op) |op| { + rex.r = op.reg.isExtended(); + } + + const b_x_op = switch (op_en) { + .rm, .rmi => inst.op2, + .m, .mi, .m1, .mc, .mr => inst.op1, + else => unreachable, + }; + switch (b_x_op) { + .reg => |r| { + rex.b = r.isExtended(); + }, + .mem => |mem| { + rex.b = if (mem.base()) |base| base.isExtended() else false; + rex.x = if (mem.scaleIndex()) |si| si.index.isExtended() else false; + }, + else => unreachable, + } + }, + } + + if (rex.isSet() and is_rex_invalid) return error.CannotEncode; + + try encoder.rex(rex); + } + + fn encodeMandatoryPrefix(inst: Instruction, encoder: anytype) !void { + const prefix = inst.encoding.mandatoryPrefix() orelse return; + try encoder.opcode_1byte(prefix); + } + + fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void { + const operand_enc = switch (operand) { + .reg => |reg| reg.lowEnc(), + .none => encoding.modRmExt(), + else => unreachable, + }; + + switch (mem) { + .moffs => unreachable, + .sib => |sib| { + if (sib.base) |base| { + if (base.class() == .segment) { + // TODO audit this wrt SIB + try encoder.modRm_SIBDisp0(operand_enc); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc()); + } else { + try encoder.sib_disp32(); + } + try encoder.disp32(sib.disp); + } else { + assert(base.class() == .general_purpose); + const dst = base.lowEnc(); + const src = operand_enc; + if (dst == 4 or sib.scale_index != null) { + if (sib.disp == 0 and dst != 5) { + try encoder.modRm_SIBDisp0(src); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexBase(scale, si.index.lowEnc(), dst); + } else { + try encoder.sib_base(dst); + } + } else if (math.cast(i8, sib.disp)) |_| { + try encoder.modRm_SIBDisp8(src); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexBaseDisp8(scale, si.index.lowEnc(), dst); + } else { + try encoder.sib_baseDisp8(dst); + } + try encoder.disp8(@truncate(i8, sib.disp)); + } else { + try encoder.modRm_SIBDisp32(src); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexBaseDisp32(scale, si.index.lowEnc(), dst); + } else { + try encoder.sib_baseDisp32(dst); + } + try encoder.disp32(sib.disp); + } + } else { + if (sib.disp == 0 and dst != 5) { + try encoder.modRm_indirectDisp0(src, dst); + } else if (math.cast(i8, sib.disp)) |_| { + try encoder.modRm_indirectDisp8(src, dst); + try encoder.disp8(@truncate(i8, sib.disp)); + } else { + try encoder.modRm_indirectDisp32(src, dst); + try encoder.disp32(sib.disp); + } + } + } + } else { + try encoder.modRm_SIBDisp0(operand_enc); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc()); + } else { + try encoder.sib_disp32(); + } + try encoder.disp32(sib.disp); + } + }, + .rip => |rip| { + try encoder.modRm_RIPDisp32(operand_enc); + try encoder.disp32(rip.disp); + }, + } + } + + fn encodeImm(imm: i64, kind: Encoding.Op, encoder: anytype) !void { + switch (kind) { + .imm8, .rel8 => try encoder.imm8(@truncate(i8, imm)), + .imm16, .rel16 => try encoder.imm16(@truncate(i16, imm)), + .imm32, .rel32 => try encoder.imm32(@truncate(i32, imm)), + .imm64 => try encoder.imm64(@bitCast(u64, imm)), + else => unreachable, + } + } +}; + +inline fn sign(i: anytype) @TypeOf(i) { + return @as(@TypeOf(i), @boolToInt(i > 0)) - @boolToInt(i < 0); +} + +pub const LegacyPrefixes = packed struct { + /// LOCK + prefix_f0: bool = false, + /// REPNZ, REPNE, REP, Scalar Double-precision + prefix_f2: bool = false, + /// REPZ, REPE, REP, Scalar Single-precision + prefix_f3: bool = false, + + /// CS segment override or Branch not taken + prefix_2e: bool = false, + /// SS segment override + prefix_36: bool = false, + /// ES segment override + prefix_26: bool = false, + /// FS segment override + prefix_64: bool = false, + /// GS segment override + prefix_65: bool = false, + + /// Branch taken + prefix_3e: bool = false, + + /// Address size override (enables 16 bit address size) + prefix_67: bool = false, + + /// Operand size override (enables 16 bit operation) + prefix_66: bool = false, + + padding: u5 = 0, + + pub fn setSegmentOverride(self: *LegacyPrefixes, reg: Register) void { + assert(reg.class() == .segment); + switch (reg) { + .cs => self.prefix_2e = true, + .ss => self.prefix_36 = true, + .es => self.prefix_26 = true, + .fs => self.prefix_64 = true, + .gs => self.prefix_65 = true, + .ds => {}, + else => unreachable, + } + } + + pub fn set16BitOverride(self: *LegacyPrefixes) void { + self.prefix_66 = true; + } +}; + +fn Encoder(comptime T: type) type { + return struct { + writer: T, + + const Self = @This(); + + // -------- + // Prefixes + // -------- + + /// Encodes legacy prefixes + pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) !void { + if (@bitCast(u16, prefixes) != 0) { + // Hopefully this path isn't taken very often, so we'll do it the slow way for now + + // LOCK + if (prefixes.prefix_f0) try self.writer.writeByte(0xf0); + // REPNZ, REPNE, REP, Scalar Double-precision + if (prefixes.prefix_f2) try self.writer.writeByte(0xf2); + // REPZ, REPE, REP, Scalar Single-precision + if (prefixes.prefix_f3) try self.writer.writeByte(0xf3); + + // CS segment override or Branch not taken + if (prefixes.prefix_2e) try self.writer.writeByte(0x2e); + // DS segment override + if (prefixes.prefix_36) try self.writer.writeByte(0x36); + // ES segment override + if (prefixes.prefix_26) try self.writer.writeByte(0x26); + // FS segment override + if (prefixes.prefix_64) try self.writer.writeByte(0x64); + // GS segment override + if (prefixes.prefix_65) try self.writer.writeByte(0x65); + + // Branch taken + if (prefixes.prefix_3e) try self.writer.writeByte(0x3e); + + // Operand size override + if (prefixes.prefix_66) try self.writer.writeByte(0x66); + + // Address size override + if (prefixes.prefix_67) try self.writer.writeByte(0x67); + } + } + + /// Use 16 bit operand size + /// + /// Note that this flag is overridden by REX.W, if both are present. + pub fn prefix16BitMode(self: Self) !void { + try self.writer.writeByte(0x66); + } + + /// Encodes a REX prefix byte given all the fields + /// + /// Use this byte whenever you need 64 bit operation, + /// or one of reg, index, r/m, base, or opcode-reg might be extended. + /// + /// See struct `Rex` for a description of each field. + /// + /// Does not add a prefix byte if none of the fields are set! + pub fn rex(self: Self, byte: Rex) !void { + var value: u8 = 0b0100_0000; + + if (byte.w) value |= 0b1000; + if (byte.r) value |= 0b0100; + if (byte.x) value |= 0b0010; + if (byte.b) value |= 0b0001; + + if (value != 0b0100_0000) { + try self.writer.writeByte(value); + } + } + + // ------ + // Opcode + // ------ + + /// Encodes a 1 byte opcode + pub fn opcode_1byte(self: Self, opcode: u8) !void { + try self.writer.writeByte(opcode); + } + + /// Encodes a 2 byte opcode + /// + /// e.g. IMUL has the opcode 0x0f 0xaf, so you use + /// + /// encoder.opcode_2byte(0x0f, 0xaf); + pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) !void { + try self.writer.writeAll(&.{ prefix, opcode }); + } + + /// Encodes a 3 byte opcode + /// + /// e.g. MOVSD has the opcode 0xf2 0x0f 0x10 + /// + /// encoder.opcode_3byte(0xf2, 0x0f, 0x10); + pub fn opcode_3byte(self: Self, prefix_1: u8, prefix_2: u8, opcode: u8) !void { + try self.writer.writeAll(&.{ prefix_1, prefix_2, opcode }); + } + + /// Encodes a 1 byte opcode with a reg field + /// + /// Remember to add a REX prefix byte if reg is extended! + pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) !void { + assert(opcode & 0b111 == 0); + try self.writer.writeByte(opcode | reg); + } + + // ------ + // ModR/M + // ------ + + /// Construct a ModR/M byte given all the fields + /// + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) !void { + try self.writer.writeByte(@as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm); + } + + /// Construct a ModR/M byte using direct r/m addressing + /// r/m effective address: r/m + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) !void { + try self.modRm(0b11, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect r/m addressing + /// r/m effective address: [r/m] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) !void { + assert(rm != 4 and rm != 5); + try self.modRm(0b00, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB addressing + /// r/m effective address: [SIB] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b00, reg_or_opx, 0b100); + } + + /// Construct a ModR/M byte using RIP-relative addressing + /// r/m effective address: [RIP + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b00, reg_or_opx, 0b101); + } + + /// Construct a ModR/M byte using indirect r/m with a 8bit displacement + /// r/m effective address: [r/m + disp8] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) !void { + assert(rm != 4); + try self.modRm(0b01, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB with a 8bit displacement + /// r/m effective address: [SIB + disp8] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b01, reg_or_opx, 0b100); + } + + /// Construct a ModR/M byte using indirect r/m with a 32bit displacement + /// r/m effective address: [r/m + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) !void { + assert(rm != 4); + try self.modRm(0b10, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB with a 32bit displacement + /// r/m effective address: [SIB + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b10, reg_or_opx, 0b100); + } + + // --- + // SIB + // --- + + /// Construct a SIB byte given all the fields + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib(self: Self, scale: u2, index: u3, base: u3) !void { + try self.writer.writeByte(@as(u8, scale) << 6 | @as(u8, index) << 3 | base); + } + + /// Construct a SIB byte with scale * index + base, no frills. + /// r/m effective address: [base + scale * index] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) !void { + assert(base != 5); + + try self.sib(scale, index, base); + } + + /// Construct a SIB byte with scale * index + disp32 + /// r/m effective address: [scale * index + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) !void { + // scale is actually ignored + // index = 4 means no index if and only if we haven't extended the register + // TODO enforce this + // base = 5 means no base, if mod == 0. + try self.sib(scale, index, 5); + } + + /// Construct a SIB byte with just base + /// r/m effective address: [base] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_base(self: Self, base: u3) !void { + assert(base != 5); + + // scale is actually ignored + // index = 4 means no index + try self.sib(0, 4, base); + } + + /// Construct a SIB byte with just disp32 + /// r/m effective address: [disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_disp32(self: Self) !void { + // scale is actually ignored + // index = 4 means no index + // base = 5 means no base, if mod == 0. + try self.sib(0, 4, 5); + } + + /// Construct a SIB byte with scale * index + base + disp8 + /// r/m effective address: [base + scale * index + disp8] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) !void { + try self.sib(scale, index, base); + } + + /// Construct a SIB byte with base + disp8, no index + /// r/m effective address: [base + disp8] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_baseDisp8(self: Self, base: u3) !void { + // scale is ignored + // index = 4 means no index + try self.sib(0, 4, base); + } + + /// Construct a SIB byte with scale * index + base + disp32 + /// r/m effective address: [base + scale * index + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) !void { + try self.sib(scale, index, base); + } + + /// Construct a SIB byte with base + disp32, no index + /// r/m effective address: [base + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_baseDisp32(self: Self, base: u3) !void { + // scale is ignored + // index = 4 means no index + try self.sib(0, 4, base); + } + + // ------------------------- + // Trivial (no bit fiddling) + // ------------------------- + + /// Encode an 8 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm8(self: Self, imm: i8) !void { + try self.writer.writeByte(@bitCast(u8, imm)); + } + + /// Encode an 8 bit displacement + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn disp8(self: Self, disp: i8) !void { + try self.writer.writeByte(@bitCast(u8, disp)); + } + + /// Encode an 16 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm16(self: Self, imm: i16) !void { + try self.writer.writeIntLittle(i16, imm); + } + + /// Encode an 32 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm32(self: Self, imm: i32) !void { + try self.writer.writeIntLittle(i32, imm); + } + + /// Encode an 32 bit displacement + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn disp32(self: Self, disp: i32) !void { + try self.writer.writeIntLittle(i32, disp); + } + + /// Encode an 64 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm64(self: Self, imm: u64) !void { + try self.writer.writeIntLittle(u64, imm); + } + }; +} + +pub const Rex = struct { + w: bool = false, + r: bool = false, + x: bool = false, + b: bool = false, + + pub fn isSet(rex: Rex) bool { + return rex.w or rex.r or rex.x or rex.b; + } +}; diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig new file mode 100644 index 0000000000..b7099d2ad2 --- /dev/null +++ b/src/arch/x86_64/encodings.zig @@ -0,0 +1,542 @@ +const Encoding = @import("Encoding.zig"); +const Mnemonic = Encoding.Mnemonic; +const OpEn = Encoding.OpEn; +const Op = Encoding.Op; +const Mode = Encoding.Mode; + +const opcode_len = u2; +const modrm_ext = u3; + +const Entry = struct { Mnemonic, OpEn, Op, Op, Op, Op, opcode_len, u8, u8, u8, modrm_ext, Mode }; + +// TODO move this into a .zon file when Zig is capable of importing .zon files +// zig fmt: off +pub const table = &[_]Entry{ + // General-purpose + .{ .adc, .zi, .al, .imm8, .none, .none, 1, 0x14, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .ax, .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .rax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, + .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, + .{ .adc, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, + .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, + .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, + + .{ .add, .zi, .al, .imm8, .none, .none, 1, 0x04, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .ax, .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .rax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, + .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, + .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, + + .{ .@"and", .zi, .al, .imm8, .none, .none, 1, 0x24, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .ax, .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .rax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, + .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, + .{ .@"and", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, + .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, + .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, + + // This is M encoding according to Intel, but D makes more sense here. + .{ .call, .d, .rel32, .none, .none, .none, 1, 0xe8, 0x00, 0x00, 0, .none }, + .{ .call, .m, .rm64, .none, .none, .none, 1, 0xff, 0x00, 0x00, 2, .none }, + + .{ .cbw, .np, .o16, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .none }, + .{ .cwde, .np, .o32, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .none }, + .{ .cdqe, .np, .o64, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .long }, + + .{ .cwd, .np, .o16, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .none }, + .{ .cdq, .np, .o32, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .none }, + .{ .cqo, .np, .o64, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .long }, + + .{ .cmova, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmova, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmova, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .long }, + .{ .cmovae, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovae, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovae, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long }, + .{ .cmovb, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovb, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovb, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long }, + .{ .cmovbe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovbe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovbe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .long }, + .{ .cmovc, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovc, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovc, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long }, + .{ .cmove, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmove, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmove, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long }, + .{ .cmovg, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovg, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovg, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .long }, + .{ .cmovge, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovge, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovge, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .long }, + .{ .cmovl, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovl, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovl, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .long }, + .{ .cmovle, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovle, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovle, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .long }, + .{ .cmovna, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovna, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovna, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .long }, + .{ .cmovnae, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovnae, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovnae, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long }, + .{ .cmovnb, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnb, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnb, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long }, + .{ .cmovnbe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmovnbe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmovnbe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .long }, + .{ .cmovnc, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnc, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnc, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long }, + .{ .cmovne, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovne, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovne, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .long }, + .{ .cmovng, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovng, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovng, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .long }, + .{ .cmovnge, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovnge, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovnge, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .long }, + .{ .cmovnl, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovnl, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovnl, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .long }, + .{ .cmovnle, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovnle, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovnle, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .long }, + .{ .cmovno, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .none }, + .{ .cmovno, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .none }, + .{ .cmovno, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .long }, + .{ .cmovnp, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovnp, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovnp, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .long }, + .{ .cmovns, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .none }, + .{ .cmovns, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .none }, + .{ .cmovns, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .long }, + .{ .cmovnz, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovnz, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovnz, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .long }, + .{ .cmovo, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .none }, + .{ .cmovo, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .none }, + .{ .cmovo, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .long }, + .{ .cmovp, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovp, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovp, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .long }, + .{ .cmovpe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovpe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovpe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .long }, + .{ .cmovpo, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovpo, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovpo, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .long }, + .{ .cmovs, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .none }, + .{ .cmovs, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .none }, + .{ .cmovs, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .long }, + .{ .cmovz, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmovz, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmovz, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long }, + + .{ .cmp, .zi, .al, .imm8, .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .ax, .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .rax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, + .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, + .{ .cmp, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, + .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, + .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, + + .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .long }, + + .{ .fisttp, .m, .m16, .none, .none, .none, 1, 0xdf, 0x00, 0x00, 1, .fpu }, + .{ .fisttp, .m, .m32, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 1, .fpu }, + .{ .fisttp, .m, .m64, .none, .none, .none, 1, 0xdd, 0x00, 0x00, 1, .fpu }, + + .{ .fld, .m, .m32, .none, .none, .none, 1, 0xd9, 0x00, 0x00, 0, .fpu }, + .{ .fld, .m, .m64, .none, .none, .none, 1, 0xdd, 0x00, 0x00, 0, .fpu }, + .{ .fld, .m, .m80, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 5, .fpu }, + + .{ .idiv, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long }, + + .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, + .{ .imul, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long }, + + .{ .int3, .np, .none, .none, .none, .none, 1, 0xcc, 0x00, 0x00, 0, .none }, + + .{ .ja, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x87, 0x00, 0, .none }, + .{ .jae, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none }, + .{ .jb, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none }, + .{ .jbe, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x86, 0x00, 0, .none }, + .{ .jc, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none }, + .{ .jrcxz, .d, .rel32, .none, .none, .none, 1, 0xe3, 0x00, 0x00, 0, .none }, + .{ .je, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x84, 0x00, 0, .none }, + .{ .jg, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8f, 0x00, 0, .none }, + .{ .jge, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8d, 0x00, 0, .none }, + .{ .jl, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8c, 0x00, 0, .none }, + .{ .jle, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8e, 0x00, 0, .none }, + .{ .jna, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x86, 0x00, 0, .none }, + .{ .jnae, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none }, + .{ .jnb, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none }, + .{ .jnbe, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x87, 0x00, 0, .none }, + .{ .jnc, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none }, + .{ .jne, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x85, 0x00, 0, .none }, + .{ .jng, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8e, 0x00, 0, .none }, + .{ .jnge, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8c, 0x00, 0, .none }, + .{ .jnl, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8d, 0x00, 0, .none }, + .{ .jnle, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8f, 0x00, 0, .none }, + .{ .jno, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x81, 0x00, 0, .none }, + .{ .jnp, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8b, 0x00, 0, .none }, + .{ .jns, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x89, 0x00, 0, .none }, + .{ .jnz, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x85, 0x00, 0, .none }, + .{ .jo, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x80, 0x00, 0, .none }, + .{ .jp, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8a, 0x00, 0, .none }, + .{ .jpe, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8a, 0x00, 0, .none }, + .{ .jpo, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8b, 0x00, 0, .none }, + .{ .js, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x88, 0x00, 0, .none }, + .{ .jz, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x84, 0x00, 0, .none }, + + .{ .jmp, .d, .rel32, .none, .none, .none, 1, 0xe9, 0x00, 0x00, 0, .none }, + .{ .jmp, .m, .rm64, .none, .none, .none, 1, 0xff, 0x00, 0x00, 4, .none }, + + .{ .lea, .rm, .r16, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none }, + .{ .lea, .rm, .r32, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none }, + .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long }, + + .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, + .{ .mov, .mr, .rm16, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .sreg, .rm16, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .sreg, .rm64, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long }, + .{ .mov, .fd, .al, .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .ax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .eax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .rax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long }, + .{ .mov, .td, .moffs, .al, .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .ax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, + .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, + .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm64, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, + + .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r64, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .long }, + .{ .movsx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .none }, + .{ .movsx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .long }, + + // This instruction is discouraged. + .{ .movsxd, .rm, .r32, .rm32, .none, .none, 1, 0x63, 0x00, 0x00, 0, .none }, + .{ .movsxd, .rm, .r64, .rm32, .none, .none, 1, 0x63, 0x00, 0x00, 0, .long }, + + .{ .movzx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .none }, + .{ .movzx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .none }, + .{ .movzx, .rm, .r64, .rm8, .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .long }, + .{ .movzx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .none }, + .{ .movzx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .long }, + + .{ .mul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .long }, + + .{ .nop, .np, .none, .none, .none, .none, 1, 0x90, 0x00, 0x00, 0, .none }, + + .{ .@"or", .zi, .al, .imm8, .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .ax, .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .rax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, + .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, + .{ .@"or", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, + .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, + .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, + + .{ .pop, .o, .r16, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, + .{ .pop, .o, .r64, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, + .{ .pop, .m, .rm16, .none, .none, .none, 1, 0x8f, 0x00, 0x00, 0, .none }, + .{ .pop, .m, .rm64, .none, .none, .none, 1, 0x8f, 0x00, 0x00, 0, .none }, + + .{ .push, .o, .r16, .none, .none, .none, 1, 0x50, 0x00, 0x00, 0, .none }, + .{ .push, .o, .r64, .none, .none, .none, 1, 0x50, 0x00, 0x00, 0, .none }, + .{ .push, .m, .rm16, .none, .none, .none, 1, 0xff, 0x00, 0x00, 6, .none }, + .{ .push, .m, .rm64, .none, .none, .none, 1, 0xff, 0x00, 0x00, 6, .none }, + .{ .push, .i, .imm8, .none, .none, .none, 1, 0x6a, 0x00, 0x00, 0, .none }, + .{ .push, .i, .imm16, .none, .none, .none, 1, 0x68, 0x00, 0x00, 0, .none }, + .{ .push, .i, .imm32, .none, .none, .none, 1, 0x68, 0x00, 0x00, 0, .none }, + + .{ .ret, .np, .none, .none, .none, .none, 1, 0xc3, 0x00, 0x00, 0, .none }, + + .{ .sal, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, + .{ .sal, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, + .{ .sal, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, + + .{ .sar, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .long }, + .{ .sar, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .long }, + .{ .sar, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long }, + + .{ .sbb, .zi, .al, .imm8, .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .ax, .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .rax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, + .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, + .{ .sbb, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, + .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, + .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, + + .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .sete, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + .{ .setg, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setna, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setnb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .setnc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setne, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .setng, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setnl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setno, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .none }, + .{ .setnp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .setns, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .none }, + .{ .setnz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .seto, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .none }, + .{ .setp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setpe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setpo, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .sets, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .none }, + .{ .setz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + + .{ .shl, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, + .{ .shl, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, + .{ .shl, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, + + .{ .shr, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .long }, + .{ .shr, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .long }, + .{ .shr, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long }, + + .{ .sub, .zi, .al, .imm8, .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .ax, .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .rax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, + .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, + .{ .sub, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, + .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, + .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, + + .{ .syscall, .np, .none, .none, .none, .none, 2, 0x0f, 0x05, 0x00, 0, .none }, + + .{ .@"test", .zi, .al, .imm8, .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .ax, .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .rax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, + .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm64, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, + .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, + + .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none }, + + .{ .xor, .zi, .al, .imm8, .none, .none, 1, 0x34, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .ax, .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .rax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, + .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, + .{ .xor, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, + .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, + .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, + + // SSE + .{ .addss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x58, 0, .sse }, + + .{ .cmpss, .rmi, .xmm, .xmm_m32, .imm8, .none, 3, 0xf3, 0x0f, 0xc2, 0, .sse }, + + .{ .movss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x10, 0, .sse }, + .{ .movss, .mr, .xmm_m32, .xmm, .none, .none, 3, 0xf3, 0x0f, 0x11, 0, .sse }, + + .{ .ucomiss, .rm, .xmm, .xmm_m32, .none, .none, 2, 0x0f, 0x2e, 0x00, 0, .sse }, + + // SSE2 + .{ .addsd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf2, 0x0f, 0x58, 0, .sse2 }, + + .{ .cmpsd, .rmi, .xmm, .xmm_m64, .imm8, .none, 3, 0xf2, 0x0f, 0xc2, 0, .sse2 }, + + .{ .movq, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf3, 0x0f, 0x7e, 0, .sse2 }, + .{ .movq, .mr, .xmm_m64, .xmm, .none, .none, 3, 0x66, 0x0f, 0xd6, 0, .sse2 }, + + .{ .movsd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf2, 0x0f, 0x10, 0, .sse2 }, + .{ .movsd, .mr, .xmm_m64, .xmm, .none, .none, 3, 0xf2, 0x0f, 0x11, 0, .sse2 }, + + .{ .ucomisd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0x66, 0x0f, 0x2e, 0, .sse2 }, +}; +// zig fmt: on From f14831ec73d7dd87a770f902fb53d1ede486e524 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 5 Mar 2023 18:47:00 +0100 Subject: [PATCH 079/294] x86_64: truncate immediates --- src/arch/x86_64/CodeGen.zig | 109 +++++++++++++++++++++++++++++------ src/arch/x86_64/Emit.zig | 16 ++--- src/arch/x86_64/Encoding.zig | 6 +- src/arch/x86_64/Mir.zig | 4 +- src/arch/x86_64/encoder.zig | 24 +++----- 5 files changed, 114 insertions(+), 45 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index c108ad6f32..ef599092de 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -385,6 +385,24 @@ pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { return self.addExtraAssumeCapacity(extra); } +fn extraData(self: *Self, comptime T: type, index: u32) struct { data: T, end: u32 } { + const fields = std.meta.fields(T); + var i: u32 = index; + var result: T = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => self.mir_extra.items[i], + i32 => @bitCast(i32, self.mir_extra.items[i]), + else => @compileError("bad field type"), + }; + i += 1; + } + return .{ + .data = result, + .end = i, + }; +} + pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { const fields = std.meta.fields(@TypeOf(extra)); const result = @intCast(u32, self.mir_extra.items.len); @@ -2759,9 +2777,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type 1, 2, 4 => { // TODO this is wasteful! // introduce new MIR tag specifically for mov [reg + 0], imm + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, - .operand = @truncate(u32, imm), + .operand = operand, }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -2872,10 +2896,17 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, // TODO check if this logic is correct - .operand = @truncate(u32, imm), + .operand = operand, }); const flags: u2 = switch (abi_size) { 1 => 0b00, @@ -3600,7 +3631,13 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu _ = try self.addInst(.{ .tag = mir_tag, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - .data = .{ .imm = @truncate(u32, imm) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + } }, }); }, .memory, @@ -3671,9 +3708,16 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu 8 => 0b11, else => unreachable, }; + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -off, - .operand = @truncate(u32, imm), + .operand = operand, }); _ = try self.addInst(.{ .tag = tag, @@ -4855,7 +4899,13 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u _ = try self.addInst(.{ .tag = .xor, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - .data = .{ .imm = @intCast(u32, imm) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + } }, }); }, .register => |reg| { @@ -5366,20 +5416,27 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + else => unreachable, + }; + const flags: u2 = switch (abi_size) { + 1 => 0b00, + 2 => 0b01, + 4 => 0b10, + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u32, imm), + .operand = operand, }); _ = try self.addInst(.{ .tag = .mov_mem_imm, .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp, - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }, + .flags = flags, }), .data = .{ .payload = payload }, }); @@ -5518,7 +5575,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), + .operand = @truncate(u8, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5530,9 +5587,15 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl }); }, 1, 2, 4 => { + const operand = switch (abi_size) { + 1 => @truncate(u8, x_big), + 2 => @truncate(u16, x_big), + 4 => @truncate(u32, x_big), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), + .operand = operand, }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5932,7 +5995,7 @@ fn genInlineMemset( const loop_start = try self.addInst(.{ .tag = .cmp, .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = @bitCast(u32, @as(i32, -1)) }, + .data = .{ .imm = @bitCast(u8, @as(i8, -1)) }, }); // je end @@ -6037,7 +6100,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = .{ .imm = @truncate(u32, x) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, x), + 2 => @truncate(u16, x), + 4 => @truncate(u32, x), + 8 => @truncate(u32, x), + else => unreachable, + } }, }); return; } @@ -6204,7 +6273,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg1 = registerAlias(reg, abi_size), .flags = 0b01, }), - .data = .{ .imm = @truncate(u32, x) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, x), + 2 => @truncate(u16, x), + 4 => @truncate(u32, x), + 8 => @truncate(u32, x), + else => unreachable, + } }, }); } else { // If this is RAX, we can use a direct load. diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 1c540adc9d..5d52b87d87 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -236,12 +236,12 @@ fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { op3: Instruction.Operand = .none, op4: Instruction.Operand = .none, }) InnerError!void { - const inst = try Instruction.new(mnemonic, .{ + const inst = Instruction.new(mnemonic, .{ .op1 = ops.op1, .op2 = ops.op2, .op3 = ops.op3, .op4 = ops.op4, - }); + }) catch unreachable; return inst.encode(emit.code.writer()); } @@ -624,7 +624,7 @@ fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const scale_index = Memory.ScaleIndex{ - .scale = scale, + .scale = @as(u4, 1) << scale, .index = index_reg_disp.index, }; return emit.encode(mnemonic, .{ @@ -643,7 +643,7 @@ fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const scale_index = Memory.ScaleIndex{ - .scale = scale, + .scale = @as(u4, 1) << scale, .index = index_reg_disp.index, }; assert(ops.reg2 != .none); @@ -663,7 +663,7 @@ fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); const scale_index = Memory.ScaleIndex{ - .scale = scale, + .scale = @as(u4, 1) << scale, .index = index_reg_disp_imm.index, }; return emit.encode(mnemonic, .{ @@ -688,7 +688,7 @@ fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.In 0b11 => .qword, }; const scale_index = Memory.ScaleIndex{ - .scale = 0, + .scale = 1, .index = index_reg_disp_imm.index, }; return emit.encode(mnemonic, .{ @@ -777,7 +777,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } else emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.mov, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = @bitCast(i64, imm) }, + .op2 = .{ .imm = imm }, }); }, 0b01 => { @@ -983,7 +983,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; const scale_index = Memory.ScaleIndex{ - .scale = 0, + .scale = 1, .index = index_reg_disp.index, }; return emit.encode(.lea, .{ diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 2cccded7ec..7cf8910924 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -390,9 +390,9 @@ pub const Op = enum { .imm => |imm| { if (imm == 1) return .unity; - if (math.cast(i8, imm)) |_| return .imm8; - if (math.cast(i16, imm)) |_| return .imm16; - if (math.cast(i32, imm)) |_| return .imm32; + if (math.cast(u8, imm)) |_| return .imm8; + if (math.cast(u16, imm)) |_| return .imm16; + if (math.cast(u32, imm)) |_| return .imm32; return .imm64; }, } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index b3be08e86b..4124592627 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -594,9 +594,9 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } -pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { +pub fn extraData(mir: Mir, comptime T: type, index: u32) struct { data: T, end: u32 } { const fields = std.meta.fields(T); - var i: usize = index; + var i: u32 = index; var result: T = undefined; inline for (fields) |field| { @field(result, field.name) = switch (field.type) { diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 3daffc7ad2..eefc7fd6e2 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -22,7 +22,7 @@ pub const Instruction = struct { none, reg: Register, mem: Memory, - imm: i64, + imm: u64, /// Returns the bitsize of the operand. /// Asserts the operand is either register or memory. @@ -47,6 +47,7 @@ pub const Instruction = struct { } pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void { + _ = enc_op; switch (op) { .none => {}, .reg => |reg| try writer.writeAll(@tagName(reg)), @@ -92,14 +93,7 @@ pub const Instruction = struct { .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }), }, .imm => |imm| { - if (enc_op == .imm64) { - return writer.print("0x{x}", .{@bitCast(u64, imm)}); - } - const imm_abs = try std.math.absInt(imm); - if (sign(imm) < 0) { - try writer.writeByte('-'); - } - try writer.print("0x{x}", .{imm_abs}); + try writer.print("0x{x}", .{imm}); }, } } @@ -117,7 +111,7 @@ pub const Instruction = struct { .op3 = args.op3, .op4 = args.op4, }) orelse return error.InvalidInstruction; - std.log.debug("{}", .{encoding}); + std.log.warn("{}", .{encoding}); return .{ .op1 = args.op1, .op2 = args.op2, @@ -386,12 +380,12 @@ pub const Instruction = struct { } } - fn encodeImm(imm: i64, kind: Encoding.Op, encoder: anytype) !void { + fn encodeImm(imm: u64, kind: Encoding.Op, encoder: anytype) !void { switch (kind) { - .imm8, .rel8 => try encoder.imm8(@truncate(i8, imm)), - .imm16, .rel16 => try encoder.imm16(@truncate(i16, imm)), - .imm32, .rel32 => try encoder.imm32(@truncate(i32, imm)), - .imm64 => try encoder.imm64(@bitCast(u64, imm)), + .imm8, .rel8 => try encoder.imm8(@bitCast(i8, @truncate(u8, imm))), + .imm16, .rel16 => try encoder.imm16(@bitCast(i16, @truncate(u16, imm))), + .imm32, .rel32 => try encoder.imm32(@bitCast(i32, @truncate(u32, imm))), + .imm64 => try encoder.imm64(imm), else => unreachable, } } From ea3b3e94aba405d4364fdb06231208467ac71f87 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 5 Mar 2023 18:55:04 +0100 Subject: [PATCH 080/294] x86_64: clean up call semantics in codegen --- src/arch/x86_64/CodeGen.zig | 6 +++--- src/arch/x86_64/Emit.zig | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index ef599092de..9f5438a9fa 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4091,11 +4091,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (self.bin_file.cast(link.File.Elf)) |elf_file| { const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); - const got_addr = @intCast(u32, atom.getOffsetTableAddress(elf_file)); + const got_addr = @intCast(i32, atom.getOffsetTableAddress(elf_file)); _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .imm = got_addr }, + .data = .{ .disp = got_addr }, }); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); @@ -4142,7 +4142,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .imm = @intCast(u32, fn_got_addr) }, + .data = .{ .disp = @intCast(i32, fn_got_addr) }, }); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5d52b87d87..8bc182d773 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -340,9 +340,9 @@ fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) }, 0b01 => { if (ops.reg1 == .none) { - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ - .op1 = .{ .imm = imm }, + .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, }); } return emit.encode(mnemonic, .{ From bc43cee7756d48064be0dd87f92e24c577788eec Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 7 Mar 2023 12:57:06 +0100 Subject: [PATCH 081/294] Get more things passing --- src/arch/x86_64/CodeGen.zig | 161 ++++++------- src/arch/x86_64/Emit.zig | 74 +++--- src/arch/x86_64/Encoding.zig | 89 +++++--- src/arch/x86_64/Mir.zig | 3 + src/arch/x86_64/bits.zig | 69 +++++- src/arch/x86_64/encoder.zig | 85 +++---- src/arch/x86_64/encodings.zig | 413 +++++++++++++++++----------------- 7 files changed, 476 insertions(+), 418 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 9f5438a9fa..90286a1bb2 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -852,10 +852,10 @@ fn processDeath(self: *Self, inst: Air.Inst.Index) void { branch.inst_table.putAssumeCapacity(inst, .dead); switch (prev_value) { .register => |reg| { - self.register_manager.freeReg(reg.to64()); + self.register_manager.freeReg(reg); }, .register_overflow => |ro| { - self.register_manager.freeReg(ro.reg.to64()); + self.register_manager.freeReg(ro.reg); self.eflags_inst = null; }, .eflags => { @@ -1253,7 +1253,13 @@ fn airNot(self: *Self, inst: Air.Inst.Index) !void { }; defer if (dst_mcv_lock) |lock| self.register_manager.unlockReg(lock); - const mask = ~@as(u64, 0); + const mask = switch (operand_ty.abiSize(self.target.*)) { + 1 => ~@as(u8, 0), + 2 => ~@as(u16, 0), + 4 => ~@as(u32, 0), + 8 => ~@as(u64, 0), + else => unreachable, + }; try self.genBinOpMir(.xor, operand_ty, dst_mcv, .{ .immediate = mask }); break :result dst_mcv; @@ -2777,15 +2783,9 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type 1, 2, 4 => { // TODO this is wasteful! // introduce new MIR tag specifically for mov [reg + 0], imm - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, - .operand = operand, + .operand = @intCast(u32, imm), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -2896,17 +2896,10 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, // TODO check if this logic is correct - .operand = operand, + .operand = @intCast(u32, imm), }); const flags: u2 = switch (abi_size) { 1 => 0b00, @@ -3102,8 +3095,8 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { const shift = @intCast(u8, struct_field_offset * @sizeOf(usize)); try self.genShiftBinOpMir(.shr, Type.usize, dst_mcv.register, .{ .immediate = shift }); - // Mask with reg.size() - struct_field_size - const max_reg_bit_width = Register.rax.size(); + // Mask with reg.bitSize() - struct_field_size + const max_reg_bit_width = Register.rax.bitSize(); const mask_shift = @intCast(u6, (max_reg_bit_width - struct_field_ty.bitSize(self.target.*))); const mask = (~@as(u64, 0)) >> mask_shift; @@ -3631,13 +3624,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu _ = try self.addInst(.{ .tag = mir_tag, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - } }, + .data = .{ .imm = @intCast(u32, imm) }, }); }, .memory, @@ -3708,16 +3695,9 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu 8 => 0b11, else => unreachable, }; - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -off, - .operand = operand, + .operand = @intCast(u32, imm), }); _ = try self.addInst(.{ .tag = tag, @@ -3791,7 +3771,7 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .reg2 = dst_reg.to32(), .flags = 0b10, }), - .data = .{ .imm = @truncate(u32, imm) }, + .data = .{ .imm = @intCast(u32, imm) }, }); } else { // TODO verify we don't spill and assign to the same register as dst_mcv @@ -4899,13 +4879,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u _ = try self.addInst(.{ .tag = .xor, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - } }, + .data = .{ .imm = @intCast(u32, imm) }, }); }, .register => |reg| { @@ -5416,12 +5390,6 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - else => unreachable, - }; const flags: u2 = switch (abi_size) { 1 => 0b00, 2 => 0b01, @@ -5430,7 +5398,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = operand, + .operand = @intCast(u32, imm), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5575,7 +5543,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u8, x_big), + .operand = @intCast(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5587,15 +5555,9 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl }); }, 1, 2, 4 => { - const operand = switch (abi_size) { - 1 => @truncate(u8, x_big), - 2 => @truncate(u16, x_big), - 4 => @truncate(u32, x_big), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = operand, + .operand = @intCast(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5724,7 +5686,7 @@ fn genInlineMemcpyRegisterRegister( src_reg: Register, offset: i32, ) InnerError!void { - assert(dst_reg.size() == 64); + assert(dst_reg.bitSize() == 64); const dst_reg_lock = self.register_manager.lockReg(dst_reg); defer if (dst_reg_lock) |lock| self.register_manager.unlockReg(lock); @@ -5823,7 +5785,7 @@ fn genInlineMemcpy( _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_addr_reg, @divExact(reg.size(), 8)), + .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), .reg2 = reg, }), .data = undefined, @@ -5852,7 +5814,7 @@ fn genInlineMemcpy( _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(src_addr_reg, @divExact(reg.size(), 8)), + .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), .reg2 = reg, }), .data = undefined, @@ -5976,7 +5938,7 @@ fn genInlineMemset( _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(addr_reg, @divExact(reg.size(), 8)), + .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), .reg2 = reg, }), .data = undefined, @@ -5994,8 +5956,11 @@ fn genInlineMemset( // cmp index_reg, -1 const loop_start = try self.addInst(.{ .tag = .cmp, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = @bitCast(u8, @as(i8, -1)) }, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = index_reg, + .flags = 0b11, + }), + .data = .{ .imm_s = -1 }, }); // je end @@ -6016,8 +5981,14 @@ fn genInlineMemset( // mov byte ptr [rbp + index_reg + stack_offset], imm _ = try self.addInst(.{ .tag = .mov_mem_index_imm, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = addr_reg }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode(index_reg, 0, @truncate(u32, x))) }, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = addr_reg, + }), + .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( + index_reg, + 0, + @intCast(u32, x), + )) }, }); }, else => return self.fail("TODO inline memset for value of type {}", .{value}), @@ -6064,7 +6035,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (!self.wantSafety()) return; // The already existing value will do just fine. // Write the debug undefined value. - switch (registerAlias(reg, abi_size).size()) { + switch (registerAlias(reg, abi_size).bitSize()) { 8 => return self.genSetReg(ty, reg, .{ .immediate = 0xaa }), 16 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaa }), 32 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa }), @@ -6100,13 +6071,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, x), - 2 => @truncate(u16, x), - 4 => @truncate(u32, x), - 8 => @truncate(u32, x), - else => unreachable, - } }, + .data = .{ .imm = @intCast(u32, x) }, }); return; } @@ -6273,13 +6238,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg1 = registerAlias(reg, abi_size), .flags = 0b01, }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, x), - 2 => @truncate(u16, x), - 4 => @truncate(u32, x), - 8 => @truncate(u32, x), - else => unreachable, - } }, + .data = .{ .disp = @intCast(i32, x) }, }); } else { // If this is RAX, we can use a direct load. @@ -6949,7 +6908,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { if (ret_ty_size == 0) { assert(ret_ty.isError()); result.return_value = .{ .immediate = 0 }; - } else if (ret_ty_size <= 8) { + } else if (ret_ty_size <= 8 and !ret_ty.isRuntimeFloat()) { const aliased_reg = registerAlias(abi.getCAbiIntReturnRegs(self.target.*)[0], ret_ty_size); result.return_value = .{ .register = aliased_reg }; } else { @@ -7024,28 +6983,34 @@ fn parseRegName(name: []const u8) ?Register { /// Returns register wide enough to hold at least `size_bytes`. fn registerAlias(reg: Register, size_bytes: u32) Register { - if (size_bytes == 0) { - unreachable; // should be comptime-known - } else if (size_bytes <= 1) { - return reg.to8(); - } else if (size_bytes <= 2) { - return reg.to16(); - } else if (size_bytes <= 4) { - return reg.to32(); - } else if (size_bytes <= 8) { - return reg.to64(); - } else if (size_bytes <= 16) { - return reg.to128(); - } else if (size_bytes <= 32) { - return reg.to256(); - } else unreachable; + return switch (reg.class()) { + .general_purpose => if (size_bytes == 0) + unreachable // should be comptime-known + else if (size_bytes <= 1) + reg.to8() + else if (size_bytes <= 2) + reg.to16() + else if (size_bytes <= 4) + reg.to32() + else if (size_bytes <= 8) + reg.to64() + else + unreachable, + .floating_point => if (size_bytes <= 16) + reg.to128() + else if (size_bytes <= 32) + reg.to256() + else + unreachable, + .segment => unreachable, + }; } /// Truncates the value in the register in place. /// Clobbers any remaining bits. fn truncateRegister(self: *Self, ty: Type, reg: Register) !void { const int_info = ty.intInfo(self.target.*); - const max_reg_bit_width = Register.rax.size(); + const max_reg_bit_width = Register.rax.bitSize(); switch (int_info.signedness) { .signed => { const shift = @intCast(u6, max_reg_bit_width - int_info.bits); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 8bc182d773..e23b500fb8 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -21,6 +21,7 @@ const CodeGen = @import("CodeGen.zig"); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const Encoder = bits.Encoder; const ErrorMsg = Module.ErrorMsg; +const Immediate = bits.Immediate; const Instruction = encoder.Instruction; const MCValue = @import("CodeGen.zig").MCValue; const Memory = bits.Memory; @@ -283,7 +284,7 @@ fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.push, .{ - .op1 = .{ .imm = imm }, + .op1 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => unreachable, @@ -327,9 +328,7 @@ fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; try emit.encode(mnemonic, .{ - .op1 = .{ - .imm = 0, - }, + .op1 = .{ .imm = Immediate.s(0) }, }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, @@ -400,7 +399,7 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }; const source = emit.code.items.len; try emit.encode(mnemonic, .{ - .op1 = .{ .imm = 0 }, + .op1 = .{ .imm = Immediate.s(0) }, }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, @@ -521,7 +520,7 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.@"test", .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); } return emit.encode(.@"test", .{ @@ -543,7 +542,7 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.ret, .{ - .op1 = .{ .imm = imm }, + .op1 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => { @@ -560,7 +559,7 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); } return emit.encode(mnemonic, .{ @@ -573,7 +572,7 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = base, .disp = disp, }) }, @@ -585,7 +584,7 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I } const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ .base = ops.reg1, .disp = disp, }) }, @@ -593,7 +592,11 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I }); }, 0b11 => { - return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); + const imm_s = emit.mir.instructions.items(.data)[inst].imm_s; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = Immediate.s(imm_s) }, + }); }, } } @@ -614,7 +617,7 @@ fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.In .disp = imm_pair.dest_off, .base = ops.reg1, }) }, - .op2 = .{ .imm = imm_pair.operand }, + .op2 = .{ .imm = Immediate.u(imm_pair.operand) }, }); } @@ -629,7 +632,7 @@ fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. }; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = ops.reg2, .scale_index = scale_index, .disp = index_reg_disp.disp, @@ -648,7 +651,7 @@ fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. }; assert(ops.reg2 != .none); return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ .base = ops.reg1, .scale_index = scale_index, .disp = index_reg_disp.disp, @@ -672,7 +675,7 @@ fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. .disp = index_reg_disp_imm.disp, .scale_index = scale_index, }) }, - .op2 = .{ .imm = index_reg_disp_imm.imm }, + .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, }); } @@ -697,7 +700,7 @@ fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.In .base = ops.reg1, .scale_index = scale_index, }) }, - .op2 = .{ .imm = index_reg_disp_imm.imm }, + .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, }); } @@ -708,7 +711,7 @@ fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; switch (ops.flags) { 0b00 => { - const mnemonic: Instruction.Mnemonic = if (ops.reg2.size() == 32) .movsxd else .movsx; + const mnemonic: Instruction.Mnemonic = if (ops.reg2.bitSize() == 32) .movsxd else .movsx; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, .op2 = .{ .reg = ops.reg2 }, @@ -718,14 +721,15 @@ fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ptr_size: Memory.PtrSize = switch (ops.flags) { 0b01 => .byte, 0b10 => .word, - 0b11 => .qword, + 0b11 => .dword, else => unreachable, }; - return emit.encode(.movsx, .{ + const mnemonic: Instruction.Mnemonic = if (ops.flags == 0b11) .movsxd else .movsx; + return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = disp, .base = ops.reg2, + .disp = disp, }) }, }); }, @@ -770,19 +774,19 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - const imm: u64 = if (ops.reg1.size() == 64) blk: { + const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.mov, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); }, 0b01 => { if (ops.reg1 == .none) { - const imm: u64 = if (ops.reg2.size() == 64) blk: { + const imm: u64 = if (ops.reg2.bitSize() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); @@ -792,7 +796,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .op2 = .{ .reg = .rax }, }); } - const imm: u64 = if (ops.reg1.size() == 64) blk: { + const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); @@ -847,7 +851,7 @@ fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I 0b00 => { return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = 1 }, + .op2 = .{ .imm = Immediate.u(1) }, }); }, 0b01 => { @@ -860,7 +864,7 @@ fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => { @@ -920,7 +924,7 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { return emit.encode(.imul, .{ .op1 = .{ .reg = ops.reg1 }, .op2 = .{ .reg = ops.reg2 }, - .op3 = .{ .imm = imm }, + .op3 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => { @@ -932,7 +936,7 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .base = ops.reg2, .disp = imm_pair.dest_off, }) }, - .op3 = .{ .imm = imm_pair.operand }, + .op3 = .{ .imm = Immediate.u(imm_pair.operand) }, }); }, } @@ -959,7 +963,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; return emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = src_reg, .disp = disp, }) }, @@ -969,7 +973,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const start_offset = emit.code.items.len; try emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, }); const end_offset = emit.code.items.len; // Backpatch the displacement @@ -988,7 +992,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }; return emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = src_reg, .scale_index = scale_index, .disp = index_reg_disp.disp, @@ -1012,7 +1016,7 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { try emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, }); const end_offset = emit.code.items.len; @@ -1065,7 +1069,7 @@ fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ .base = ops.reg2, .disp = disp, }) }, @@ -1074,7 +1078,7 @@ fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index 0b01 => { const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = ops.reg1, .disp = disp, }) }, @@ -1127,7 +1131,7 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const offset = blk: { // callq try emit.encode(.call, .{ - .op1 = .{ .imm = 0 }, + .op1 = .{ .imm = Immediate.s(0) }, }); break :blk @intCast(u32, emit.code.items.len) - 4; }; diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 7cf8910924..5d3944a554 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -144,20 +144,20 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct { // rex.W mov dil, 0x1 // Here, rex.W is not needed. const rex_w_allowed = blk: { - const bit_size = enc.operandSize(); + const bit_size = enc.operandBitSize(); break :blk bit_size == 64 or bit_size == 8; }; if (rex_w_allowed) return enc; }, } } else if (prefixes.legacy.prefix_66) { - switch (enc.operandSize()) { + switch (enc.operandBitSize()) { 16 => return enc, else => {}, } } else { if (enc.mode == .none) { - switch (enc.operandSize()) { + switch (enc.operandBitSize()) { 16 => {}, else => return enc, } @@ -187,17 +187,17 @@ pub fn modRmExt(encoding: Encoding) u3 { }; } -pub fn operandSize(encoding: Encoding) u32 { +pub fn operandBitSize(encoding: Encoding) u64 { if (encoding.mode == .long) return 64; - const bit_size: u32 = switch (encoding.op_en) { + const bit_size: u64 = switch (encoding.op_en) { .np => switch (encoding.op1) { .o16 => 16, .o32 => 32, .o64 => 64, else => 32, }, - .td => encoding.op2.size(), - else => encoding.op1.size(), + .td => encoding.op2.bitSize(), + else => encoding.op1.bitSize(), }; return bit_size; } @@ -244,9 +244,9 @@ pub fn format( else => unreachable, }; const tag = switch (op) { - .imm8 => "ib", - .imm16 => "iw", - .imm32 => "id", + .imm8, .imm8s => "ib", + .imm16, .imm16s => "iw", + .imm32, .imm32s => "id", .imm64 => "io", .rel8 => "cb", .rel16 => "cw", @@ -330,6 +330,7 @@ pub const Op = enum { o16, o32, o64, unity, imm8, imm16, imm32, imm64, + imm8s, imm16s, imm32s, al, ax, eax, rax, cl, r8, r16, r32, r64, @@ -349,7 +350,7 @@ pub const Op = enum { .reg => |reg| { switch (reg.class()) { .segment => return .sreg, - .floating_point => return switch (reg.size()) { + .floating_point => return switch (reg.bitSize()) { 128 => .xmm, else => unreachable, }, @@ -362,7 +363,7 @@ pub const Op = enum { else => unreachable, }; if (reg == .cl) return .cl; - return switch (reg.size()) { + return switch (reg.bitSize()) { 8 => .r8, 16 => .r16, 32 => .r32, @@ -376,7 +377,7 @@ pub const Op = enum { .mem => |mem| switch (mem) { .moffs => return .moffs, .sib, .rip => { - const bit_size = mem.size(); + const bit_size = mem.bitSize(); return switch (bit_size) { 8 => .m8, 16 => .m16, @@ -389,21 +390,34 @@ pub const Op = enum { }, .imm => |imm| { - if (imm == 1) return .unity; - if (math.cast(u8, imm)) |_| return .imm8; - if (math.cast(u16, imm)) |_| return .imm16; - if (math.cast(u32, imm)) |_| return .imm32; - return .imm64; + switch (imm) { + .signed => |x| { + if (x == 1) return .unity; + if (math.cast(i8, x)) |_| return .imm8s; + if (math.cast(i16, x)) |_| return .imm16s; + return .imm32s; + }, + .unsigned => |x| { + if (x == 1) return .unity; + if (math.cast(i8, x)) |_| return .imm8s; + if (math.cast(u8, x)) |_| return .imm8; + if (math.cast(i16, x)) |_| return .imm16s; + if (math.cast(u16, x)) |_| return .imm16; + if (math.cast(i32, x)) |_| return .imm32s; + if (math.cast(u32, x)) |_| return .imm32; + return .imm64; + }, + } }, } } - pub fn size(op: Op) u32 { + pub fn bitSize(op: Op) u64 { return switch (op) { - .none, .o16, .o32, .o64, .moffs, .m, .sreg, .unity => unreachable, - .imm8, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, - .imm16, .ax, .r16, .m16, .rm16, .rel16 => 16, - .imm32, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, + .none, .o16, .o32, .o64, .moffs, .m, .sreg => unreachable, + .unity, .imm8, .imm8s, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, + .imm16, .imm16s, .ax, .r16, .m16, .rm16, .rel16 => 16, + .imm32, .imm32s, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64, .m80 => 80, .xmm => 128, @@ -428,6 +442,7 @@ pub const Op = enum { // zig fmt: off return switch (op) { .imm8, .imm16, .imm32, .imm64, + .imm8s, .imm16s, .imm32s, .rel8, .rel16, .rel32, .unity, => true, @@ -479,28 +494,40 @@ pub const Op = enum { .sse, .sse2 => return op.isFloatingPointRegister() and target.isFloatingPointRegister(), else => switch (target) { .cl, .al, .ax, .eax, .rax => return op == target, - else => return op.size() == target.size(), + else => return op.bitSize() == target.bitSize(), }, } } if (op.isMemory() and target.isMemory()) { switch (target) { .m => return true, - else => return op.size() == target.size(), + else => return op.bitSize() == target.bitSize(), } } if (op.isImmediate() and target.isImmediate()) { switch (target) { - .imm32, .rel32 => switch (op) { - .unity, .imm8, .imm16, .imm32 => return true, + .imm32s, .rel32 => switch (op) { + .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s => return true, else => return op == target, }, - .imm16, .rel16 => switch (op) { - .unity, .imm8, .imm16 => return true, + .imm32 => switch (op) { + .unity, .imm8, .imm8s, .imm16, .imm16s, .imm32, .imm32s => return true, else => return op == target, }, - .imm8, .rel8 => switch (op) { - .unity, .imm8 => return true, + .imm16s, .rel16 => switch (op) { + .unity, .imm8s, .imm8, .imm16s => return true, + else => return op == target, + }, + .imm16 => switch (op) { + .unity, .imm8, .imm8s, .imm16, .imm16s => return true, + else => return op == target, + }, + .imm8s, .rel8 => switch (op) { + .unity, .imm8s => return true, + else => return op == target, + }, + .imm8 => switch (op) { + .unity, .imm8, .imm8s => return true, else => return op == target, }, else => return op == target, diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 4124592627..43c24216a8 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -34,6 +34,7 @@ pub const Inst = struct { /// 0b01 reg1, [reg2 + imm32] /// 0b01 reg1, [ds:imm32] /// 0b10 [reg1 + imm32], reg2 + /// 0b11 reg1, imm_s /// Notes: /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. @@ -421,6 +422,8 @@ pub const Inst = struct { inst: Index, /// A 32-bit immediate value. imm: u32, + /// A 32-bit signed immediate value. + imm_s: i32, /// A 32-bit signed displacement value. disp: i32, /// A condition code for use with EFLAGS register. diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index 9166550f16..cd7ed91849 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -207,7 +207,7 @@ pub const Register = enum(u7) { return @intCast(u6, @enumToInt(reg) - base); } - pub fn size(reg: Register) u32 { + pub fn bitSize(reg: Register) u64 { return switch (@enumToInt(reg)) { // zig fmt: off @enumToInt(Register.rax) ... @enumToInt(Register.r15) => 64, @@ -273,7 +273,7 @@ pub const Register = enum(u7) { return @truncate(u3, reg.enc()); } - pub fn toSize(reg: Register, bit_size: u32) Register { + pub fn toBitSize(reg: Register, bit_size: u64) Register { return switch (bit_size) { 8 => reg.to8(), 16 => reg.to16(), @@ -334,7 +334,17 @@ pub const Register = enum(u7) { pub fn dwarfLocOp(reg: Register) u8 { return switch (reg.class()) { - .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0, + .general_purpose => switch (reg.to64()) { + .rax => DW.OP.reg0, + .rdx => DW.OP.reg1, + .rcx => DW.OP.reg2, + .rbx => DW.OP.reg3, + .rsi => DW.OP.reg4, + .rdi => DW.OP.reg5, + .rbp => DW.OP.reg6, + .rsp => DW.OP.reg7, + else => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0, + }, .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.reg17, else => unreachable, }; @@ -345,7 +355,17 @@ pub const Register = enum(u7) { /// register to a given signed offset. pub fn dwarfLocOpDeref(reg: Register) u8 { return switch (reg.class()) { - .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0, + .general_purpose => switch (reg.to64()) { + .rax => DW.OP.breg0, + .rdx => DW.OP.breg1, + .rcx => DW.OP.breg2, + .rbx => DW.OP.breg3, + .rsi => DW.OP.breg4, + .rdi => DW.OP.breg5, + .rbp => DW.OP.breg6, + .rsp => DW.OP.breg7, + else => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0, + }, .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.breg17, else => unreachable, }; @@ -397,7 +417,7 @@ pub const Memory = union(enum) { qword, tbyte, - pub fn fromSize(bit_size: u32) PtrSize { + pub fn fromBitSize(bit_size: u64) PtrSize { return switch (bit_size) { 8 => .byte, 16 => .word, @@ -408,7 +428,7 @@ pub const Memory = union(enum) { }; } - pub fn size(s: PtrSize) u32 { + pub fn bitSize(s: PtrSize) u64 { return switch (s) { .byte => 8, .word => 16, @@ -481,11 +501,42 @@ pub const Memory = union(enum) { }; } - pub fn size(mem: Memory) u32 { + pub fn bitSize(mem: Memory) u64 { return switch (mem) { - .rip => |r| r.ptr_size.size(), - .sib => |s| s.ptr_size.size(), + .rip => |r| r.ptr_size.bitSize(), + .sib => |s| s.ptr_size.bitSize(), .moffs => unreachable, }; } }; + +pub const Immediate = union(enum) { + signed: i32, + unsigned: u64, + + pub fn u(x: u64) Immediate { + return .{ .unsigned = x }; + } + + pub fn s(x: i32) Immediate { + return .{ .signed = x }; + } + + pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 { + return switch (imm) { + .signed => |x| switch (bit_size) { + 8 => @bitCast(u8, @intCast(i8, x)), + 16 => @bitCast(u16, @intCast(i16, x)), + 32 => @bitCast(u32, @intCast(i32, x)), + else => unreachable, + }, + .unsigned => |x| switch (bit_size) { + 8 => @intCast(u8, x), + 16 => @intCast(u16, x), + 32 => @intCast(u32, x), + 64 => x, + else => unreachable, + }, + }; + } +}; diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index eefc7fd6e2..d4ca925891 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -4,6 +4,7 @@ const math = std.math; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); +const Immediate = bits.Immediate; const Memory = bits.Memory; const Moffs = bits.Moffs; const PtrSize = bits.PtrSize; @@ -22,15 +23,14 @@ pub const Instruction = struct { none, reg: Register, mem: Memory, - imm: u64, + imm: Immediate, /// Returns the bitsize of the operand. - /// Asserts the operand is either register or memory. - pub fn size(op: Operand) u64 { + pub fn bitSize(op: Operand) u64 { return switch (op) { .none => unreachable, - .reg => |reg| reg.size(), - .mem => |mem| mem.size(), + .reg => |reg| reg.bitSize(), + .mem => |mem| mem.bitSize(), .imm => unreachable, }; } @@ -47,7 +47,6 @@ pub const Instruction = struct { } pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void { - _ = enc_op; switch (op) { .none => {}, .reg => |reg| try writer.writeAll(@tagName(reg)), @@ -92,9 +91,7 @@ pub const Instruction = struct { }, .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }), }, - .imm => |imm| { - try writer.print("0x{x}", .{imm}); - }, + .imm => |imm| try writer.print("0x{x}", .{imm.asUnsigned(enc_op.bitSize())}), } } }; @@ -110,8 +107,17 @@ pub const Instruction = struct { .op2 = args.op2, .op3 = args.op3, .op4 = args.op4, - }) orelse return error.InvalidInstruction; - std.log.warn("{}", .{encoding}); + }) orelse { + std.log.debug("{s} {s} {s} {s} {s}", .{ + @tagName(mnemonic), + @tagName(Encoding.Op.fromOperand(args.op1)), + @tagName(Encoding.Op.fromOperand(args.op2)), + @tagName(Encoding.Op.fromOperand(args.op3)), + @tagName(Encoding.Op.fromOperand(args.op4)), + }); + return error.InvalidInstruction; + }; + std.log.debug("{}", .{encoding}); return .{ .op1 = args.op1, .op2 = args.op2, @@ -210,7 +216,7 @@ pub const Instruction = struct { var legacy = LegacyPrefixes{}; if (enc.mode == .none) { - const bit_size = enc.operandSize(); + const bit_size = enc.operandBitSize(); if (bit_size == 16) { legacy.set16BitOverride(); } @@ -380,12 +386,13 @@ pub const Instruction = struct { } } - fn encodeImm(imm: u64, kind: Encoding.Op, encoder: anytype) !void { - switch (kind) { - .imm8, .rel8 => try encoder.imm8(@bitCast(i8, @truncate(u8, imm))), - .imm16, .rel16 => try encoder.imm16(@bitCast(i16, @truncate(u16, imm))), - .imm32, .rel32 => try encoder.imm32(@bitCast(i32, @truncate(u32, imm))), - .imm64 => try encoder.imm64(imm), + fn encodeImm(imm: Immediate, kind: Encoding.Op, encoder: anytype) !void { + const raw = imm.asUnsigned(kind.bitSize()); + switch (kind.bitSize()) { + 8 => try encoder.imm8(@intCast(u8, raw)), + 16 => try encoder.imm16(@intCast(u16, raw)), + 32 => try encoder.imm32(@intCast(u32, raw)), + 64 => try encoder.imm64(raw), else => unreachable, } } @@ -732,13 +739,6 @@ fn Encoder(comptime T: type) type { // Trivial (no bit fiddling) // ------------------------- - /// Encode an 8 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm8(self: Self, imm: i8) !void { - try self.writer.writeByte(@bitCast(u8, imm)); - } - /// Encode an 8 bit displacement /// /// It is sign-extended to 64 bits by the cpu. @@ -746,20 +746,6 @@ fn Encoder(comptime T: type) type { try self.writer.writeByte(@bitCast(u8, disp)); } - /// Encode an 16 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm16(self: Self, imm: i16) !void { - try self.writer.writeIntLittle(i16, imm); - } - - /// Encode an 32 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm32(self: Self, imm: i32) !void { - try self.writer.writeIntLittle(i32, imm); - } - /// Encode an 32 bit displacement /// /// It is sign-extended to 64 bits by the cpu. @@ -767,6 +753,27 @@ fn Encoder(comptime T: type) type { try self.writer.writeIntLittle(i32, disp); } + /// Encode an 8 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm8(self: Self, imm: u8) !void { + try self.writer.writeByte(imm); + } + + /// Encode an 16 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm16(self: Self, imm: u16) !void { + try self.writer.writeIntLittle(u16, imm); + } + + /// Encode an 32 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm32(self: Self, imm: u32) !void { + try self.writer.writeIntLittle(u32, imm); + } + /// Encode an 64 bit immediate /// /// It is sign-extended to 64 bits by the cpu. diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig index b7099d2ad2..c3fb347f22 100644 --- a/src/arch/x86_64/encodings.zig +++ b/src/arch/x86_64/encodings.zig @@ -13,65 +13,65 @@ const Entry = struct { Mnemonic, OpEn, Op, Op, Op, Op, opcode_len, u8, u8, u8, m // zig fmt: off pub const table = &[_]Entry{ // General-purpose - .{ .adc, .zi, .al, .imm8, .none, .none, 1, 0x14, 0x00, 0x00, 0, .none }, - .{ .adc, .zi, .ax, .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, - .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, - .{ .adc, .zi, .rax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, - .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, - .{ .adc, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, - .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, - .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, - .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, - .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, - .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, - .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, - .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, - .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, + .{ .adc, .zi, .al, .imm8, .none, .none, 1, 0x14, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .ax, .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .rax, .imm32s, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, + .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, + .{ .adc, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, + .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, + .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, - .{ .add, .zi, .al, .imm8, .none, .none, 1, 0x04, 0x00, 0x00, 0, .none }, - .{ .add, .zi, .ax, .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, - .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, - .{ .add, .zi, .rax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, - .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, - .{ .add, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, - .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, - .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, - .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, - .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, - .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, - .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, - .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, - .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, + .{ .add, .zi, .al, .imm8, .none, .none, 1, 0x04, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .ax, .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .rax, .imm32s, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, + .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, + .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, - .{ .@"and", .zi, .al, .imm8, .none, .none, 1, 0x24, 0x00, 0x00, 0, .none }, - .{ .@"and", .zi, .ax, .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, - .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, - .{ .@"and", .zi, .rax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, - .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, - .{ .@"and", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, - .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, - .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, - .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, - .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, - .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, - .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, - .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, - .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, + .{ .@"and", .zi, .al, .imm8, .none, .none, 1, 0x24, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .ax, .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .rax, .imm32s, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, + .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, + .{ .@"and", .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, + .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, + .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, // This is M encoding according to Intel, but D makes more sense here. .{ .call, .d, .rel32, .none, .none, .none, 1, 0xe8, 0x00, 0x00, 0, .none }, @@ -176,25 +176,25 @@ pub const table = &[_]Entry{ .{ .cmovz, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, .{ .cmovz, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long }, - .{ .cmp, .zi, .al, .imm8, .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none }, - .{ .cmp, .zi, .ax, .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, - .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, - .{ .cmp, .zi, .rax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, - .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, - .{ .cmp, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, - .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, - .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, - .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, - .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, - .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, - .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, - .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, - .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, + .{ .cmp, .zi, .al, .imm8, .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .ax, .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .rax, .imm32s, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, + .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, + .{ .cmp, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, + .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, + .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none }, .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, @@ -214,19 +214,19 @@ pub const table = &[_]Entry{ .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long }, - .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, - .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, - .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, - .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, - .{ .imul, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, - .{ .imul, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, - .{ .imul, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .long }, - .{ .imul, .rmi, .r16, .rm16, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r32, .rm32, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r64, .rm64, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .long }, - .{ .imul, .rmi, .r16, .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r32, .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r64, .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long }, + .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, + .{ .imul, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm8s, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm8s, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm8s, .none, 1, 0x6b, 0x00, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long }, .{ .int3, .np, .none, .none, .none, .none, 1, 0xcc, 0x00, 0x00, 0, .none }, @@ -269,34 +269,34 @@ pub const table = &[_]Entry{ .{ .lea, .rm, .r32, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none }, .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long }, - .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, - .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, - .{ .mov, .mr, .rm16, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm64, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long }, - .{ .mov, .rm, .sreg, .rm16, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .sreg, .rm64, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long }, - .{ .mov, .fd, .al, .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none }, - .{ .mov, .fd, .ax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, - .{ .mov, .fd, .eax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, - .{ .mov, .fd, .rax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long }, - .{ .mov, .td, .moffs, .al, .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none }, - .{ .mov, .td, .moffs, .ax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, - .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, - .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, - .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, - .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, - .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, - .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, - .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, - .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, - .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, - .{ .mov, .mi, .rm64, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, + .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, + .{ .mov, .mr, .rm16, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .sreg, .rm16, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .sreg, .rm64, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long }, + .{ .mov, .fd, .al, .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .ax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .eax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .rax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long }, + .{ .mov, .td, .moffs, .al, .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .ax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, + .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, + .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm64, .imm32s, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, @@ -321,25 +321,25 @@ pub const table = &[_]Entry{ .{ .nop, .np, .none, .none, .none, .none, 1, 0x90, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .al, .imm8, .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .ax, .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .rax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, - .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, - .{ .@"or", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, - .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, - .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, - .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, - .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, - .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, - .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, - .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, - .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, + .{ .@"or", .zi, .al, .imm8, .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .ax, .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .rax, .imm32s, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, + .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, + .{ .@"or", .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, + .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, + .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, .{ .pop, .o, .r16, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, .{ .pop, .o, .r64, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, @@ -382,25 +382,25 @@ pub const table = &[_]Entry{ .{ .sar, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, .{ .sar, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long }, - .{ .sbb, .zi, .al, .imm8, .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none }, - .{ .sbb, .zi, .ax, .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, - .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, - .{ .sbb, .zi, .rax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, - .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, - .{ .sbb, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, - .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, - .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, - .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, - .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, - .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, - .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, - .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, - .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, + .{ .sbb, .zi, .al, .imm8, .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .ax, .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .rax, .imm32s, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, + .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, + .{ .sbb, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, + .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, + .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, @@ -459,62 +459,62 @@ pub const table = &[_]Entry{ .{ .shr, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, .{ .shr, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long }, - .{ .sub, .zi, .al, .imm8, .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none }, - .{ .sub, .zi, .ax, .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, - .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, - .{ .sub, .zi, .rax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, - .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, - .{ .sub, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, - .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, - .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, - .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, - .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, - .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, - .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, - .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, - .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, + .{ .sub, .zi, .al, .imm8, .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .ax, .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .rax, .imm32s, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, + .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, + .{ .sub, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, + .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, + .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, .{ .syscall, .np, .none, .none, .none, .none, 2, 0x0f, 0x05, 0x00, 0, .none }, - .{ .@"test", .zi, .al, .imm8, .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none }, - .{ .@"test", .zi, .ax, .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, - .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, - .{ .@"test", .zi, .rax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, - .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, - .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, - .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, - .{ .@"test", .mi, .rm64, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, - .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, - .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, - .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, - .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, + .{ .@"test", .zi, .al, .imm8, .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .ax, .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .rax, .imm32s, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, + .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm64, .imm32s, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, + .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, - .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none }, + .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none }, - .{ .xor, .zi, .al, .imm8, .none, .none, 1, 0x34, 0x00, 0x00, 0, .none }, - .{ .xor, .zi, .ax, .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, - .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, - .{ .xor, .zi, .rax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, - .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, - .{ .xor, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, - .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, - .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, - .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, - .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, - .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, - .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, - .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, - .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, + .{ .xor, .zi, .al, .imm8, .none, .none, 1, 0x34, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .ax, .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .rax, .imm32s, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, + .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, + .{ .xor, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, + .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, + .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, // SSE .{ .addss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x58, 0, .sse }, @@ -540,3 +540,4 @@ pub const table = &[_]Entry{ .{ .ucomisd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0x66, 0x0f, 0x2e, 0, .sse2 }, }; // zig fmt: on + From 292f91aef2d2ed360f4dbb1f8cf6e22500a682ca Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 7 Mar 2023 16:59:38 +0100 Subject: [PATCH 082/294] Handle .ah vs .spl register aliases --- src/arch/x86_64/Encoding.zig | 77 +++++++++++++++++++++++++--------- src/arch/x86_64/encoder.zig | 22 ++++------ src/arch/x86_64/encodings.zig | 78 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 34 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 5d3944a554..3c2e9f848c 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -29,12 +29,42 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { op2: Instruction.Operand, op3: Instruction.Operand, op4: Instruction.Operand, -}) ?Encoding { +}) !?Encoding { const input_op1 = Op.fromOperand(args.op1); const input_op2 = Op.fromOperand(args.op2); const input_op3 = Op.fromOperand(args.op3); const input_op4 = Op.fromOperand(args.op4); + const ops = &[_]Instruction.Operand{ args.op1, args.op2, args.op3, args.op4 }; + const rex_required = for (ops) |op| switch (op) { + .reg => |r| switch (r) { + .spl, .bpl, .sil, .dil => break true, + else => {}, + }, + else => {}, + } else false; + const rex_invalid = for (ops) |op| switch (op) { + .reg => |r| switch (r) { + .ah, .bh, .ch, .dh => break true, + else => {}, + }, + else => {}, + } else false; + const rex_extended = for (ops) |op| switch (op) { + .reg => |r| if (r.isExtended()) break true, + .mem => |m| { + if (m.base()) |base| { + if (base.isExtended()) break true; + } + if (m.scaleIndex()) |si| { + if (si.index.isExtended()) break true; + } + }, + else => {}, + } else false; + + if ((rex_required or rex_extended) and rex_invalid) return error.CannotEncode; + // TODO work out what is the maximum number of variants we can actually find in one swoop. var candidates: [10]Encoding = undefined; var count: usize = 0; @@ -57,13 +87,24 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { input_op3.isSubset(enc.op3, enc.mode) and input_op4.isSubset(enc.op4, enc.mode)) { - candidates[count] = enc; - count += 1; + if (rex_required) { + switch (enc.mode) { + .rex, .long => { + candidates[count] = enc; + count += 1; + }, + else => {}, + } + } else { + if (enc.mode != .rex) { + candidates[count] = enc; + count += 1; + } + } } } if (count == 0) return null; - if (count == 1) return candidates[0]; const EncodingLength = struct { fn estimate(encoding: Encoding, params: struct { @@ -71,7 +112,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { op2: Instruction.Operand, op3: Instruction.Operand, op4: Instruction.Operand, - }) usize { + }) !usize { var inst = Instruction{ .op1 = params.op1, .op2 = params.op2, @@ -91,7 +132,13 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { } = null; var i: usize = 0; while (i < count) : (i += 1) { - const len = EncodingLength.estimate(candidates[i], .{ + const candidate = candidates[i]; + switch (candidate.mode) { + .long, .rex => if (rex_invalid) return error.CannotEncode, + else => {}, + } + + const len = try EncodingLength.estimate(candidate, .{ .op1 = args.op1, .op2 = args.op2, .op3 = args.op3, @@ -136,20 +183,11 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct { if (match) { if (prefixes.rex.w) { switch (enc.mode) { - .fpu, .sse, .sse2 => {}, - .long => return enc, - .none => { - // TODO this is a hack to allow parsing of instructions which contain - // spurious prefix bytes such as - // rex.W mov dil, 0x1 - // Here, rex.W is not needed. - const rex_w_allowed = blk: { - const bit_size = enc.operandBitSize(); - break :blk bit_size == 64 or bit_size == 8; - }; - if (rex_w_allowed) return enc; - }, + .fpu, .sse, .sse2, .none => {}, + .long, .rex => return enc, } + } else if (prefixes.rex.present and !prefixes.rex.isSet()) { + if (enc.mode == .rex) return enc; } else if (prefixes.legacy.prefix_66) { switch (enc.operandBitSize()) { 16 => return enc, @@ -542,6 +580,7 @@ pub const Op = enum { pub const Mode = enum { none, fpu, + rex, long, sse, sse2, diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index d4ca925891..066a733960 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -102,12 +102,12 @@ pub const Instruction = struct { op3: Operand = .none, op4: Operand = .none, }) !Instruction { - const encoding = Encoding.findByMnemonic(mnemonic, .{ + const encoding = (try Encoding.findByMnemonic(mnemonic, .{ .op1 = args.op1, .op2 = args.op2, .op3 = args.op3, .op4 = args.op4, - }) orelse { + })) orelse { std.log.debug("{s} {s} {s} {s} {s}", .{ @tagName(mnemonic), @tagName(Encoding.Op.fromOperand(args.op1)), @@ -251,13 +251,8 @@ pub const Instruction = struct { fn encodeRexPrefix(inst: Instruction, encoder: anytype) !void { const op_en = inst.encoding.op_en; - // Check if we need REX and can actually encode it - const is_rex_invalid = for (&[_]Operand{ inst.op1, inst.op2, inst.op3, inst.op4 }) |op| switch (op) { - .reg => |r| if (r.isRexInvalid()) break true, - else => {}, - } else false; - var rex = Rex{}; + rex.present = inst.encoding.mode == .rex; rex.w = inst.encoding.mode == .long; switch (op_en) { @@ -293,8 +288,6 @@ pub const Instruction = struct { }, } - if (rex.isSet() and is_rex_invalid) return error.CannotEncode; - try encoder.rex(rex); } @@ -507,9 +500,9 @@ fn Encoder(comptime T: type) type { /// or one of reg, index, r/m, base, or opcode-reg might be extended. /// /// See struct `Rex` for a description of each field. - /// - /// Does not add a prefix byte if none of the fields are set! pub fn rex(self: Self, byte: Rex) !void { + if (!byte.present and !byte.isSet()) return; + var value: u8 = 0b0100_0000; if (byte.w) value |= 0b1000; @@ -517,9 +510,7 @@ fn Encoder(comptime T: type) type { if (byte.x) value |= 0b0010; if (byte.b) value |= 0b0001; - if (value != 0b0100_0000) { - try self.writer.writeByte(value); - } + try self.writer.writeByte(value); } // ------ @@ -788,6 +779,7 @@ pub const Rex = struct { r: bool = false, x: bool = false, b: bool = false, + present: bool = false, pub fn isSet(rex: Rex) bool { return rex.w or rex.r or rex.x or rex.b; diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig index c3fb347f22..b008eb9f3e 100644 --- a/src/arch/x86_64/encodings.zig +++ b/src/arch/x86_64/encodings.zig @@ -18,6 +18,7 @@ pub const table = &[_]Entry{ .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, .{ .adc, .zi, .rax, .imm32s, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .rex }, .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, .{ .adc, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, @@ -25,10 +26,12 @@ pub const table = &[_]Entry{ .{ .adc, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, .{ .adc, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .rex }, .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .rex }, .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, @@ -38,6 +41,7 @@ pub const table = &[_]Entry{ .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, .{ .add, .zi, .rax, .imm32s, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .rex }, .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, .{ .add, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, @@ -45,10 +49,12 @@ pub const table = &[_]Entry{ .{ .add, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, .{ .add, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .rex }, .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .rex }, .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, @@ -58,6 +64,7 @@ pub const table = &[_]Entry{ .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, .{ .@"and", .zi, .rax, .imm32s, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .rex }, .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, .{ .@"and", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, @@ -65,10 +72,12 @@ pub const table = &[_]Entry{ .{ .@"and", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, .{ .@"and", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .rex }, .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .rex }, .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, @@ -181,6 +190,7 @@ pub const table = &[_]Entry{ .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, .{ .cmp, .zi, .rax, .imm32s, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .rex }, .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, .{ .cmp, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, @@ -188,15 +198,18 @@ pub const table = &[_]Entry{ .{ .cmp, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, .{ .cmp, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .rex }, .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .rex }, .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .rex }, .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, .{ .div, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, .{ .div, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .long }, @@ -210,11 +223,13 @@ pub const table = &[_]Entry{ .{ .fld, .m, .m80, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 5, .fpu }, .{ .idiv, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .rex }, .{ .idiv, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long }, .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .rex }, .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, @@ -270,10 +285,12 @@ pub const table = &[_]Entry{ .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long }, .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .rex }, .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .rex }, .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, @@ -290,16 +307,20 @@ pub const table = &[_]Entry{ .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .rex }, .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .rex }, .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, .{ .mov, .mi, .rm64, .imm32s, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .rex }, .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .rex }, .{ .movsx, .rm, .r64, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .long }, .{ .movsx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .none }, .{ .movsx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .long }, @@ -315,6 +336,7 @@ pub const table = &[_]Entry{ .{ .movzx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .long }, .{ .mul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .rex }, .{ .mul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, .{ .mul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, .{ .mul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .long }, @@ -326,6 +348,7 @@ pub const table = &[_]Entry{ .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, .{ .@"or", .zi, .rax, .imm32s, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .rex }, .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, .{ .@"or", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, @@ -333,10 +356,12 @@ pub const table = &[_]Entry{ .{ .@"or", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, .{ .@"or", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .rex }, .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .rex }, .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, @@ -357,27 +382,33 @@ pub const table = &[_]Entry{ .{ .ret, .np, .none, .none, .none, .none, 1, 0xc3, 0x00, 0x00, 0, .none }, .{ .sal, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .rex }, .{ .sal, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .sal, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .sal, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, .{ .sal, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .rex }, .{ .sal, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .sal, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .sal, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, .{ .sal, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .rex }, .{ .sal, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .sal, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .sal, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, .{ .sar, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .rex }, .{ .sar, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, .{ .sar, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, .{ .sar, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .long }, .{ .sar, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 7, .rex }, .{ .sar, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, .{ .sar, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, .{ .sar, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .long }, .{ .sar, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 7, .rex }, .{ .sar, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, .{ .sar, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, .{ .sar, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long }, @@ -387,6 +418,7 @@ pub const table = &[_]Entry{ .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, .{ .sbb, .zi, .rax, .imm32s, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .rex }, .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, .{ .sbb, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, @@ -394,67 +426,105 @@ pub const table = &[_]Entry{ .{ .sbb, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, .{ .sbb, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .rex }, .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .rex }, .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .rex }, .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .rex }, .{ .setb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .rex }, .{ .setbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .rex }, .{ .setc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .rex }, .{ .sete, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + .{ .sete, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .rex }, .{ .setg, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setg, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .rex }, .{ .setge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .rex }, .{ .setl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .rex }, .{ .setle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .rex }, .{ .setna, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setna, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .rex }, .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .rex }, .{ .setnb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setnb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .rex }, .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .rex }, .{ .setnc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setnc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .rex }, .{ .setne, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .setne, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .rex }, .{ .setng, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setng, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .rex }, .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .rex }, .{ .setnl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setnl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .rex }, .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .rex }, .{ .setno, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .none }, + .{ .setno, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .rex }, .{ .setnp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .setnp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .rex }, .{ .setns, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .none }, + .{ .setns, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .rex }, .{ .setnz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .setnz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .rex }, .{ .seto, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .none }, + .{ .seto, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .rex }, .{ .setp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .rex }, .{ .setpe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setpe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .rex }, .{ .setpo, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .setpo, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .rex }, .{ .sets, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .none }, + .{ .sets, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .rex }, .{ .setz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + .{ .setz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .rex }, .{ .shl, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .rex }, .{ .shl, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .shl, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .shl, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, .{ .shl, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .rex }, .{ .shl, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .shl, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .shl, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, .{ .shl, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .rex }, .{ .shl, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .shl, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .shl, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, .{ .shr, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .rex }, .{ .shr, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, .{ .shr, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, .{ .shr, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .long }, .{ .shr, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 5, .rex }, .{ .shr, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, .{ .shr, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, .{ .shr, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .long }, .{ .shr, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 5, .rex }, .{ .shr, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, .{ .shr, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, .{ .shr, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long }, @@ -464,6 +534,7 @@ pub const table = &[_]Entry{ .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, .{ .sub, .zi, .rax, .imm32s, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .rex }, .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, .{ .sub, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, @@ -471,10 +542,12 @@ pub const table = &[_]Entry{ .{ .sub, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, .{ .sub, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .rex }, .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .rex }, .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, @@ -486,10 +559,12 @@ pub const table = &[_]Entry{ .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, .{ .@"test", .zi, .rax, .imm32s, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .rex }, .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, .{ .@"test", .mi, .rm64, .imm32s, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .rex }, .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, @@ -501,6 +576,7 @@ pub const table = &[_]Entry{ .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, .{ .xor, .zi, .rax, .imm32s, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .rex }, .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, .{ .xor, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, @@ -508,10 +584,12 @@ pub const table = &[_]Entry{ .{ .xor, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, .{ .xor, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .rex }, .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .rex }, .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, From 219c1261a5f46636425be50fdc6e82534bb003a4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 7 Mar 2023 20:22:45 +0100 Subject: [PATCH 083/294] x86_64: all behavior tests passing --- src/arch/x86_64/CodeGen.zig | 58 +++++++++++++++++++----------------- src/arch/x86_64/Emit.zig | 7 ++++- src/arch/x86_64/Encoding.zig | 4 +++ src/arch/x86_64/encoder.zig | 2 +- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 90286a1bb2..fe44adcfa1 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2785,7 +2785,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type // introduce new MIR tag specifically for mov [reg + 0], imm const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, - .operand = @intCast(u32, imm), + .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5385,31 +5385,33 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE return self.genSetStackArg(ty, stack_offset, .{ .register = reg }); }, .immediate => |imm| { + _ = imm; switch (abi_size) { - 1, 2, 4 => { - // We have a positive stack offset value but we want a twos complement negative - // offset from rbp, which is at the top of the stack frame. - // mov [rbp+offset], immediate - const flags: u2 = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }; - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @intCast(u32, imm), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rsp, - .flags = flags, - }), - .data = .{ .payload = payload }, - }); - }, - 8 => { + // TODO + // 1, 2, 4 => { + // // We have a positive stack offset value but we want a twos complement negative + // // offset from rbp, which is at the top of the stack frame. + // // mov [rbp+offset], immediate + // const flags: u2 = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // else => unreachable, + // }; + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @intCast(u32, imm), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rsp, + // .flags = flags, + // }), + // .data = .{ .payload = payload }, + // }); + // }, + 1, 2, 4, 8 => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); }, @@ -5543,7 +5545,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @intCast(u32, x_big), + .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5557,7 +5559,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl 1, 2, 4 => { const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @intCast(u32, x_big), + .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -7020,7 +7022,7 @@ fn truncateRegister(self: *Self, ty: Type, reg: Register) !void { .unsigned => { const shift = @intCast(u6, max_reg_bit_width - int_info.bits); const mask = (~@as(u64, 0)) >> shift; - if (int_info.bits <= 32) { + if (int_info.bits < 32) { try self.genBinOpMir(.@"and", Type.usize, .{ .register = reg }, .{ .immediate = mask }); } else { const tmp_reg = try self.copyToTmpRegister(Type.usize, .{ .immediate = mask }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e23b500fb8..e69601c40b 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -612,12 +612,17 @@ fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.In 0b10 => .dword, 0b11 => .qword, }; + const imm = switch (ops.flags) { + 0b00 => @truncate(u8, imm_pair.operand), + 0b01 => @truncate(u16, imm_pair.operand), + 0b10, 0b11 => @truncate(u32, imm_pair.operand), + }; return emit.encode(mnemonic, .{ .op1 = .{ .mem = Memory.sib(ptr_size, .{ .disp = imm_pair.dest_off, .base = ops.reg1, }) }, - .op2 = .{ .imm = Immediate.u(imm_pair.operand) }, + .op2 = .{ .imm = Immediate.u(imm) }, }); } diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 3c2e9f848c..635a09512b 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -544,6 +544,10 @@ pub const Op = enum { } if (op.isImmediate() and target.isImmediate()) { switch (target) { + .imm64 => switch (op) { + .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s, .imm32, .imm64 => return true, + else => return op == target, + }, .imm32s, .rel32 => switch (op) { .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s => return true, else => return op == target, diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 066a733960..925e3fe181 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -108,7 +108,7 @@ pub const Instruction = struct { .op3 = args.op3, .op4 = args.op4, })) orelse { - std.log.debug("{s} {s} {s} {s} {s}", .{ + std.log.warn("{s} {s} {s} {s} {s}", .{ @tagName(mnemonic), @tagName(Encoding.Op.fromOperand(args.op1)), @tagName(Encoding.Op.fromOperand(args.op2)), From 5b3770102845b17207f85d3a43a3b6cab551a329 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 8 Mar 2023 09:49:59 +0100 Subject: [PATCH 084/294] x86_64: refactor immediate selection logic --- src/arch/x86_64/Encoding.zig | 59 +++++++++++++++++------------------- src/arch/x86_64/bits.zig | 4 +-- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 635a09512b..94f816eaa1 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -453,7 +453,8 @@ pub const Op = enum { pub fn bitSize(op: Op) u64 { return switch (op) { .none, .o16, .o32, .o64, .moffs, .m, .sreg => unreachable, - .unity, .imm8, .imm8s, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, + .unity => 1, + .imm8, .imm8s, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, .imm16, .imm16s, .ax, .r16, .m16, .rm16, .rel16 => 16, .imm32, .imm32s, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64, @@ -462,6 +463,18 @@ pub const Op = enum { }; } + pub fn isSigned(op: Op) bool { + return switch (op) { + .unity, .imm8, .imm16, .imm32, .imm64 => false, + .imm8s, .imm16s, .imm32s => true, + else => unreachable, + }; + } + + pub fn isUnsigned(op: Op) bool { + return !op.isSigned(); + } + pub fn isRegister(op: Op) bool { // zig fmt: off return switch (op) { @@ -516,8 +529,7 @@ pub const Op = enum { }; } - /// Given an operand `op` checks if `target` is a subset for the purposes - /// of the encoding. + /// Given an operand `op` checks if `target` is a subset for the purposes of the encoding. pub fn isSubset(op: Op, target: Op, mode: Mode) bool { switch (op) { .m, .o16, .o32, .o64 => unreachable, @@ -544,36 +556,19 @@ pub const Op = enum { } if (op.isImmediate() and target.isImmediate()) { switch (target) { - .imm64 => switch (op) { - .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s, .imm32, .imm64 => return true, - else => return op == target, - }, - .imm32s, .rel32 => switch (op) { - .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s => return true, - else => return op == target, - }, - .imm32 => switch (op) { - .unity, .imm8, .imm8s, .imm16, .imm16s, .imm32, .imm32s => return true, - else => return op == target, - }, - .imm16s, .rel16 => switch (op) { - .unity, .imm8s, .imm8, .imm16s => return true, - else => return op == target, - }, - .imm16 => switch (op) { - .unity, .imm8, .imm8s, .imm16, .imm16s => return true, - else => return op == target, - }, - .imm8s, .rel8 => switch (op) { - .unity, .imm8s => return true, - else => return op == target, - }, - .imm8 => switch (op) { - .unity, .imm8, .imm8s => return true, - else => return op == target, - }, - else => return op == target, + .imm64 => if (op.bitSize() <= 64) return true, + .imm32s, .rel32 => if (op.bitSize() < 32 or (op.bitSize() == 32 and op.isSigned())) + return true, + .imm32 => if (op.bitSize() <= 32) return true, + .imm16s, .rel16 => if (op.bitSize() < 16 or (op.bitSize() == 16 and op.isSigned())) + return true, + .imm16 => if (op.bitSize() <= 16) return true, + .imm8s, .rel8 => if (op.bitSize() < 8 or (op.bitSize() == 8 and op.isSigned())) + return true, + .imm8 => if (op.bitSize() <= 8) return true, + else => {}, } + return op == target; } return false; }, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index cd7ed91849..ad9a6f7f23 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -525,13 +525,13 @@ pub const Immediate = union(enum) { pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 { return switch (imm) { .signed => |x| switch (bit_size) { - 8 => @bitCast(u8, @intCast(i8, x)), + 1, 8 => @bitCast(u8, @intCast(i8, x)), 16 => @bitCast(u16, @intCast(i16, x)), 32 => @bitCast(u32, @intCast(i32, x)), else => unreachable, }, .unsigned => |x| switch (bit_size) { - 8 => @intCast(u8, x), + 1, 8 => @intCast(u8, x), 16 => @intCast(u16, x), 32 => @intCast(u32, x), 64 => x, From 6e882d730b2ed1f2494e7b28f1fed2726e4a1ac0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 8 Mar 2023 23:45:05 +0100 Subject: [PATCH 085/294] x86_64: introduce assemble() helper which encodes/decodes into MIR -> Instruction --- src/arch/x86_64/CodeGen.zig | 2154 ++++++++++++++++++----------------- src/arch/x86_64/Emit.zig | 1411 +++++++---------------- src/arch/x86_64/Mir.zig | 688 ++++------- src/arch/x86_64/encoder.zig | 11 +- 4 files changed, 1750 insertions(+), 2514 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index fe44adcfa1..be8d07a2f6 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -374,71 +374,99 @@ pub fn generate( fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { const gpa = self.gpa; try self.mir_instructions.ensureUnusedCapacity(gpa, 1); - const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); + const result_index = @intCast(Mir.Inst.Index, self.mir_instructions.len); self.mir_instructions.appendAssumeCapacity(inst); return result_index; } -pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { +fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { const fields = std.meta.fields(@TypeOf(extra)); try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len); return self.addExtraAssumeCapacity(extra); } -fn extraData(self: *Self, comptime T: type, index: u32) struct { data: T, end: u32 } { - const fields = std.meta.fields(T); - var i: u32 = index; - var result: T = undefined; - inline for (fields) |field| { - @field(result, field.name) = switch (field.type) { - u32 => self.mir_extra.items[i], - i32 => @bitCast(i32, self.mir_extra.items[i]), - else => @compileError("bad field type"), - }; - i += 1; - } - return .{ - .data = result, - .end = i, - }; -} - -pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { +fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { const fields = std.meta.fields(@TypeOf(extra)); const result = @intCast(u32, self.mir_extra.items.len); inline for (fields) |field| { self.mir_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), i32 => @bitCast(u32, @field(extra, field.name)), - else => @compileError("bad field type"), + else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)), }); } return result; } +fn assemble(self: *Self, tag: Mir.Inst.Tag, args: struct { + op1: Mir.Operand = .none, + op2: Mir.Operand = .none, + op3: Mir.Operand = .none, + op4: Mir.Operand = .none, +}) !void { + const ops: Mir.Inst.Ops = blk: { + if (args.op1 == .none and args.op2 == .none and args.op3 == .none and args.op4 == .none) + break :blk .none; + + if (args.op1 == .reg and args.op2 == .reg) + break :blk .rr; + if (args.op1 == .reg and args.op2 == .imm) switch (args.op2.imm) { + .signed => break :blk .ri_s, + .unsigned => break :blk .ri_u, + }; + if (args.op1 == .reg) + break :blk .r; + if (args.op1 == .imm) switch (args.op1.imm) { + .signed => break :blk .imm_s, + .unsigned => break :blk .imm_u, // TODO 64bits + }; + + unreachable; + }; + const data: Mir.Inst.Data = switch (ops) { + .none => undefined, + .imm_s => .{ .imm_s = args.op1.imm.signed }, + .imm_u => .{ .imm_u = @intCast(u32, args.op1.imm.unsigned) }, + .r => .{ .r = args.op1.reg }, + .rr => .{ .rr = .{ + .r1 = args.op1.reg, + .r2 = args.op2.reg, + } }, + .ri_s => .{ .ri_s = .{ + .r1 = args.op1.reg, + .imm = args.op2.imm.signed, + } }, + .ri_u => .{ .ri_u = .{ + .r1 = args.op1.reg, + .imm = @intCast(u32, args.op2.imm.unsigned), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn gen(self: *Self) InnerError!void { const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { - _ = try self.addInst(.{ - .tag = .push, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), - .data = undefined, // unused for push reg, + try self.assemble(.push, .{ + .op1 = .{ .reg = .rbp }, }); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .reg2 = .rsp, - }), - .data = undefined, + try self.assemble(.mov, .{ + .op1 = .{ .reg = .rbp }, + .op2 = .{ .reg = .rsp }, }); + // We want to subtract the aligned stack frame size from rsp here, but we don't // yet know how big it will be, so we leave room for a 4-byte stack size. // TODO During semantic analysis, check if there are no function calls. If there // are none, here we can omit the part where we subtract and then add rsp. const backpatch_stack_sub = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); @@ -465,7 +493,7 @@ fn gen(self: *Self) InnerError!void { // Push callee-preserved regs that were used actually in use. const backpatch_push_callee_preserved_regs = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); @@ -496,7 +524,7 @@ fn gen(self: *Self) InnerError!void { // Pop saved callee-preserved regs. const backpatch_pop_callee_preserved_regs = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); @@ -509,21 +537,12 @@ fn gen(self: *Self) InnerError!void { // Maybe add rsp, x if required. This is backpatched later. const backpatch_stack_add = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); - _ = try self.addInst(.{ - .tag = .pop, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), - .data = undefined, - }); - - _ = try self.addInst(.{ - .tag = .ret, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), - .data = undefined, - }); + try self.assemble(.pop, .{ .op1 = .{ .reg = .rbp } }); + try self.assemble(.ret, .{}); // Adjust the stack if (self.max_end_stack > math.maxInt(i32)) { @@ -537,27 +556,34 @@ fn gen(self: *Self) InnerError!void { if (aligned_stack_end > 0) { self.mir_instructions.set(backpatch_stack_sub, .{ .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = aligned_stack_end }, + .ops = .ri_u, + .data = .{ .ri_u = .{ + .r1 = .rsp, + .imm = aligned_stack_end, + } }, }); self.mir_instructions.set(backpatch_stack_add, .{ .tag = .add, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = aligned_stack_end }, + .ops = .ri_u, + .data = .{ .ri_u = .{ + .r1 = .rsp, + .imm = aligned_stack_end, + } }, }); const save_reg_list = try self.addExtra(Mir.SaveRegisterList{ + .base_reg = @enumToInt(Register.rbp), .register_list = reg_list.asInt(), .stack_end = aligned_stack_end, }); self.mir_instructions.set(backpatch_push_callee_preserved_regs, .{ .tag = .push_regs, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), + .ops = undefined, .data = .{ .payload = save_reg_list }, }); self.mir_instructions.set(backpatch_pop_callee_preserved_regs, .{ .tag = .pop_regs, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), + .ops = undefined, .data = .{ .payload = save_reg_list }, }); } @@ -1306,14 +1332,15 @@ fn airMin(self: *Self, inst: Air.Inst.Index) !void { .unsigned => .b, .signed => .l, }; - _ = try self.addInst(.{ - .tag = .cond_mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_mcv.register, - .reg2 = lhs_reg, - }), - .data = .{ .cc = cc }, - }); + _ = cc; + // _ = try self.addInst(.{ + // .tag = .cond_mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_mcv.register, + // .reg2 = lhs_reg, + // }), + // .data = .{ .cc = cc }, + // }); break :result dst_mcv; }; @@ -1513,13 +1540,14 @@ fn genSetStackTruncatedOverflowCompare( .signed => .o, .unsigned => .c, }; - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = overflow_reg.to8(), - }), - .data = .{ .cc = cc }, - }); + _ = cc; + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = overflow_reg.to8(), + // }), + // .data = .{ .cc = cc }, + // }); const scratch_reg = temp_regs[1]; try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg }); @@ -1532,11 +1560,11 @@ fn genSetStackTruncatedOverflowCompare( ); const eq_reg = temp_regs[2]; - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = eq_reg.to8() }), - .data = .{ .cc = .ne }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = eq_reg.to8() }), + // .data = .{ .cc = .ne }, + // }); try self.genBinOpMir( .@"or", @@ -1680,25 +1708,26 @@ fn genIntMulDivOpMir( try self.genSetReg(ty, .rax, lhs); } - switch (signedness) { - .signed => { - _ = try self.addInst(.{ - .tag = .cwd, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), - .data = undefined, - }); - }, - .unsigned => { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rdx, - .reg2 = .rdx, - }), - .data = undefined, - }); - }, - } + _ = signedness; + // switch (signedness) { + // .signed => { + // _ = try self.addInst(.{ + // .tag = .cwd, + // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), + // .data = undefined, + // }); + // }, + // .unsigned => { + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rdx, + // .reg2 = .rdx, + // }), + // .data = undefined, + // }); + // }, + // } const factor = switch (rhs) { .register => rhs, @@ -1708,33 +1737,35 @@ fn genIntMulDivOpMir( break :blk MCValue{ .register = reg }; }, }; + _ = factor; + _ = tag; - switch (factor) { - .register => |reg| { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = undefined, - }); - }, - .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg2 = .rbp, - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - 8 => 0b11, - else => unreachable, - }, - }), - .data = .{ .disp = -off }, - }); - }, - else => unreachable, - } + // switch (factor) { + // .register => |reg| { + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), + // .data = undefined, + // }); + // }, + // .stack_offset => |off| { + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg2 = .rbp, + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // 8 => 0b11, + // else => unreachable, + // }, + // }), + // .data = .{ .disp = -off }, + // }); + // }, + // else => unreachable, + // } } /// Always returns a register. @@ -1760,38 +1791,38 @@ fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCVa .unsigned => .div, }, Type.isize, signedness, .{ .register = dividend }, .{ .register = divisor }); - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = divisor.to64(), - .reg2 = dividend.to64(), - }), - .data = undefined, - }); - _ = try self.addInst(.{ - .tag = .sar, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = divisor.to64(), - .flags = 0b10, - }), - .data = .{ .imm = 63 }, - }); - _ = try self.addInst(.{ - .tag = .@"test", - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rdx, - .reg2 = .rdx, - }), - .data = undefined, - }); - _ = try self.addInst(.{ - .tag = .cond_mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = divisor.to64(), - .reg2 = .rdx, - }), - .data = .{ .cc = .e }, - }); + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = divisor.to64(), + // .reg2 = dividend.to64(), + // }), + // .data = undefined, + // }); + // _ = try self.addInst(.{ + // .tag = .sar, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = divisor.to64(), + // .flags = 0b10, + // }), + // .data = .{ .imm = 63 }, + // }); + // _ = try self.addInst(.{ + // .tag = .@"test", + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rdx, + // .reg2 = .rdx, + // }), + // .data = undefined, + // }); + // _ = try self.addInst(.{ + // .tag = .cond_mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = divisor.to64(), + // .reg2 = .rdx, + // }), + // .data = .{ .cc = .e }, + // }); try self.genBinOpMir(.add, Type.isize, .{ .register = divisor }, .{ .register = .rax }); return MCValue{ .register = divisor }; } @@ -2226,16 +2257,17 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue { const addr_reg = try self.register_manager.allocReg(null, gp); switch (slice_mcv) { .stack_offset => |off| { + _ = off; // mov reg, [rbp - 8] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -@intCast(i32, off) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -@intCast(i32, off) }, + // }); }, else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}), } @@ -2312,25 +2344,26 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { )); try self.genSetStack(array_ty, off, array, .{}); // lea reg, [rbp] - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .stack_offset => |off| { + _ = off; // lea reg, [rbp] - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .memory, .linker_load => { try self.loadMemPtrIntoRegister(addr_reg, Type.usize, array); @@ -2388,15 +2421,15 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO copy value with size {} from pointer", .{elem_abi_size}); } else { // mov dst_mcv, [dst_mcv] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)), - .reg2 = dst_mcv.register, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)), + // .reg2 = dst_mcv.register, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) }; } }; @@ -2650,16 +2683,17 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .undef => unreachable, .eflags => unreachable, .register => |dst_reg| { + _ = dst_reg; // mov dst_reg, [reg] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), - .reg2 = reg, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), + // .reg2 = reg, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); }, .stack_offset => |off| { if (abi_size <= 8) { @@ -2724,19 +2758,22 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue .direct => 0b01, .import => 0b10, }; - _ = try self.addInst(.{ - .tag = .lea_pic, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = flags, - }), - .data = .{ - .relocation = .{ - .atom_index = atom_index, - .sym_index = load_struct.sym_index, - }, - }, - }); + _ = abi_size; + _ = atom_index; + _ = flags; + // _ = try self.addInst(.{ + // .tag = .lea_pic, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = flags, + // }), + // .data = .{ + // .relocation = .{ + // .atom_index = atom_index, + // .sym_index = load_struct.sym_index, + // }, + // }, + // }); }, .memory => |addr| { // TODO: in case the address fits in an imm32 we can use [ds:imm32] @@ -2779,27 +2816,28 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.genSetReg(value_ty, reg, value); }, .immediate => |imm| { + _ = imm; switch (abi_size) { 1, 2, 4 => { // TODO this is wasteful! // introduce new MIR tag specifically for mov [reg + 0], imm - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = 0, - .operand = @truncate(u32, imm), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = 0, + // .operand = @truncate(u32, imm), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // else => unreachable, + // }, + // }), + // .data = .{ .payload = payload }, + // }); }, 8 => { // TODO: optimization: if the imm is only using the lower @@ -2829,13 +2867,13 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type const overflow_bit_ty = value_ty.structFieldType(1); const overflow_bit_offset = value_ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg.to8(), - }), - .data = .{ .cc = ro.eflags }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg.to8(), + // }), + // .data = .{ .cc = ro.eflags }, + // }); try self.genInlineMemcpyRegisterRegister( overflow_bit_ty, reg, @@ -2878,15 +2916,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type // to get the actual address of the value we want to modify we have to go through the GOT // mov reg, [reg] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = addr_reg.to64(), - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = addr_reg.to64(), + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); const new_ptr = MCValue{ .register = addr_reg.to64() }; @@ -2896,11 +2934,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = 0, - // TODO check if this logic is correct - .operand = @intCast(u32, imm), - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = 0, + // // TODO check if this logic is correct + // .operand = @intCast(u32, imm), + // }); const flags: u2 = switch (abi_size) { 1 => 0b00, 2 => 0b01, @@ -2919,14 +2957,14 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO imm64 would get incorrectly sign extended", .{}); } } - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .flags = flags, - }), - .data = .{ .payload = payload }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .flags = flags, + // }), + // .data = .{ .payload = payload }, + // }); }, .register => { return self.store(new_ptr, value, ptr_ty, value_ty); @@ -2939,15 +2977,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.loadMemPtrIntoRegister(tmp_reg, value_ty, value); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg, - .reg2 = tmp_reg, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg, + // .reg2 = tmp_reg, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } @@ -3109,14 +3147,14 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { }; const field_size = @intCast(u32, struct_field_ty.abiSize(self.target.*)); if (signedness == .signed and field_size < 8) { - _ = try self.addInst(.{ - .tag = .mov_sign_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_mcv.register, - .reg2 = registerAlias(dst_mcv.register, field_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov_sign_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_mcv.register, + // .reg2 = registerAlias(dst_mcv.register, field_size), + // }), + // .data = undefined, + // }); } break :result dst_mcv; @@ -3133,13 +3171,13 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { defer self.register_manager.unlockReg(reg_lock); const dst_reg = try self.register_manager.allocReg(inst, gp); - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg.to8(), - }), - .data = .{ .cc = ro.eflags }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg.to8(), + // }), + // .data = .{ .cc = ro.eflags }, + // }); break :result MCValue{ .register = dst_reg.to8() }; }, else => unreachable, @@ -3176,22 +3214,22 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi .immediate => |imm| switch (imm) { 0 => return, 1 => { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), + // .data = undefined, + // }); return; }, else => { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = 0b10, - }), - .data = .{ .imm = @intCast(u8, imm) }, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = 0b10, + // }), + // .data = .{ .imm = @intCast(u8, imm) }, + // }); return; }, }, @@ -3204,15 +3242,16 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi try self.register_manager.getReg(.rcx, null); try self.genSetReg(Type.u8, .rcx, shift); } + _ = abi_size; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = 0b01, + // }), + // .data = undefined, + // }); } /// Result is always a register. @@ -3583,49 +3622,51 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .register => |src_reg| switch (dst_ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, dst_ty)) { - const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { - .f32 => switch (mir_tag) { - .add => Mir.Inst.Tag.add_f32, - .cmp => Mir.Inst.Tag.cmp_f32, - else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), - }, - .f64 => switch (mir_tag) { - .add => Mir.Inst.Tag.add_f64, - .cmp => Mir.Inst.Tag.cmp_f64, - else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), - }, - else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = actual_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg.to128(), - .reg2 = src_reg.to128(), - }), - .data = undefined, - }); + // const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { + // .f32 => switch (mir_tag) { + // .add => Mir.Inst.Tag.add_f32, + // .cmp => Mir.Inst.Tag.cmp_f32, + // else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), + // }, + // .f64 => switch (mir_tag) { + // .add => Mir.Inst.Tag.add_f64, + // .cmp => Mir.Inst.Tag.cmp_f64, + // else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), + // }, + // else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = actual_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg.to128(), + // .reg2 = src_reg.to128(), + // }), + // .data = undefined, + // }); return; } return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{}); }, else => { - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + _ = src_reg; + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); }, }, .immediate => |imm| { - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - .data = .{ .imm = @intCast(u32, imm) }, - }); + _ = imm; + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), + // .data = .{ .imm = @intCast(u32, imm) }, + // }); }, .memory, .linker_load, @@ -3642,15 +3683,15 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu if (off > math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -off }, + // }); }, } }, @@ -3668,26 +3709,28 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .dead, .unreach => unreachable, .register_overflow => unreachable, .register => |src_reg| { - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .reg2 = registerAlias(src_reg, abi_size), - .flags = 0b10, - }), - .data = .{ .disp = -off }, - }); + _ = src_reg; + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .reg2 = registerAlias(src_reg, abi_size), + // .flags = 0b10, + // }), + // .data = .{ .disp = -off }, + // }); }, .immediate => |imm| { - const tag: Mir.Inst.Tag = switch (mir_tag) { - .add => .add_mem_imm, - .@"or" => .or_mem_imm, - .@"and" => .and_mem_imm, - .sub => .sub_mem_imm, - .xor => .xor_mem_imm, - .cmp => .cmp_mem_imm, - else => unreachable, - }; + _ = imm; + // const tag: Mir.Inst.Tag = switch (mir_tag) { + // .add => .add_mem_imm, + // .@"or" => .or_mem_imm, + // .@"and" => .and_mem_imm, + // .sub => .sub_mem_imm, + // .xor => .xor_mem_imm, + // .cmp => .cmp_mem_imm, + // else => unreachable, + // }; const flags: u2 = switch (abi_size) { 1 => 0b00, 2 => 0b01, @@ -3695,18 +3738,19 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu 8 => 0b11, else => unreachable, }; - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -off, - .operand = @intCast(u32, imm), - }); - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .flags = flags, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -off, + // .operand = @intCast(u32, imm), + // }); + _ = flags; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .flags = flags, + // }), + // .data = .{ .payload = payload }, + // }); }, .memory, .stack_offset, @@ -3735,6 +3779,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu /// Does not support byte-size operands. fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) InnerError!void { const abi_size = @intCast(u32, dst_ty.abiSize(self.target.*)); + _ = abi_size; switch (dst_mcv) { .none => unreachable, .undef => unreachable, @@ -3750,29 +3795,30 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .ptr_stack_offset => unreachable, .register_overflow => unreachable, .register => |src_reg| { + _ = src_reg; // register, register - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); }, .immediate => |imm| { // TODO take into account the type's ABI size when selecting the register alias // register, immediate if (math.minInt(i32) <= imm and imm <= math.maxInt(i32)) { - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg.to32(), - .reg2 = dst_reg.to32(), - .flags = 0b10, - }), - .data = .{ .imm = @intCast(u32, imm) }, - }); + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg.to32(), + // .reg2 = dst_reg.to32(), + // .flags = 0b10, + // }), + // .data = .{ .imm = @intCast(u32, imm) }, + // }); } else { // TODO verify we don't spill and assign to the same register as dst_mcv const src_reg = try self.copyToTmpRegister(dst_ty, src_mcv); @@ -3780,15 +3826,16 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M } }, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -off }, + // }); }, .memory => { return self.fail("TODO implement x86 multiply source memory", .{}); @@ -3811,16 +3858,17 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .register => |src_reg| { // copy dst to a register const dst_reg = try self.copyToTmpRegister(dst_ty, dst_mcv); + _ = src_reg; // multiply into dst_reg // register, register - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); // copy dst_reg back out return self.genSetStack(dst_ty, off, .{ .register = dst_reg }, .{}); }, @@ -3946,20 +3994,20 @@ fn genVarDbgInfo( } fn airTrap(self: *Self) !void { - _ = try self.addInst(.{ - .tag = .ud, - .ops = Mir.Inst.Ops.encode(.{}), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .ud, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = undefined, + // }); return self.finishAirBookkeeping(); } fn airBreakpoint(self: *Self) !void { - _ = try self.addInst(.{ - .tag = .interrupt, - .ops = Mir.Inst.Ops.encode(.{}), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .interrupt, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = undefined, + // }); return self.finishAirBookkeeping(); } @@ -4054,11 +4102,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (info.stack_byte_count > 0) { // Adjust the stack - _ = try self.addInst(.{ - .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = info.stack_byte_count }, - }); + // _ = try self.addInst(.{ + // .tag = .sub, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), + // .data = .{ .imm = info.stack_byte_count }, + // }); } // Due to incremental compilation, how function calls are generated depends @@ -4072,11 +4120,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); const got_addr = @intCast(i32, atom.getOffsetTableAddress(elf_file)); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .disp = got_addr }, - }); + _ = got_addr; + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), + // .data = .{ .disp = got_addr }, + // }); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; @@ -4086,14 +4135,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const atom_index = try macho_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; @@ -4103,14 +4152,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { const decl_block_index = try p9.seeDecl(func.owner_decl); const decl_block = p9.getDeclBlock(decl_block_index); @@ -4119,11 +4168,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = p9.bases.data; const got_index = decl_block.got_index.?; const fn_got_addr = got_addr + got_index * ptr_bytes; - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .disp = @intCast(i32, fn_got_addr) }, - }); + _ = fn_got_addr; + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), + // .data = .{ .disp = @intCast(i32, fn_got_addr) }, + // }); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; @@ -4143,26 +4193,28 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; - _ = try self.addInst(.{ - .tag = .call_extern, - .ops = undefined, - .data = .{ .relocation = .{ - .atom_index = atom_index, - .sym_index = sym_index, - } }, - }); + _ = sym_index; + _ = atom_index; + // _ = try self.addInst(.{ + // .tag = .call_extern, + // .ops = undefined, + // .data = .{ .relocation = .{ + // .atom_index = atom_index, + // .sym_index = sym_index, + // } }, + // }); } else { return self.fail("TODO implement calling extern functions", .{}); } @@ -4173,23 +4225,23 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier assert(ty.zigTypeTag() == .Pointer); const mcv = try self.resolveInst(callee); try self.genSetReg(Type.initTag(.usize), .rax, mcv); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } if (info.stack_byte_count > 0) { // Readjust the stack - _ = try self.addInst(.{ - .tag = .add, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = info.stack_byte_count }, - }); + // _ = try self.addInst(.{ + // .tag = .add, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), + // .data = .{ .imm = info.stack_byte_count }, + // }); } const result: MCValue = result: { @@ -4246,12 +4298,12 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = undefined }, - }); - try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + // const jmp_reloc = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = undefined }, + // }); + // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4282,12 +4334,12 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = undefined }, - }); - try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + // const jmp_reloc = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = undefined }, + // }); + // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4461,33 +4513,35 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { const abi_size = ty.abiSize(self.target.*); switch (mcv) { .eflags => |cc| { - return self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ - .inst_cc = .{ - .inst = undefined, - // Here we map the opposites since the jump is to the false branch. - .cc = cc.negate(), - }, - }, - }); + _ = cc; + // return self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ + // .inst_cc = .{ + // .inst = undefined, + // // Here we map the opposites since the jump is to the false branch. + // .cc = cc.negate(), + // }, + // }, + // }); }, .register => |reg| { + _ = reg; try self.spillEflagsIfOccupied(); - _ = try self.addInst(.{ - .tag = .@"test", - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = .{ .imm = 1 }, - }); - return self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + // _ = try self.addInst(.{ + // .tag = .@"test", + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), + // .data = .{ .imm = 1 }, + // }); + // return self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .e, + // } }, + // }); }, .immediate, .stack_offset, @@ -4501,6 +4555,7 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { }, else => return self.fail("TODO implement condbr when condition is {s}", .{@tagName(mcv)}), } + return 0; // TODO } fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { @@ -4825,12 +4880,13 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const loop = self.air.extraData(Air.Block, ty_pl.payload); const body = self.air.extra[loop.end..][0..loop.data.body_len]; const jmp_target = @intCast(u32, self.mir_instructions.len); + _ = jmp_target; try self.genBody(body); - _ = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = jmp_target }, - }); + // _ = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = jmp_target }, + // }); return self.finishAirBookkeeping(); } @@ -4876,21 +4932,23 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u .undef => unreachable, .dead, .unreach => unreachable, .immediate => |imm| { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - .data = .{ .imm = @intCast(u32, imm) }, - }); + _ = imm; + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), + // .data = .{ .imm = @intCast(u32, imm) }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(cond_reg, abi_size), - .reg2 = registerAlias(reg, abi_size), - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(cond_reg, abi_size), + // .reg2 = registerAlias(reg, abi_size), + // }), + // .data = undefined, + // }); }, .stack_offset => { if (abi_size <= 8) { @@ -4905,22 +4963,22 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u }, } - _ = try self.addInst(.{ - .tag = .@"test", - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(cond_reg, abi_size), - .reg2 = registerAlias(cond_reg, abi_size), - }), - .data = undefined, - }); - return self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .ne, - } }, - }); + // _ = try self.addInst(.{ + // .tag = .@"test", + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(cond_reg, abi_size), + // .reg2 = registerAlias(cond_reg, abi_size), + // }), + // .data = undefined, + // }); + // return self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .ne, + // } }, + // }); }, .stack_offset => { try self.spillEflagsIfOccupied(); @@ -4938,6 +4996,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u return self.fail("TODO implemenent switch mir when condition is {}", .{condition}); }, } + return 0; // TODO } fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { @@ -5132,9 +5191,9 @@ fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Bran fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { const next_inst = @intCast(u32, self.mir_instructions.len); switch (self.mir_instructions.items(.tag)[reloc]) { - .cond_jmp => { - self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; - }, + // .cond_jmp => { + // self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; + // }, .jmp => { self.mir_instructions.items(.data)[reloc].inst = next_inst; }, @@ -5177,12 +5236,12 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined - const jmp_reloc = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = undefined }, - }); - block_data.relocs.appendAssumeCapacity(jmp_reloc); + // const jmp_reloc = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = undefined }, + // }); + // block_data.relocs.appendAssumeCapacity(jmp_reloc); } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { @@ -5254,30 +5313,22 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { - _ = try self.addInst(.{ - .tag = .syscall, - .ops = undefined, - .data = undefined, - }); + try self.assemble(.syscall, .{}); } else if (mem.indexOf(u8, ins, "push")) |_| { const arg = ins[4..]; if (mem.indexOf(u8, arg, "$")) |l| { const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch { return self.fail("TODO implement more inline asm int parsing", .{}); }; - _ = try self.addInst(.{ - .tag = .push, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b10 }), - .data = .{ .imm = n }, + try self.assemble(.push, .{ + .op1 = .{ .imm = Mir.Operand.Immediate.u(n) }, }); } else if (mem.indexOf(u8, arg, "%%")) |l| { const reg_name = ins[4 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - _ = try self.addInst(.{ - .tag = .push, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = undefined, + try self.assemble(.push, .{ + .op1 = .{ .reg = reg }, }); } else return self.fail("TODO more push operands", .{}); } else if (mem.indexOf(u8, ins, "pop")) |_| { @@ -5286,10 +5337,8 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg_name = ins[3 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - _ = try self.addInst(.{ - .tag = .pop, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = undefined, + try self.assemble(.pop, .{ + .op1 = .{ .reg = reg }, }); } else return self.fail("TODO more pop operands", .{}); } else { @@ -5433,39 +5482,40 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = switch (ty.tag()) { - .f32 => .esp, - .f64 => .rsp, - else => unreachable, - }, - .reg2 = reg.to128(), - .flags = 0b01, - }), - .data = .{ .disp = -stack_offset }, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), + // }; + _ = reg; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = switch (ty.tag()) { + // .f32 => .esp, + // .f64 => .rsp, + // else => unreachable, + // }, + // .reg2 = reg.to128(), + // .flags = 0b01, + // }), + // .data = .{ .disp = -stack_offset }, + // }); return; } return self.fail("TODO genSetStackArg for register with no intrinsics", .{}); }, else => { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rsp, - .reg2 = registerAlias(reg, @intCast(u32, abi_size)), - .flags = 0b10, - }), - .data = .{ .disp = -stack_offset }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rsp, + // .reg2 = registerAlias(reg, @intCast(u32, abi_size)), + // .flags = 0b10, + // }), + // .data = .{ .disp = -stack_offset }, + // }); }, } }, @@ -5519,13 +5569,13 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl const overflow_bit_ty = ty.structFieldType(1); const overflow_bit_offset = ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg.to8(), - }), - .data = .{ .cc = ro.eflags }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg.to8(), + // }), + // .data = .{ .cc = ro.eflags }, + // }); return self.genSetStack( overflow_bit_ty, @@ -5539,72 +5589,74 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl return self.genSetStack(ty, stack_offset, .{ .register = reg }, opts); }, .immediate => |x_big| { + _ = x_big; const base_reg = opts.dest_stack_base orelse .rbp; + _ = base_reg; switch (abi_size) { 0 => { assert(ty.isError()); - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = 0b00, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @truncate(u32, x_big), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = 0b00, + // }), + // .data = .{ .payload = payload }, + // }); }, 1, 2, 4 => { - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @truncate(u32, x_big), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // else => unreachable, + // }, + // }), + // .data = .{ .payload = payload }, + // }); }, 8 => { // 64 bit write to memory would take two mov's anyways so we // insted just use two 32 bit writes to avoid register allocation { - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset + 4, - .operand = @truncate(u32, x_big >> 32), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = 0b10, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset + 4, + // .operand = @truncate(u32, x_big >> 32), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = 0b10, + // }), + // .data = .{ .payload = payload }, + // }); } { - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = 0b10, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @truncate(u32, x_big), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = 0b10, + // }), + // .data = .{ .payload = payload }, + // }); } }, else => { @@ -5622,24 +5674,24 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = switch (ty.tag()) { - .f32 => base_reg.to32(), - .f64 => base_reg.to64(), - else => unreachable, - }, - .reg2 = reg.to128(), - .flags = 0b01, - }), - .data = .{ .disp = -stack_offset }, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = switch (ty.tag()) { + // .f32 => base_reg.to32(), + // .f64 => base_reg.to64(), + // else => unreachable, + // }, + // .reg2 = reg.to128(), + // .flags = 0b01, + // }), + // .data = .{ .disp = -stack_offset }, + // }); return; } @@ -5706,15 +5758,15 @@ fn genInlineMemcpyRegisterRegister( while (remainder > 0) { const nearest_power_of_two = @as(u6, 1) << math.log2_int(u3, @intCast(u3, remainder)); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg, - .reg2 = registerAlias(tmp_reg, nearest_power_of_two), - .flags = 0b10, - }), - .data = .{ .disp = -next_offset }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg, + // .reg2 = registerAlias(tmp_reg, nearest_power_of_two), + // .flags = 0b10, + // }), + // .data = .{ .disp = -next_offset }, + // }); if (nearest_power_of_two > 1) { try self.genShiftBinOpMir(.shr, ty, tmp_reg, .{ @@ -5726,15 +5778,15 @@ fn genInlineMemcpyRegisterRegister( next_offset -= nearest_power_of_two; } } else { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg, - .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), - .flags = 0b10, - }), - .data = .{ .disp = -offset }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg, + // .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), + // .flags = 0b10, + // }), + // .data = .{ .disp = -offset }, + // }); } } @@ -5768,30 +5820,34 @@ fn genInlineMemcpy( const index_reg = regs[2].to64(); const count_reg = regs[3].to64(); const tmp_reg = regs[4].to8(); + _ = index_reg; + _ = tmp_reg; switch (dst_ptr) { .memory, .linker_load => { try self.loadMemPtrIntoRegister(dst_addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_addr_reg.to64(), - .reg2 = opts.dest_stack_base orelse .rbp, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_addr_reg.to64(), + // .reg2 = opts.dest_stack_base orelse .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - .reg2 = reg, - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + // .reg2 = reg, + // }), + // .data = undefined, + // }); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -5803,24 +5859,26 @@ fn genInlineMemcpy( try self.loadMemPtrIntoRegister(src_addr_reg, Type.usize, src_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = src_addr_reg.to64(), - .reg2 = opts.source_stack_base orelse .rbp, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = src_addr_reg.to64(), + // .reg2 = opts.source_stack_base orelse .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - .reg2 = reg, - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + // .reg2 = reg, + // }), + // .data = undefined, + // }); }, else => { return self.fail("TODO implement memcpy for setting stack when src is {}", .{src_ptr}); @@ -5830,73 +5888,73 @@ fn genInlineMemcpy( try self.genSetReg(Type.usize, count_reg, len); // mov index_reg, 0 - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), + // .data = .{ .imm = 0 }, + // }); // loop: // cmp count, 0 - const loop_start = try self.addInst(.{ - .tag = .cmp, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - .data = .{ .imm = 0 }, - }); + // const loop_start = try self.addInst(.{ + // .tag = .cmp, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), + // .data = .{ .imm = 0 }, + // }); // je end - const loop_reloc = try self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + // const loop_reloc = try self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .e, + // } }, + // }); // mov tmp, [addr + index_reg] - _ = try self.addInst(.{ - .tag = .mov_scale_src, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg.to8(), - .reg2 = src_addr_reg, - }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_scale_src, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg.to8(), + // .reg2 = src_addr_reg, + // }), + // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, + // }); // mov [stack_offset + index_reg], tmp - _ = try self.addInst(.{ - .tag = .mov_scale_dst, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_addr_reg, - .reg2 = tmp_reg.to8(), - }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_scale_dst, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_addr_reg, + // .reg2 = tmp_reg.to8(), + // }), + // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, + // }); // add index_reg, 1 - _ = try self.addInst(.{ - .tag = .add, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = 1 }, - }); + // _ = try self.addInst(.{ + // .tag = .add, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), + // .data = .{ .imm = 1 }, + // }); // sub count, 1 - _ = try self.addInst(.{ - .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - .data = .{ .imm = 1 }, - }); + // _ = try self.addInst(.{ + // .tag = .sub, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), + // .data = .{ .imm = 1 }, + // }); // jmp loop - _ = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = loop_start }, - }); + // _ = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = loop_start }, + // }); // end: - try self.performReloc(loop_reloc); + // try self.performReloc(loop_reloc); } fn genInlineMemset( @@ -5927,24 +5985,26 @@ fn genInlineMemset( try self.loadMemPtrIntoRegister(addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = opts.dest_stack_base orelse .rbp, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = opts.dest_stack_base orelse .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - .reg2 = reg, - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + // .reg2 = reg, + // }), + // .data = undefined, + // }); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -5956,24 +6016,24 @@ fn genInlineMemset( // loop: // cmp index_reg, -1 - const loop_start = try self.addInst(.{ - .tag = .cmp, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = index_reg, - .flags = 0b11, - }), - .data = .{ .imm_s = -1 }, - }); + // const loop_start = try self.addInst(.{ + // .tag = .cmp, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = index_reg, + // .flags = 0b11, + // }), + // .data = .{ .imm_s = -1 }, + // }); // je end - const loop_reloc = try self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + // const loop_reloc = try self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .e, + // } }, + // }); switch (value) { .immediate => |x| { @@ -5981,37 +6041,37 @@ fn genInlineMemset( return self.fail("TODO inline memset for value immediate larger than 32bits", .{}); } // mov byte ptr [rbp + index_reg + stack_offset], imm - _ = try self.addInst(.{ - .tag = .mov_mem_index_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg, - }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( - index_reg, - 0, - @intCast(u32, x), - )) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_index_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg, + // }), + // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( + // index_reg, + // 0, + // @intCast(u32, x), + // )) }, + // }); }, else => return self.fail("TODO inline memset for value of type {}", .{value}), } // sub index_reg, 1 - _ = try self.addInst(.{ - .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = 1 }, - }); + // _ = try self.addInst(.{ + // .tag = .sub, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), + // .data = .{ .imm = 1 }, + // }); // jmp loop - _ = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = loop_start }, - }); + // _ = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = loop_start }, + // }); // end: - try self.performReloc(loop_reloc); + // try self.performReloc(loop_reloc); } fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { @@ -6023,14 +6083,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = .rbp, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .unreach, .none => return, // Nothing to do. .undef => { @@ -6046,34 +6106,30 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .eflags => |cc| { - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to8(), - }), - .data = .{ .cc = cc }, - }); + _ = cc; + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to8(), + // }), + // .data = .{ .cc = cc }, + // }); }, .immediate => |x| { // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit // register is the fastest way to zero a register. if (x == 0) { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to32(), - .reg2 = reg.to32(), - }), - .data = undefined, + try self.assemble(.xor, .{ + .op1 = .{ .reg = reg.to32() }, + .op2 = .{ .reg = reg.to32() }, }); return; } if (x <= math.maxInt(i32)) { // Next best case: if we set the lower four bytes, the upper four will be zeroed. - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = .{ .imm = @intCast(u32, x) }, + try self.assemble(.mov, .{ + .op1 = .{ .reg = registerAlias(reg, abi_size) }, + .op2 = .{ .imm = Mir.Operand.Immediate.u(@intCast(u32, x)) }, }); return; } @@ -6084,12 +6140,12 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only // difference is that we set REX.W before the instruction, which extends the load to // 64-bit and uses the full bit-width of the register. - const payload = try self.addExtra(Mir.Imm64.encode(x)); - _ = try self.addInst(.{ - .tag = .movabs, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg.to64() }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.Imm64.encode(x)); + // _ = try self.addInst(.{ + // .tag = .movabs, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg.to64() }), + // .data = .{ .payload = payload }, + // }); }, .register => |src_reg| { // If the registers are the same, nothing to do. @@ -6100,47 +6156,47 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Int => switch (ty.intInfo(self.target.*).signedness) { .signed => { if (abi_size <= 4) { - _ = try self.addInst(.{ - .tag = .mov_sign_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov_sign_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); return; } }, .unsigned => { if (abi_size <= 2) { - _ = try self.addInst(.{ - .tag = .mov_zero_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov_zero_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); return; } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = src_reg.to128(), - .flags = 0b10, - }), - .data = undefined, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = src_reg.to128(), + // .flags = 0b10, + // }), + // .data = undefined, + // }); return; } @@ -6149,14 +6205,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => {}, } - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); }, .linker_load => { switch (ty.zigTypeTag()) { @@ -6165,24 +6221,24 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - }; + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + // }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = switch (ty.tag()) { - .f32 => base_reg.to32(), - .f64 => base_reg.to64(), - else => unreachable, - }, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = switch (ty.tag()) { + // .f32 => base_reg.to32(), + // .f64 => base_reg.to64(), + // else => unreachable, + // }, + // }), + // .data = .{ .disp = 0 }, + // }); return; } @@ -6190,15 +6246,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void }, else => { try self.loadMemPtrIntoRegister(reg, Type.usize, mcv); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = reg.to64(), - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = reg.to64(), + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); }, } }, @@ -6208,24 +6264,24 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - }; + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + // }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = switch (ty.tag()) { - .f32 => base_reg.to32(), - .f64 => base_reg.to64(), - else => unreachable, - }, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = switch (ty.tag()) { + // .f32 => base_reg.to32(), + // .f64 => base_reg.to64(), + // else => unreachable, + // }, + // }), + // .data = .{ .disp = 0 }, + // }); return; } @@ -6234,42 +6290,42 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => { if (x <= math.maxInt(i32)) { // mov reg, [ds:imm32] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = 0b01, - }), - .data = .{ .disp = @intCast(i32, x) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = 0b01, + // }), + // .data = .{ .disp = @intCast(i32, x) }, + // }); } else { // If this is RAX, we can use a direct load. // Otherwise, we need to load the address, then indirectly load the value. if (reg.id() == 0) { // movabs rax, ds:moffs64 - const payload = try self.addExtra(Mir.Imm64.encode(x)); - _ = try self.addInst(.{ - .tag = .movabs, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, // imm64 will become moffs64 - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.Imm64.encode(x)); + // _ = try self.addInst(.{ + // .tag = .movabs, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, // imm64 will become moffs64 + // }), + // .data = .{ .payload = payload }, + // }); } else { // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. try self.genSetReg(ty, reg, MCValue{ .immediate = x }); // mov reg, [reg + 0x0] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = reg.to64(), - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = reg.to64(), + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); } } }, @@ -6289,15 +6345,16 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void 4 => 0b11, else => unreachable, }; - _ = try self.addInst(.{ - .tag = .mov_sign_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = .rbp, - .flags = flags, - }), - .data = .{ .disp = -off }, - }); + _ = flags; + // _ = try self.addInst(.{ + // .tag = .mov_sign_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = .rbp, + // .flags = flags, + // }), + // .data = .{ .disp = -off }, + // }); return; } }, @@ -6308,38 +6365,39 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void 2 => 0b10, else => unreachable, }; - _ = try self.addInst(.{ - .tag = .mov_zero_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = .rbp, - .flags = flags, - }), - .data = .{ .disp = -off }, - }); + _ = flags; + // _ = try self.addInst(.{ + // .tag = .mov_zero_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = .rbp, + // .flags = flags, + // }), + // .data = .{ .disp = -off }, + // }); return; } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = switch (ty.tag()) { - .f32 => .ebp, - .f64 => .rbp, - else => unreachable, - }, - }), - .data = .{ .disp = -off }, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = switch (ty.tag()) { + // .f32 => .ebp, + // .f64 => .rbp, + // else => unreachable, + // }, + // }), + // .data = .{ .disp = -off }, + // }); return; } return self.fail("TODO genSetReg from stack offset for float with no intrinsics", .{}); @@ -6347,15 +6405,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => {}, } - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -off }, + // }); }, } } @@ -6419,6 +6477,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { const src_ty = self.air.typeOf(ty_op.operand); const dst_ty = self.air.typeOfIndex(inst); const operand = try self.resolveInst(ty_op.operand); + _ = dst_ty; // move float src to ST(0) const stack_offset = switch (operand) { @@ -6433,34 +6492,35 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { break :blk offset; }, }; - _ = try self.addInst(.{ - .tag = .fld, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .flags = switch (src_ty.abiSize(self.target.*)) { - 4 => 0b01, - 8 => 0b10, - else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), - }, - }), - .data = .{ .disp = -stack_offset }, - }); + _ = stack_offset; + // _ = try self.addInst(.{ + // .tag = .fld, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .flags = switch (src_ty.abiSize(self.target.*)) { + // 4 => 0b01, + // 8 => 0b10, + // else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), + // }, + // }), + // .data = .{ .disp = -stack_offset }, + // }); // convert const stack_dst = try self.allocRegOrMem(inst, false); - _ = try self.addInst(.{ - .tag = .fisttp, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .flags = switch (dst_ty.abiSize(self.target.*)) { - 1...2 => 0b00, - 3...4 => 0b01, - 5...8 => 0b10, - else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), - }, - }), - .data = .{ .disp = -stack_dst.stack_offset }, - }); + // _ = try self.addInst(.{ + // .tag = .fisttp, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .flags = switch (dst_ty.abiSize(self.target.*)) { + // 1...2 => 0b00, + // 3...4 => 0b01, + // 5...8 => 0b10, + // else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), + // }, + // }), + // .data = .{ .disp = -stack_dst.stack_offset }, + // }); return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none }); } @@ -6551,15 +6611,15 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { .linker_load, .memory => { const reg = try self.register_manager.allocReg(null, gp); try self.loadMemPtrIntoRegister(reg, src_ty, src_ptr); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg, - .reg2 = reg, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg, + // .reg2 = reg, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); break :blk MCValue{ .register = reg }; }, else => break :blk src_ptr, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e69601c40b..15f41a943c 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -71,124 +71,53 @@ pub fn lowerMir(emit: *Emit) InnerError!void { const inst = @intCast(u32, index); try emit.code_offset_mapping.putNoClobber(emit.bin_file.allocator, inst, emit.code.items.len); switch (tag) { - // GPR instructions - .adc => try emit.mirArith(.adc, inst), - .add => try emit.mirArith(.add, inst), - .sub => try emit.mirArith(.sub, inst), - .xor => try emit.mirArith(.xor, inst), - .@"and" => try emit.mirArith(.@"and", inst), - .@"or" => try emit.mirArith(.@"or", inst), - .sbb => try emit.mirArith(.sbb, inst), - .cmp => try emit.mirArith(.cmp, inst), - .mov => try emit.mirArith(.mov, inst), + .adc, + .add, + .@"and", + .cbw, + .cwde, + .cdqe, + .cwd, + .cdq, + .cqo, + .cmp, + .div, + .fisttp, + .fld, + .idiv, + .imul, + .int3, + .mov, + .movsx, + .movzx, + .mul, + .nop, + .@"or", + .pop, + .push, + .ret, + .sal, + .sar, + .sbb, + .shl, + .shr, + .sub, + .syscall, + .@"test", + .ud2, + .xor, - .adc_mem_imm => try emit.mirArithMemImm(.adc, inst), - .add_mem_imm => try emit.mirArithMemImm(.add, inst), - .sub_mem_imm => try emit.mirArithMemImm(.sub, inst), - .xor_mem_imm => try emit.mirArithMemImm(.xor, inst), - .and_mem_imm => try emit.mirArithMemImm(.@"and", inst), - .or_mem_imm => try emit.mirArithMemImm(.@"or", inst), - .sbb_mem_imm => try emit.mirArithMemImm(.sbb, inst), - .cmp_mem_imm => try emit.mirArithMemImm(.cmp, inst), - .mov_mem_imm => try emit.mirArithMemImm(.mov, inst), - - .adc_scale_src => try emit.mirArithScaleSrc(.adc, inst), - .add_scale_src => try emit.mirArithScaleSrc(.add, inst), - .sub_scale_src => try emit.mirArithScaleSrc(.sub, inst), - .xor_scale_src => try emit.mirArithScaleSrc(.xor, inst), - .and_scale_src => try emit.mirArithScaleSrc(.@"and", inst), - .or_scale_src => try emit.mirArithScaleSrc(.@"or", inst), - .sbb_scale_src => try emit.mirArithScaleSrc(.sbb, inst), - .cmp_scale_src => try emit.mirArithScaleSrc(.cmp, inst), - .mov_scale_src => try emit.mirArithScaleSrc(.mov, inst), - - .adc_scale_dst => try emit.mirArithScaleDst(.adc, inst), - .add_scale_dst => try emit.mirArithScaleDst(.add, inst), - .sub_scale_dst => try emit.mirArithScaleDst(.sub, inst), - .xor_scale_dst => try emit.mirArithScaleDst(.xor, inst), - .and_scale_dst => try emit.mirArithScaleDst(.@"and", inst), - .or_scale_dst => try emit.mirArithScaleDst(.@"or", inst), - .sbb_scale_dst => try emit.mirArithScaleDst(.sbb, inst), - .cmp_scale_dst => try emit.mirArithScaleDst(.cmp, inst), - .mov_scale_dst => try emit.mirArithScaleDst(.mov, inst), - - .adc_scale_imm => try emit.mirArithScaleImm(.adc, inst), - .add_scale_imm => try emit.mirArithScaleImm(.add, inst), - .sub_scale_imm => try emit.mirArithScaleImm(.sub, inst), - .xor_scale_imm => try emit.mirArithScaleImm(.xor, inst), - .and_scale_imm => try emit.mirArithScaleImm(.@"and", inst), - .or_scale_imm => try emit.mirArithScaleImm(.@"or", inst), - .sbb_scale_imm => try emit.mirArithScaleImm(.sbb, inst), - .cmp_scale_imm => try emit.mirArithScaleImm(.cmp, inst), - .mov_scale_imm => try emit.mirArithScaleImm(.mov, inst), - - .adc_mem_index_imm => try emit.mirArithMemIndexImm(.adc, inst), - .add_mem_index_imm => try emit.mirArithMemIndexImm(.add, inst), - .sub_mem_index_imm => try emit.mirArithMemIndexImm(.sub, inst), - .xor_mem_index_imm => try emit.mirArithMemIndexImm(.xor, inst), - .and_mem_index_imm => try emit.mirArithMemIndexImm(.@"and", inst), - .or_mem_index_imm => try emit.mirArithMemIndexImm(.@"or", inst), - .sbb_mem_index_imm => try emit.mirArithMemIndexImm(.sbb, inst), - .cmp_mem_index_imm => try emit.mirArithMemIndexImm(.cmp, inst), - .mov_mem_index_imm => try emit.mirArithMemIndexImm(.mov, inst), - - .mov_sign_extend => try emit.mirMovSignExtend(inst), - .mov_zero_extend => try emit.mirMovZeroExtend(inst), - - .movabs => try emit.mirMovabs(inst), - - .fisttp => try emit.mirFisttp(inst), - .fld => try emit.mirFld(inst), - - .lea => try emit.mirLea(inst), - .lea_pic => try emit.mirLeaPic(inst), - - .shl => try emit.mirShift(.shl, inst), - .sal => try emit.mirShift(.sal, inst), - .shr => try emit.mirShift(.shr, inst), - .sar => try emit.mirShift(.sar, inst), - - .imul => try emit.mirMulDiv(.imul, inst), - .mul => try emit.mirMulDiv(.mul, inst), - .idiv => try emit.mirMulDiv(.idiv, inst), - .div => try emit.mirMulDiv(.div, inst), - .imul_complex => try emit.mirIMulComplex(inst), - - .cwd => try emit.mirCwd(inst), - - .push => try emit.mirPushPop(.push, inst), - .pop => try emit.mirPushPop(.pop, inst), - - .jmp => try emit.mirJmpCall(.jmp, inst), - .call => try emit.mirJmpCall(.call, inst), - - .cond_jmp => try emit.mirCondJmp(inst), - .cond_set_byte => try emit.mirCondSetByte(inst), - .cond_mov => try emit.mirCondMov(inst), - - .ret => try emit.mirRet(inst), - - .syscall => try emit.mirSyscall(), - - .@"test" => try emit.mirTest(inst), - - .ud => try emit.mirUndefinedInstruction(), - .interrupt => try emit.mirInterrupt(inst), - .nop => {}, // just skip it - - // SSE/AVX instructions - .mov_f64 => try emit.mirMovFloat(.movsd, inst), - .mov_f32 => try emit.mirMovFloat(.movss, inst), - - .add_f64 => try emit.mirAddFloat(.addsd, inst), - .add_f32 => try emit.mirAddFloat(.addss, inst), - - .cmp_f64 => try emit.mirCmpFloat(.ucomisd, inst), - .cmp_f32 => try emit.mirCmpFloat(.ucomiss, inst), + .addss, + .cmpss, + .movss, + .ucomiss, + .addsd, + .cmpsd, + .movsd, + .ucomisd, + => try emit.mirEncodeGeneric(tag, inst), // Pseudo-instructions - .call_extern => try emit.mirCallExtern(inst), - .dbg_line => try emit.mirDbgLine(inst), .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst), @@ -196,9 +125,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push_regs => try emit.mirPushPopRegisterList(.push, inst), .pop_regs => try emit.mirPushPopRegisterList(.pop, inst), - else => { - return emit.fail("Implement MIR->Emit lowering for x86_64 for pseudo-inst: {}", .{tag}); - }, + else => return emit.fail("Implement MIR->Emit lowering for x86_64 for pseudo-inst: {}", .{tag}), } } @@ -246,66 +173,57 @@ fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { return inst.encode(emit.code.writer()); } -fn mirUndefinedInstruction(emit: *Emit) InnerError!void { - return emit.encode(.ud2, .{}); -} +fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { + const mnemonic = inline for (@typeInfo(Instruction.Mnemonic).Enum.fields) |field| { + if (mem.eql(u8, field.name, @tagName(tag))) break @field(Instruction.Mnemonic, field.name); + } else unreachable; -fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .interrupt); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => return emit.encode(.int3, .{}), - else => return emit.fail("TODO handle variant 0b{b} of interrupt instruction", .{ops.flags}), + var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; + const ops = emit.mir.instructions.items(.ops)[inst]; + const data = emit.mir.instructions.items(.data)[inst]; + switch (ops) { + .none => {}, + .imm_s => operands[0] = .{ .imm = Immediate.s(data.imm_s) }, + .imm_u => operands[0] = .{ .imm = Immediate.u(data.imm_u) }, + .r => operands[0] = .{ .reg = data.r }, + .rr => operands[0..2].* = .{ + .{ .reg = data.rr.r1 }, + .{ .reg = data.rr.r2 }, + }, + .ri_s => operands[0..2].* = .{ + .{ .reg = data.ri_s.r1 }, + .{ .imm = Immediate.s(data.ri_s.imm) }, + }, + .ri_u => operands[0..2].* = .{ + .{ .reg = data.ri_u.r1 }, + .{ .imm = Immediate.u(data.ri_u.imm) }, + }, + else => unreachable, } + + return emit.encode(mnemonic, .{ + .op1 = operands[0], + .op2 = operands[1], + .op3 = operands[2], + .op4 = operands[3], + }); } -fn mirSyscall(emit: *Emit) InnerError!void { - return emit.encode(.syscall, .{}); -} - -fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, - .disp = disp, - }) }, - }); - }, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.push, .{ - .op1 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => unreachable, - } -} - -fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); +fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; + const base = @intToEnum(Register, save_reg_list.base_reg); var disp: i32 = -@intCast(i32, save_reg_list.stack_end); const reg_list = Mir.RegisterList.fromInt(save_reg_list.register_list); const callee_preserved_regs = abi.getCalleePreservedRegs(emit.target.*); for (callee_preserved_regs) |reg| { if (reg_list.isSet(callee_preserved_regs, reg)) { const op1: Instruction.Operand = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, + .base = base, .disp = disp, }) }; const op2: Instruction.Operand = .{ .reg = reg }; - switch (mnemonic) { + switch (tag) { .push => try emit.encode(.mov, .{ .op1 = op1, .op2 = op2, @@ -321,858 +239,345 @@ fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir } } -fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const target = emit.mir.instructions.items(.data)[inst].inst; - const source = emit.code.items.len; - try emit.encode(mnemonic, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = target, - .offset = emit.code.items.len - 4, - .length = 5, - }); - }, - 0b01 => { - if (ops.reg1 == .none) { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, - }); - } - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - }); - }, - 0b10 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, - .disp = disp, - }) }, - }); - }, - 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), - } -} +// fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// switch (ops.flags) { +// 0b00 => { +// const target = emit.mir.instructions.items(.data)[inst].inst; +// const source = emit.code.items.len; +// try emit.encode(mnemonic, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// try emit.relocs.append(emit.bin_file.allocator, .{ +// .source = source, +// .target = target, +// .offset = emit.code.items.len - 4, +// .length = 5, +// }); +// }, +// 0b01 => { +// if (ops.reg1 == .none) { +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// return emit.encode(mnemonic, .{ +// .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, +// }); +// } +// return emit.encode(mnemonic, .{ +// .op1 = .{ .reg = ops.reg1 }, +// }); +// }, +// 0b10 => { +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// return emit.encode(mnemonic, .{ +// .op1 = .{ .mem = Memory.sib(.qword, .{ +// .base = ops.reg1, +// .disp = disp, +// }) }, +// }); +// }, +// 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), +// } +// } -fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .cond_jmp); - const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; - const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { - .a => .ja, - .ae => .jae, - .b => .jb, - .be => .jbe, - .c => .jc, - .e => .je, - .g => .jg, - .ge => .jge, - .l => .jl, - .le => .jle, - .na => .jna, - .nae => .jnae, - .nb => .jnb, - .nbe => .jnbe, - .nc => .jnc, - .ne => .jne, - .ng => .jng, - .nge => .jnge, - .nl => .jnl, - .nle => .jnle, - .no => .jno, - .np => .jnp, - .ns => .jns, - .nz => .jnz, - .o => .jo, - .p => .jp, - .pe => .jpe, - .po => .jpo, - .s => .js, - .z => .jz, - }; - const source = emit.code.items.len; - try emit.encode(mnemonic, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = inst_cc.inst, - .offset = emit.code.items.len - 4, - .length = 6, - }); -} +// fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .cond_jmp); +// const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; +// const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { +// .a => .ja, +// .ae => .jae, +// .b => .jb, +// .be => .jbe, +// .c => .jc, +// .e => .je, +// .g => .jg, +// .ge => .jge, +// .l => .jl, +// .le => .jle, +// .na => .jna, +// .nae => .jnae, +// .nb => .jnb, +// .nbe => .jnbe, +// .nc => .jnc, +// .ne => .jne, +// .ng => .jng, +// .nge => .jnge, +// .nl => .jnl, +// .nle => .jnle, +// .no => .jno, +// .np => .jnp, +// .ns => .jns, +// .nz => .jnz, +// .o => .jo, +// .p => .jp, +// .pe => .jpe, +// .po => .jpo, +// .s => .js, +// .z => .jz, +// }; +// const source = emit.code.items.len; +// try emit.encode(mnemonic, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// try emit.relocs.append(emit.bin_file.allocator, .{ +// .source = source, +// .target = inst_cc.inst, +// .offset = emit.code.items.len - 4, +// .length = 6, +// }); +// } -fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .cond_set_byte); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const cc = emit.mir.instructions.items(.data)[inst].cc; - const mnemonic: Instruction.Mnemonic = switch (cc) { - .a => .seta, - .ae => .setae, - .b => .setb, - .be => .setbe, - .c => .setc, - .e => .sete, - .g => .setg, - .ge => .setge, - .l => .setl, - .le => .setle, - .na => .setna, - .nae => .setnae, - .nb => .setnb, - .nbe => .setnbe, - .nc => .setnc, - .ne => .setne, - .ng => .setng, - .nge => .setnge, - .nl => .setnl, - .nle => .setnle, - .no => .setno, - .np => .setnp, - .ns => .setns, - .nz => .setnz, - .o => .seto, - .p => .setp, - .pe => .setpe, - .po => .setpo, - .s => .sets, - .z => .setz, - }; - return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); -} +// fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .cond_set_byte); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// const cc = emit.mir.instructions.items(.data)[inst].cc; +// const mnemonic: Instruction.Mnemonic = switch (cc) { +// .a => .seta, +// .ae => .setae, +// .b => .setb, +// .be => .setbe, +// .c => .setc, +// .e => .sete, +// .g => .setg, +// .ge => .setge, +// .l => .setl, +// .le => .setle, +// .na => .setna, +// .nae => .setnae, +// .nb => .setnb, +// .nbe => .setnbe, +// .nc => .setnc, +// .ne => .setne, +// .ng => .setng, +// .nge => .setnge, +// .nl => .setnl, +// .nle => .setnle, +// .no => .setno, +// .np => .setnp, +// .ns => .setns, +// .nz => .setnz, +// .o => .seto, +// .p => .setp, +// .pe => .setpe, +// .po => .setpo, +// .s => .sets, +// .z => .setz, +// }; +// return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); +// } -fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .cond_mov); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const cc = emit.mir.instructions.items(.data)[inst].cc; - const mnemonic: Instruction.Mnemonic = switch (cc) { - .a => .cmova, - .ae => .cmovae, - .b => .cmovb, - .be => .cmovbe, - .c => .cmovc, - .e => .cmove, - .g => .cmovg, - .ge => .cmovge, - .l => .cmovl, - .le => .cmovle, - .na => .cmovna, - .nae => .cmovnae, - .nb => .cmovnb, - .nbe => .cmovnbe, - .nc => .cmovnc, - .ne => .cmovne, - .ng => .cmovng, - .nge => .cmovnge, - .nl => .cmovnl, - .nle => .cmovnle, - .no => .cmovno, - .np => .cmovnp, - .ns => .cmovns, - .nz => .cmovnz, - .o => .cmovo, - .p => .cmovp, - .pe => .cmovpe, - .po => .cmovpo, - .s => .cmovs, - .z => .cmovz, - }; - const op1: Instruction.Operand = .{ .reg = ops.reg1 }; +// fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .cond_mov); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// const cc = emit.mir.instructions.items(.data)[inst].cc; +// const mnemonic: Instruction.Mnemonic = switch (cc) { +// .a => .cmova, +// .ae => .cmovae, +// .b => .cmovb, +// .be => .cmovbe, +// .c => .cmovc, +// .e => .cmove, +// .g => .cmovg, +// .ge => .cmovge, +// .l => .cmovl, +// .le => .cmovle, +// .na => .cmovna, +// .nae => .cmovnae, +// .nb => .cmovnb, +// .nbe => .cmovnbe, +// .nc => .cmovnc, +// .ne => .cmovne, +// .ng => .cmovng, +// .nge => .cmovnge, +// .nl => .cmovnl, +// .nle => .cmovnle, +// .no => .cmovno, +// .np => .cmovnp, +// .ns => .cmovns, +// .nz => .cmovnz, +// .o => .cmovo, +// .p => .cmovp, +// .pe => .cmovpe, +// .po => .cmovpo, +// .s => .cmovs, +// .z => .cmovz, +// }; +// const op1: Instruction.Operand = .{ .reg = ops.reg1 }; - if (ops.flags == 0b00) { - return emit.encode(mnemonic, .{ - .op1 = op1, - .op2 = .{ .reg = ops.reg2 }, - }); - } - const disp = emit.mir.instructions.items(.data)[inst].disp; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => unreachable, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - return emit.encode(mnemonic, .{ - .op1 = op1, - .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); -} +// if (ops.flags == 0b00) { +// return emit.encode(mnemonic, .{ +// .op1 = op1, +// .op2 = .{ .reg = ops.reg2 }, +// }); +// } +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// const ptr_size: Memory.PtrSize = switch (ops.flags) { +// 0b00 => unreachable, +// 0b01 => .word, +// 0b10 => .dword, +// 0b11 => .qword, +// }; +// return emit.encode(mnemonic, .{ +// .op1 = op1, +// .op2 = .{ .mem = Memory.sib(ptr_size, .{ +// .base = ops.reg2, +// .disp = disp, +// }) }, +// }); +// } -fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .@"test"); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - if (ops.reg2 == .none) { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.@"test", .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - } - return emit.encode(.@"test", .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO more TEST alternatives", .{}), - } -} +// fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .lea); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// switch (ops.flags) { +// 0b00 => { +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; +// return emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ +// .base = src_reg, +// .disp = disp, +// }) }, +// }); +// }, +// 0b01 => { +// const start_offset = emit.code.items.len; +// try emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, +// }); +// const end_offset = emit.code.items.len; +// // Backpatch the displacement +// const payload = emit.mir.instructions.items(.data)[inst].payload; +// const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); +// const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); +// mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); +// }, +// 0b10 => { +// const payload = emit.mir.instructions.items(.data)[inst].payload; +// const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); +// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; +// const scale_index = Memory.ScaleIndex{ +// .scale = 1, +// .index = index_reg_disp.index, +// }; +// return emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ +// .base = src_reg, +// .scale_index = scale_index, +// .disp = index_reg_disp.disp, +// }) }, +// }); +// }, +// 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), +// } +// } -fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .ret); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => unreachable, - 0b01 => unreachable, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.ret, .{ - .op1 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => { - return emit.encode(.ret, .{}); - }, - } -} +// fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .lea_pic); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// const relocation = emit.mir.instructions.items(.data)[inst].relocation; -fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - if (ops.reg2 == .none) { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - } - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = base, - .disp = disp, - }) }, - }); - }, - 0b10 => { - if (ops.reg2 == .none) { - return emit.fail("TODO unused variant: mov reg1, none, 0b10", .{}); - } - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ - .base = ops.reg1, - .disp = disp, - }) }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b11 => { - const imm_s = emit.mir.instructions.items(.data)[inst].imm_s; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.s(imm_s) }, - }); - }, - } -} +// switch (ops.flags) { +// 0b00, 0b01, 0b10 => {}, +// else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), +// } -fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - assert(ops.reg2 == .none); - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - const imm = switch (ops.flags) { - 0b00 => @truncate(u8, imm_pair.operand), - 0b01 => @truncate(u16, imm_pair.operand), - 0b10, 0b11 => @truncate(u32, imm_pair.operand), - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = imm_pair.dest_off, - .base = ops.reg1, - }) }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); -} +// try emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, +// }); -fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const scale = ops.flags; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const scale_index = Memory.ScaleIndex{ - .scale = @as(u4, 1) << scale, - .index = index_reg_disp.index, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = ops.reg2, - .scale_index = scale_index, - .disp = index_reg_disp.disp, - }) }, - }); -} +// const end_offset = emit.code.items.len; -fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const scale = ops.flags; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const scale_index = Memory.ScaleIndex{ - .scale = @as(u4, 1) << scale, - .index = index_reg_disp.index, - }; - assert(ops.reg2 != .none); - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ - .base = ops.reg1, - .scale_index = scale_index, - .disp = index_reg_disp.disp, - }) }, - .op2 = .{ .reg = ops.reg2 }, - }); -} +// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { +// const reloc_type = switch (ops.flags) { +// 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), +// 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), +// else => unreachable, +// }; +// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ +// .type = reloc_type, +// .target = .{ .sym_index = relocation.sym_index, .file = null }, +// .offset = @intCast(u32, end_offset - 4), +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { +// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ +// .type = switch (ops.flags) { +// 0b00 => .got, +// 0b01 => .direct, +// 0b10 => .import, +// else => unreachable, +// }, +// .target = switch (ops.flags) { +// 0b00, 0b01 => .{ .sym_index = relocation.sym_index, .file = null }, +// 0b10 => coff_file.getGlobalByIndex(relocation.sym_index), +// else => unreachable, +// }, +// .offset = @intCast(u32, end_offset - 4), +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else { +// return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); +// } +// } -fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const scale = ops.flags; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); - const scale_index = Memory.ScaleIndex{ - .scale = @as(u4, 1) << scale, - .index = index_reg_disp_imm.index, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, - .disp = index_reg_disp_imm.disp, - .scale_index = scale_index, - }) }, - .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, - }); -} +// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .call_extern); +// const relocation = emit.mir.instructions.items(.data)[inst].relocation; -fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - assert(ops.reg2 == .none); - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - const scale_index = Memory.ScaleIndex{ - .scale = 1, - .index = index_reg_disp_imm.index, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = index_reg_disp_imm.disp, - .base = ops.reg1, - .scale_index = scale_index, - }) }, - .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, - }); -} +// const offset = blk: { +// // callq +// try emit.encode(.call, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// break :blk @intCast(u32, emit.code.items.len) - 4; +// }; -fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .mov_sign_extend); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; - switch (ops.flags) { - 0b00 => { - const mnemonic: Instruction.Mnemonic = if (ops.reg2.bitSize() == 32) .movsxd else .movsx; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => { - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b01 => .byte, - 0b10 => .word, - 0b11 => .dword, - else => unreachable, - }; - const mnemonic: Instruction.Mnemonic = if (ops.flags == 0b11) .movsxd else .movsx; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); - }, - } -} - -fn mirMovZeroExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .mov_zero_extend); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; - switch (ops.flags) { - 0b00 => { - return emit.encode(.movzx, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b01, 0b10 => { - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b01 => .byte, - 0b10 => .word, - else => unreachable, - }; - return emit.encode(.movzx, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = disp, - .base = ops.reg2, - }) }, - }); - }, - 0b11 => { - return emit.fail("TODO unused variant: movzx 0b11", .{}); - }, - } -} - -fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .movabs); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk imm.decode(); - } else emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.mov, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b01 => { - if (ops.reg1 == .none) { - const imm: u64 = if (ops.reg2.bitSize() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk imm.decode(); - } else emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.mov, .{ - .op1 = .{ .mem = Memory.moffs(ops.reg2, imm) }, - .op2 = .{ .reg = .rax }, - }); - } - const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk imm.decode(); - } else emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.mov, .{ - .op1 = .{ .reg = .rax }, - .op2 = .{ .mem = Memory.moffs(ops.reg1, imm) }, - }); - }, - else => return emit.fail("TODO unused movabs variant", .{}), - } -} - -fn mirFisttp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .fisttp); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .word, - 0b01 => .dword, - 0b10 => .qword, - else => unreachable, - }; - return emit.encode(.fisttp, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].disp, - }) }, - }); -} - -fn mirFld(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .fld); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b01 => .dword, - 0b10 => .qword, - else => unreachable, - }; - return emit.encode(.fld, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].disp, - }) }, - }); -} - -fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(1) }, - }); - }, - 0b01 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = .cl }, - }); - }, - 0b10 => { - const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => { - return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{}); - }, - } -} - -fn mirMulDiv(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - if (ops.reg1 != .none) { - assert(ops.reg2 == .none); - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - }); - } - assert(ops.reg2 != .none); - const disp = emit.mir.instructions.items(.data)[inst].disp; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); -} - -fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .imul_complex); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(.qword, .{ - .base = src_reg, - .disp = disp, - }) }, - }); - }, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - .op3 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg2, - .disp = imm_pair.dest_off, - }) }, - .op3 = .{ .imm = Immediate.u(imm_pair.operand) }, - }); - }, - } -} - -fn mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const mnemonic: Instruction.Mnemonic = switch (ops.flags) { - 0b00 => .cbw, - 0b01 => .cwd, - 0b10 => .cdq, - 0b11 => .cqo, - }; - return emit.encode(mnemonic, .{}); -} - -fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = src_reg, - .disp = disp, - }) }, - }); - }, - 0b01 => { - const start_offset = emit.code.items.len; - try emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, - }); - const end_offset = emit.code.items.len; - // Backpatch the displacement - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); - const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); - mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); - }, - 0b10 => { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - const scale_index = Memory.ScaleIndex{ - .scale = 1, - .index = index_reg_disp.index, - }; - return emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = src_reg, - .scale_index = scale_index, - .disp = index_reg_disp.disp, - }) }, - }); - }, - 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), - } -} - -fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea_pic); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const relocation = emit.mir.instructions.items(.data)[inst].relocation; - - switch (ops.flags) { - 0b00, 0b01, 0b10 => {}, - else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), - } - - try emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, - }); - - const end_offset = emit.code.items.len; - - if (emit.bin_file.cast(link.File.MachO)) |macho_file| { - const reloc_type = switch (ops.flags) { - 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), - 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), - else => unreachable, - }; - const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ - .type = reloc_type, - .target = .{ .sym_index = relocation.sym_index, .file = null }, - .offset = @intCast(u32, end_offset - 4), - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { - const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ - .type = switch (ops.flags) { - 0b00 => .got, - 0b01 => .direct, - 0b10 => .import, - else => unreachable, - }, - .target = switch (ops.flags) { - 0b00, 0b01 => .{ .sym_index = relocation.sym_index, .file = null }, - 0b10 => coff_file.getGlobalByIndex(relocation.sym_index), - else => unreachable, - }, - .offset = @intCast(u32, end_offset - 4), - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else { - return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); - } -} - -// SSE/AVX instructions - -fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = ops.reg1, - .disp = disp, - }) }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b10 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), - } -} - -fn mirAddFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), - } -} - -fn mirCmpFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), - } -} - -// Pseudo-instructions - -fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .call_extern); - const relocation = emit.mir.instructions.items(.data)[inst].relocation; - - const offset = blk: { - // callq - try emit.encode(.call, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - break :blk @intCast(u32, emit.code.items.len) - 4; - }; - - if (emit.bin_file.cast(link.File.MachO)) |macho_file| { - // Add relocation to the decl. - const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - const target = macho_file.getGlobalByIndex(relocation.sym_index); - try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ - .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), - .target = target, - .offset = offset, - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { - // Add relocation to the decl. - const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - const target = coff_file.getGlobalByIndex(relocation.sym_index); - try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ - .type = .direct, - .target = target, - .offset = offset, - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else { - return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); - } -} +// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { +// // Add relocation to the decl. +// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = macho_file.getGlobalByIndex(relocation.sym_index); +// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ +// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { +// // Add relocation to the decl. +// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = coff_file.getGlobalByIndex(relocation.sym_index); +// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ +// .type = .direct, +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else { +// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); +// } +// } fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_line); const payload = emit.mir.instructions.items(.data)[inst].payload; const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data; log.debug("mirDbgLine", .{}); @@ -1230,8 +635,7 @@ fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void { } fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_prologue_end); + _ = inst; switch (emit.debug_output) { .dwarf => |dw| { try dw.setPrologueEnd(); @@ -1247,8 +651,7 @@ fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_epilogue_begin); + _ = inst; switch (emit.debug_output) { .dwarf => |dw| { try dw.setEpilogueBegin(); diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 43c24216a8..5f4ed05deb 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -12,6 +12,8 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const bits = @import("bits.zig"); +const encoder = @import("encoder.zig"); + const Air = @import("../../Air.zig"); const CodeGen = @import("CodeGen.zig"); const IntegerBitSet = std.bit_set.IntegerBitSet; @@ -21,421 +23,239 @@ instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. extra: []const u32, +pub const Mnemonic = encoder.Instruction.Mnemonic; +pub const Operand = encoder.Instruction.Operand; + pub const Inst = struct { tag: Tag, ops: Ops, - /// The meaning of this depends on `tag` and `ops`. data: Data, - pub const Tag = enum(u16) { - /// ops flags: form: - /// 0b00 reg1, reg2 - /// 0b00 reg1, imm32 - /// 0b01 reg1, [reg2 + imm32] - /// 0b01 reg1, [ds:imm32] - /// 0b10 [reg1 + imm32], reg2 - /// 0b11 reg1, imm_s - /// Notes: - /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. - /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. - adc, - - /// ops flags: form: - /// 0b00 byte ptr [reg1 + imm32], imm8 - /// 0b01 word ptr [reg1 + imm32], imm16 - /// 0b10 dword ptr [reg1 + imm32], imm32 - /// 0b11 qword ptr [reg1 + imm32], imm32 (sign-extended to imm64) - /// Notes: - /// * Uses `ImmPair` as payload - adc_mem_imm, - - /// form: reg1, [reg2 + scale*index + imm32] - /// ops flags scale - /// 0b00 1 - /// 0b01 2 - /// 0b10 4 - /// 0b11 8 - /// Notes: - /// * Uses `IndexRegisterDisp` as payload - adc_scale_src, - - /// form: [reg1 + scale*index + imm32], reg2 - /// ops flags scale - /// 0b00 1 - /// 0b01 2 - /// 0b10 4 - /// 0b11 8 - /// Notes: - /// * Uses `IndexRegisterDisp` payload. - adc_scale_dst, - - /// form: [reg1 + scale*rax + imm32], imm32 - /// ops flags scale - /// 0b00 1 - /// 0b01 2 - /// 0b10 4 - /// 0b11 8 - /// Notes: - /// * Uses `IndexRegisterDispImm` payload. - adc_scale_imm, - - /// ops flags: form: - /// 0b00 byte ptr [reg1 + index + imm32], imm8 - /// 0b01 word ptr [reg1 + index + imm32], imm16 - /// 0b10 dword ptr [reg1 + index + imm32], imm32 - /// 0b11 qword ptr [reg1 + index + imm32], imm32 (sign-extended to imm64) - /// Notes: - /// * Uses `IndexRegisterDispImm` payload. - adc_mem_index_imm, - - // The following instructions all have the same encoding as `adc`. - - add, - add_mem_imm, - add_scale_src, - add_scale_dst, - add_scale_imm, - add_mem_index_imm, - sub, - sub_mem_imm, - sub_scale_src, - sub_scale_dst, - sub_scale_imm, - sub_mem_index_imm, - xor, - xor_mem_imm, - xor_scale_src, - xor_scale_dst, - xor_scale_imm, - xor_mem_index_imm, - @"and", - and_mem_imm, - and_scale_src, - and_scale_dst, - and_scale_imm, - and_mem_index_imm, - @"or", - or_mem_imm, - or_scale_src, - or_scale_dst, - or_scale_imm, - or_mem_index_imm, - rol, - rol_mem_imm, - rol_scale_src, - rol_scale_dst, - rol_scale_imm, - rol_mem_index_imm, - ror, - ror_mem_imm, - ror_scale_src, - ror_scale_dst, - ror_scale_imm, - ror_mem_index_imm, - rcl, - rcl_mem_imm, - rcl_scale_src, - rcl_scale_dst, - rcl_scale_imm, - rcl_mem_index_imm, - rcr, - rcr_mem_imm, - rcr_scale_src, - rcr_scale_dst, - rcr_scale_imm, - rcr_mem_index_imm, - sbb, - sbb_mem_imm, - sbb_scale_src, - sbb_scale_dst, - sbb_scale_imm, - sbb_mem_index_imm, - cmp, - cmp_mem_imm, - cmp_scale_src, - cmp_scale_dst, - cmp_scale_imm, - cmp_mem_index_imm, - mov, - mov_mem_imm, - mov_scale_src, - mov_scale_dst, - mov_scale_imm, - mov_mem_index_imm, - - /// ops flags: form: - /// 0b00 reg1, reg2, - /// 0b01 reg1, byte ptr [reg2 + imm32] - /// 0b10 reg1, word ptr [reg2 + imm32] - /// 0b11 reg1, dword ptr [reg2 + imm32] - mov_sign_extend, - - /// ops flags: form: - /// 0b00 reg1, reg2 - /// 0b01 reg1, byte ptr [reg2 + imm32] - /// 0b10 reg1, word ptr [reg2 + imm32] - mov_zero_extend, - - /// ops flags: form: - /// 0b00 reg1, [reg2 + imm32] - /// 0b00 reg1, [ds:imm32] - /// 0b01 reg1, [rip + imm32] - /// 0b10 reg1, [reg2 + index + imm32] - /// Notes: - /// * 0b10 uses `IndexRegisterDisp` payload - lea, - - /// ops flags: form: - /// 0b00 reg1, [rip + reloc] // via GOT PIC - /// 0b01 reg1, [rip + reloc] // direct load PIC - /// 0b10 reg1, [rip + reloc] // via imports table PIC - /// Notes: - /// * `Data` contains `relocation` - lea_pic, - - /// ops flags: form: - /// 0b00 reg1, 1 - /// 0b01 reg1, .cl - /// 0b10 reg1, imm8 - /// Notes: - /// * If flags == 0b10, uses `imm`. - shl, - shl_mem_imm, - shl_scale_src, - shl_scale_dst, - shl_scale_imm, - shl_mem_index_imm, - sal, - sal_mem_imm, - sal_scale_src, - sal_scale_dst, - sal_scale_imm, - sal_mem_index_imm, - shr, - shr_mem_imm, - shr_scale_src, - shr_scale_dst, - shr_scale_imm, - shr_mem_index_imm, - sar, - sar_mem_imm, - sar_scale_src, - sar_scale_dst, - sar_scale_imm, - sar_mem_index_imm, - - /// ops flags: form: - /// 0b00 reg1 - /// 0b00 byte ptr [reg2 + imm32] - /// 0b01 word ptr [reg2 + imm32] - /// 0b10 dword ptr [reg2 + imm32] - /// 0b11 qword ptr [reg2 + imm32] - imul, - idiv, - mul, - div, - - /// ops flags: form: - /// 0b00 AX <- AL - /// 0b01 DX:AX <- AX - /// 0b10 EDX:EAX <- EAX - /// 0b11 RDX:RAX <- RAX - cwd, - - /// ops flags: form: - /// 0b00 reg1, reg2 - /// 0b01 reg1, [reg2 + imm32] - /// 0b01 reg1, [imm32] if reg2 is none - /// 0b10 reg1, reg2, imm32 - /// 0b11 reg1, [reg2 + imm32], imm32 - imul_complex, - - /// ops flags: form: - /// 0b00 reg1, imm64 - /// 0b01 rax, moffs64 - /// Notes: - /// * If reg1 is 64-bit, the immediate is 64-bit and stored - /// within extra data `Imm64`. - /// * For 0b01, reg1 (or reg2) need to be - /// a version of rax. If reg1 == .none, then reg2 == .rax, - /// or vice versa. - movabs, - - /// ops flags: form: - /// 0b00 word ptr [reg1 + imm32] - /// 0b01 dword ptr [reg1 + imm32] - /// 0b10 qword ptr [reg1 + imm32] - /// Notes: - /// * source is always ST(0) - /// * only supports memory operands as destination - fisttp, - - /// ops flags: form: - /// 0b01 dword ptr [reg1 + imm32] - /// 0b10 qword ptr [reg1 + imm32] - fld, - - /// ops flags: form: - /// 0b00 inst - /// 0b01 reg1 - /// 0b01 [imm32] if reg1 is none - /// 0b10 [reg1 + imm32] - jmp, - call, - - /// ops flags: - /// unused - /// Notes: - /// * uses `inst_cc` in Data. - cond_jmp, - - /// ops flags: - /// 0b00 reg1 - /// Notes: - /// * uses condition code (CC) stored as part of data - cond_set_byte, - - /// ops flags: - /// 0b00 reg1, reg2, - /// 0b01 reg1, word ptr [reg2 + imm] - /// 0b10 reg1, dword ptr [reg2 + imm] - /// 0b11 reg1, qword ptr [reg2 + imm] - /// Notes: - /// * uses condition code (CC) stored as part of data - cond_mov, - - /// ops flags: form: - /// 0b00 reg1 - /// 0b01 [reg1 + imm32] - /// 0b10 imm32 - /// Notes: - /// * If 0b10 is specified and the tag is push, pushes immediate onto the stack - /// using the mnemonic PUSH imm32. - push, - pop, - - /// ops flags: form: - /// 0b00 retf imm16 - /// 0b01 retf - /// 0b10 retn imm16 - /// 0b11 retn - ret, - - /// Fast system call - syscall, - - /// ops flags: form: - /// 0b00 reg1, imm32 if reg2 == .none - /// 0b00 reg1, reg2 - /// TODO handle more cases - @"test", - - /// Undefined Instruction - ud, - - /// Breakpoint form: - /// 0b00 int3 - interrupt, - - /// Nop - nop, - - /// SSE/AVX instructions - /// ops flags: form: - /// 0b00 reg1, qword ptr [reg2 + imm32] - /// 0b01 qword ptr [reg1 + imm32], reg2 - /// 0b10 reg1, reg2 - mov_f64, - mov_f32, - - /// ops flags: form: - /// 0b00 reg1, reg2 - add_f64, - add_f32, - - /// ops flags: form: - /// 0b00 reg1, reg2 - cmp_f64, - cmp_f32, - - /// Pseudo-instructions - /// call extern function - /// Notes: - /// * target of the call is stored as `relocation` in `Data` union. - call_extern, - - /// end of prologue - dbg_prologue_end, - - /// start of epilogue - dbg_epilogue_begin, - - /// update debug line - dbg_line, - - /// push registers - /// Uses `payload` field with `SaveRegisterList` as payload. - push_regs, - - /// pop registers - /// Uses `payload` field with `SaveRegisterList` as payload. - pop_regs, - }; - /// The position of an MIR instruction within the `Mir` instructions array. pub const Index = u32; - pub const Ops = packed struct { - reg1: u7, - reg2: u7, - flags: u2, + pub const Tag = enum(u8) { + /// Add with carry + adc, + /// Add + add, + /// Logical and + @"and", + /// Call + call, + /// Convert byte to word + cbw, + /// Convert word to doubleword + cwde, + /// Convert doubleword to quadword + cdqe, + /// Convert word to doubleword + cwd, + /// Convert doubleword to quadword + cdq, + /// Convert doubleword to quadword + cqo, + /// Logical compare + cmp, + /// Conditional move + cmovcc, + /// Unsigned division + div, + /// Store integer with truncation + fisttp, + /// Load floating-point value + fld, + /// Signed division + idiv, + /// Signed multiplication + imul, + /// + int3, + /// Conditional jump + jcc, + /// Jump + jmp, + /// Load effective address + lea, + /// Move + mov, + /// Move with sign extension + movsx, + /// Move with zero extension + movzx, + /// Multiply + mul, + /// No-op + nop, + /// Logical or + @"or", + /// Pop + pop, + /// Push + push, + /// Return + ret, + /// Arithmetic shift left + sal, + /// Arithmetic shift right + sar, + /// Integer subtraction with borrow + sbb, + /// Set byte on condition + setcc, + /// Logical shift left + shl, + /// Logical shift right + shr, + /// Subtract + sub, + /// Syscall + syscall, + /// Test condition + @"test", + /// Undefined instruction + ud2, + /// Logical exclusive-or + xor, - pub fn encode(vals: struct { - reg1: Register = .none, - reg2: Register = .none, - flags: u2 = 0b00, - }) Ops { - return .{ - .reg1 = @enumToInt(vals.reg1), - .reg2 = @enumToInt(vals.reg2), - .flags = vals.flags, - }; - } + /// Add single precision floating point + addss, + /// Compare scalar single-precision floating-point values + cmpss, + /// Move scalar single-precision floating-point value + movss, + /// Unordered compare scalar single-precision floating-point values + ucomiss, + /// Add double precision floating point + addsd, + /// Compare scalar double-precision floating-point values + cmpsd, + /// Move scalar double-precision floating-point value + movsd, + /// Unordered compare scalar double-precision floating-point values + ucomisd, - pub fn decode(ops: Ops) struct { - reg1: Register, - reg2: Register, - flags: u2, - } { - return .{ - .reg1 = @intToEnum(Register, ops.reg1), - .reg2 = @intToEnum(Register, ops.reg2), - .flags = ops.flags, - }; - } + /// End of prologue + dbg_prologue_end, + /// Start of epilogue + dbg_epilogue_begin, + /// Update debug line + /// Uses `payload` payload with data of type `DbgLineColumn`. + dbg_line, + /// Push registers + /// Uses `payload` payload with data of type `SaveRegisterList`. + push_regs, + /// Pop registers + /// Uses `payload` payload with data of type `SaveRegisterList`. + pop_regs, + }; + + pub const Ops = enum(u8) { + /// No data associated with this instruction (only mnemonic is used). + none, + /// Single register operand. + /// Uses `r` payload. + r, + /// Register, register operands. + /// Uses `rr` payload. + rr, + /// Register, register, register operands. + /// Uses `rrr` payload. + rrr, + /// Register, immediate (sign-extended) operands. + /// Uses `ri_s` payload. + ri_s, + /// Register, immediate (unsigned) operands. + /// Uses `ri_u` payload. + ri_u, + /// Register, 64-bit unsigned immediate operands. + /// Uses `rx` payload with payload type `Imm64`. + ri64, + /// Immediate (sign-extended) operand. + /// Uses `imm_s` payload. + imm_s, + /// Immediate (unsigned) operand. + /// Uses `imm_u` payload. + imm_u, + /// Relative displacement operand. + /// Uses `rel` payload. + rel, + /// Register, memory operands. + /// Uses `rx` payload. + rm, + /// Register, memory, immediate (unsigned) operands + /// Uses `rx` payload. + rmi_u, + /// Register, memory, immediate (sign-extended) operands + /// Uses `rx` payload. + rmi_s, + /// Memory, immediate (unsigned) operands. + /// Uses `payload` payload. + mi_u, + /// Memory, immediate (sign-extend) operands. + /// Uses `payload` payload. + mi_s, + /// Memory, register operands. + /// Uses `payload` payload. + mr, + /// Lea into register with linker relocation. + /// Uses `payload` payload with data of type `LeaRegisterReloc`. + lea_r_reloc, + /// References another Mir instruction directly. + /// Uses `inst` payload. + inst, + /// References another Mir instruction directly with condition code (CC). + /// Uses `inst_cc` payload. + inst_cc, + /// Uses `payload` payload with data of type `MemoryConditionCode`. + m_cc, + /// Uses `rx` payload with extra data of type `MemoryConditionCode`. + rm_cc, + /// Uses `reloc` payload. + reloc, }; - /// All instructions have a 4-byte payload, which is contained within - /// this union. `Tag` determines which union field is active, as well as - /// how to interpret the data within. pub const Data = union { - /// Another instruction. + /// References another Mir instruction. inst: Index, - /// A 32-bit immediate value. - imm: u32, - /// A 32-bit signed immediate value. - imm_s: i32, - /// A 32-bit signed displacement value. - disp: i32, - /// A condition code for use with EFLAGS register. - cc: bits.Condition, - /// Another instruction with condition code. - /// Used by `cond_jmp`. + /// Another instruction with condition code (CC). + /// Used by `jcc`. inst_cc: struct { /// Another instruction. inst: Index, /// A condition code for use with EFLAGS register. cc: bits.Condition, }, + /// A 32-bit signed immediate value. + imm_s: i32, + /// A 32-bit unsigned immediate value. + imm_u: u32, + /// A 32-bit signed relative offset value. + rel: i32, + r: Register, + rr: struct { + r1: Register, + r2: Register, + }, + rrr: struct { + r1: Register, + r2: Register, + r3: Register, + }, + /// Register, signed immediate. + ri_s: struct { + r1: Register, + imm: i32, + }, + /// Register, unsigned immediate. + ri_u: struct { + r1: Register, + imm: u32, + }, + /// Register, followed by custom payload found in extra. + rx: struct { + r1: Register, + payload: u32, + }, /// Relocation for the linker where: /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target @@ -458,62 +278,19 @@ pub const Inst = struct { } }; -pub const IndexRegisterDisp = struct { - /// Index register to use with SIB-based encoding - index: u32, - - /// Displacement value - disp: i32, - - pub fn encode(index: Register, disp: i32) IndexRegisterDisp { - return .{ - .index = @enumToInt(index), - .disp = disp, - }; - } - - pub fn decode(this: IndexRegisterDisp) struct { - index: Register, - disp: i32, - } { - return .{ - .index = @intToEnum(Register, this.index), - .disp = this.disp, - }; - } -}; - -/// TODO: would it be worth making `IndexRegisterDisp` and `IndexRegisterDispImm` a variable length list -/// instead of having two structs, one a superset of the other one? -pub const IndexRegisterDispImm = struct { - /// Index register to use with SIB-based encoding - index: u32, - - /// Displacement value - disp: i32, - - /// Immediate - imm: u32, - - pub fn encode(index: Register, disp: i32, imm: u32) IndexRegisterDispImm { - return .{ - .index = @enumToInt(index), - .disp = disp, - .imm = imm, - }; - } - - pub fn decode(this: IndexRegisterDispImm) struct { - index: Register, - disp: i32, - imm: u32, - } { - return .{ - .index = @intToEnum(Register, this.index), - .disp = this.disp, - .imm = this.imm, - }; - } +pub const LeaRegisterReloc = struct { + /// Destination register. + reg: Register, + /// Type of the load. + load_type: enum(u2) { + got, + direct, + import, + }, + /// Index of the containing atom. + atom_index: u32, + /// Index into the linker's symbol table. + sym_index: u32, }; /// Used in conjunction with `SaveRegisterList` payload to transfer a list of used registers @@ -557,16 +334,13 @@ pub const RegisterList = struct { }; pub const SaveRegisterList = struct { + /// Base register + base_reg: u32, /// Use `RegisterList` to populate. register_list: u32, stack_end: u32, }; -pub const ImmPair = struct { - dest_off: i32, - operand: u32, -}; - pub const Imm64 = struct { msb: u32, lsb: u32, diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 925e3fe181..690f777c28 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -4,10 +4,6 @@ const math = std.math; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); -const Immediate = bits.Immediate; -const Memory = bits.Memory; -const Moffs = bits.Moffs; -const PtrSize = bits.PtrSize; const Register = bits.Register; pub const Instruction = struct { @@ -25,6 +21,9 @@ pub const Instruction = struct { mem: Memory, imm: Immediate, + pub const Memory = bits.Memory; + pub const Immediate = bits.Immediate; + /// Returns the bitsize of the operand. pub fn bitSize(op: Operand) u64 { return switch (op) { @@ -296,7 +295,7 @@ pub const Instruction = struct { try encoder.opcode_1byte(prefix); } - fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void { + fn encodeMemory(encoding: Encoding, mem: Operand.Memory, operand: Operand, encoder: anytype) !void { const operand_enc = switch (operand) { .reg => |reg| reg.lowEnc(), .none => encoding.modRmExt(), @@ -379,7 +378,7 @@ pub const Instruction = struct { } } - fn encodeImm(imm: Immediate, kind: Encoding.Op, encoder: anytype) !void { + fn encodeImm(imm: Operand.Immediate, kind: Encoding.Op, encoder: anytype) !void { const raw = imm.asUnsigned(kind.bitSize()); switch (kind.bitSize()) { 8 => try encoder.imm8(@intCast(u8, raw)), From aa8fda799e64c02d44fe80d1297b5ec8ae6b7677 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 09:02:06 +0100 Subject: [PATCH 086/294] x86_64: split up assemble() into more declarative single-purpose helpers --- src/arch/x86_64/CodeGen.zig | 126 ++++++++++++++++++------------------ src/arch/x86_64/encoder.zig | 9 ++- 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index be8d07a2f6..6dacbadd8f 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -33,9 +33,11 @@ const errUnionPayloadOffset = codegen.errUnionPayloadOffset; const errUnionErrorOffset = codegen.errUnionErrorOffset; const Condition = bits.Condition; +const Immediate = bits.Immediate; +const Memory = bits.Memory; +const Register = bits.Register; const RegisterManager = abi.RegisterManager; const RegisterLock = RegisterManager.RegisterLock; -const Register = bits.Register; const gp = abi.RegisterClass.gp; const sse = abi.RegisterClass.sse; @@ -398,47 +400,58 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { return result; } -fn assemble(self: *Self, tag: Mir.Inst.Tag, args: struct { - op1: Mir.Operand = .none, - op2: Mir.Operand = .none, - op3: Mir.Operand = .none, - op4: Mir.Operand = .none, -}) !void { - const ops: Mir.Inst.Ops = blk: { - if (args.op1 == .none and args.op2 == .none and args.op3 == .none and args.op4 == .none) - break :blk .none; +fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { + _ = try self.addInst(.{ + .tag = tag, + .ops = .none, + .data = undefined, + }); +} - if (args.op1 == .reg and args.op2 == .reg) - break :blk .rr; - if (args.op1 == .reg and args.op2 == .imm) switch (args.op2.imm) { - .signed => break :blk .ri_s, - .unsigned => break :blk .ri_u, - }; - if (args.op1 == .reg) - break :blk .r; - if (args.op1 == .imm) switch (args.op1.imm) { - .signed => break :blk .imm_s, - .unsigned => break :blk .imm_u, // TODO 64bits - }; +fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void { + _ = try self.addInst(.{ + .tag = tag, + .ops = .r, + .data = .{ .r = reg }, + }); +} - unreachable; - }; +fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void { + // TODO imm64 + const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u; const data: Mir.Inst.Data = switch (ops) { - .none => undefined, - .imm_s => .{ .imm_s = args.op1.imm.signed }, - .imm_u => .{ .imm_u = @intCast(u32, args.op1.imm.unsigned) }, - .r => .{ .r = args.op1.reg }, - .rr => .{ .rr = .{ - .r1 = args.op1.reg, - .r2 = args.op2.reg, + .imm_s => .{ .imm_s = imm.signed }, + .imm_u => .{ .imm_u = @intCast(u32, imm.unsigned) }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + +fn asmRegisterRegister(self: *Self, tag: Mir.Inst.Tag, reg1: Register, reg2: Register) !void { + _ = try self.addInst(.{ + .tag = tag, + .ops = .rr, + .data = .{ .rr = .{ + .r1 = reg1, + .r2 = reg2, } }, + }); +} + +fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Immediate) !void { + const ops: Mir.Inst.Ops = if (imm == .signed) .ri_s else .ri_u; + const data: Mir.Inst.Data = switch (ops) { .ri_s => .{ .ri_s = .{ - .r1 = args.op1.reg, - .imm = args.op2.imm.signed, + .r1 = reg, + .imm = imm.signed, } }, .ri_u => .{ .ri_u = .{ - .r1 = args.op1.reg, - .imm = @intCast(u32, args.op2.imm.unsigned), + .r1 = reg, + .imm = @intCast(u32, imm.unsigned), } }, else => unreachable, }; @@ -452,13 +465,8 @@ fn assemble(self: *Self, tag: Mir.Inst.Tag, args: struct { fn gen(self: *Self) InnerError!void { const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { - try self.assemble(.push, .{ - .op1 = .{ .reg = .rbp }, - }); - try self.assemble(.mov, .{ - .op1 = .{ .reg = .rbp }, - .op2 = .{ .reg = .rsp }, - }); + try self.asmRegister(.push, .rbp); + try self.asmRegisterRegister(.mov, .rbp, .rsp); // We want to subtract the aligned stack frame size from rsp here, but we don't // yet know how big it will be, so we leave room for a 4-byte stack size. @@ -541,8 +549,8 @@ fn gen(self: *Self) InnerError!void { .data = undefined, }); - try self.assemble(.pop, .{ .op1 = .{ .reg = .rbp } }); - try self.assemble(.ret, .{}); + try self.asmRegister(.pop, .rbp); + try self.asmNone(.ret); // Adjust the stack if (self.max_end_stack > math.maxInt(i32)) { @@ -5313,23 +5321,19 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { - try self.assemble(.syscall, .{}); + try self.asmNone(.syscall); } else if (mem.indexOf(u8, ins, "push")) |_| { const arg = ins[4..]; if (mem.indexOf(u8, arg, "$")) |l| { const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch { return self.fail("TODO implement more inline asm int parsing", .{}); }; - try self.assemble(.push, .{ - .op1 = .{ .imm = Mir.Operand.Immediate.u(n) }, - }); + try self.asmImmediate(.push, Immediate.u(n)); } else if (mem.indexOf(u8, arg, "%%")) |l| { const reg_name = ins[4 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - try self.assemble(.push, .{ - .op1 = .{ .reg = reg }, - }); + try self.asmRegister(.push, reg); } else return self.fail("TODO more push operands", .{}); } else if (mem.indexOf(u8, ins, "pop")) |_| { const arg = ins[3..]; @@ -5337,9 +5341,7 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg_name = ins[3 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - try self.assemble(.pop, .{ - .op1 = .{ .reg = reg }, - }); + try self.asmRegister(.pop, reg); } else return self.fail("TODO more pop operands", .{}); } else { return self.fail("TODO implement support for more x86 assembly instructions", .{}); @@ -6119,19 +6121,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit // register is the fastest way to zero a register. if (x == 0) { - try self.assemble(.xor, .{ - .op1 = .{ .reg = reg.to32() }, - .op2 = .{ .reg = reg.to32() }, - }); - return; + return self.asmRegisterRegister(.xor, reg.to32(), reg.to32()); } if (x <= math.maxInt(i32)) { // Next best case: if we set the lower four bytes, the upper four will be zeroed. - try self.assemble(.mov, .{ - .op1 = .{ .reg = registerAlias(reg, abi_size) }, - .op2 = .{ .imm = Mir.Operand.Immediate.u(@intCast(u32, x)) }, - }); - return; + return self.asmRegisterImmediate( + .mov, + registerAlias(reg, abi_size), + Immediate.u(@intCast(u32, x)), + ); } // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls // this `movabs`, though this is officially just a different variant of the plain `mov` diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 690f777c28..292b61ee21 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -4,6 +4,8 @@ const math = std.math; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); +const Immediate = bits.Immediate; +const Memory = bits.Memory; const Register = bits.Register; pub const Instruction = struct { @@ -21,9 +23,6 @@ pub const Instruction = struct { mem: Memory, imm: Immediate, - pub const Memory = bits.Memory; - pub const Immediate = bits.Immediate; - /// Returns the bitsize of the operand. pub fn bitSize(op: Operand) u64 { return switch (op) { @@ -295,7 +294,7 @@ pub const Instruction = struct { try encoder.opcode_1byte(prefix); } - fn encodeMemory(encoding: Encoding, mem: Operand.Memory, operand: Operand, encoder: anytype) !void { + fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void { const operand_enc = switch (operand) { .reg => |reg| reg.lowEnc(), .none => encoding.modRmExt(), @@ -378,7 +377,7 @@ pub const Instruction = struct { } } - fn encodeImm(imm: Operand.Immediate, kind: Encoding.Op, encoder: anytype) !void { + fn encodeImm(imm: Immediate, kind: Encoding.Op, encoder: anytype) !void { const raw = imm.asUnsigned(kind.bitSize()); switch (kind.bitSize()) { 8 => try encoder.imm8(@intCast(u8, raw)), From f61a70e812b0301f4e54e38ff4ce2b041f395e8d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 09:38:59 +0100 Subject: [PATCH 087/294] x86_64: handle encoding and decoding Imm64 unsigned --- src/arch/x86_64/CodeGen.zig | 37 +++++++++++++++++-------------------- src/arch/x86_64/Emit.zig | 8 +++++++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 6dacbadd8f..d6dd63ee57 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -417,7 +417,6 @@ fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void { } fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void { - // TODO imm64 const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u; const data: Mir.Inst.Data = switch (ops) { .imm_s => .{ .imm_s = imm.signed }, @@ -443,7 +442,10 @@ fn asmRegisterRegister(self: *Self, tag: Mir.Inst.Tag, reg1: Register, reg2: Reg } fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Immediate) !void { - const ops: Mir.Inst.Ops = if (imm == .signed) .ri_s else .ri_u; + const ops: Mir.Inst.Ops = switch (imm) { + .signed => .ri_s, + .unsigned => |x| if (x <= math.maxInt(u32)) .ri_u else .ri64, + }; const data: Mir.Inst.Data = switch (ops) { .ri_s => .{ .ri_s = .{ .r1 = reg, @@ -453,6 +455,10 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme .r1 = reg, .imm = @intCast(u32, imm.unsigned), } }, + .ri64 => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.Imm64.encode(imm.unsigned)), + } }, else => unreachable, }; _ = try self.addInst(.{ @@ -6118,32 +6124,23 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // }); }, .immediate => |x| { - // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit - // register is the fastest way to zero a register. if (x == 0) { + // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit + // register is the fastest way to zero a register. return self.asmRegisterRegister(.xor, reg.to32(), reg.to32()); } - if (x <= math.maxInt(i32)) { - // Next best case: if we set the lower four bytes, the upper four will be zeroed. + if (ty.isSignedInt() and x <= math.maxInt(i32)) { return self.asmRegisterImmediate( .mov, registerAlias(reg, abi_size), - Immediate.u(@intCast(u32, x)), + Immediate.s(@intCast(i32, @bitCast(i64, x))), ); } - // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls - // this `movabs`, though this is officially just a different variant of the plain `mov` - // instruction. - // - // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only - // difference is that we set REX.W before the instruction, which extends the load to - // 64-bit and uses the full bit-width of the register. - // const payload = try self.addExtra(Mir.Imm64.encode(x)); - // _ = try self.addInst(.{ - // .tag = .movabs, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg.to64() }), - // .data = .{ .payload = payload }, - // }); + return self.asmRegisterImmediate( + .mov, + registerAlias(reg, abi_size), + Immediate.u(x), + ); }, .register => |src_reg| { // If the registers are the same, nothing to do. diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 15f41a943c..35b8b59846 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -178,9 +178,10 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE if (mem.eql(u8, field.name, @tagName(tag))) break @field(Instruction.Mnemonic, field.name); } else unreachable; - var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; const ops = emit.mir.instructions.items(.ops)[inst]; const data = emit.mir.instructions.items(.data)[inst]; + + var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; switch (ops) { .none => {}, .imm_s => operands[0] = .{ .imm = Immediate.s(data.imm_s) }, @@ -198,6 +199,11 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .reg = data.ri_u.r1 }, .{ .imm = Immediate.u(data.ri_u.imm) }, }, + .ri64 => { + operands[0] = .{ .reg = data.rx.r1 }; + const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data; + operands[1] = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }; + }, else => unreachable, } From 7221cd8ec90b5f206cf0b2979ba165719e5a2f23 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 11:48:23 +0100 Subject: [PATCH 088/294] x86_64: add helpers for CMOVcc and SETcc at the MIR level --- src/arch/x86_64/CodeGen.zig | 91 ++++++++-------------- src/arch/x86_64/Emit.zig | 146 +++++++++++------------------------- src/arch/x86_64/Mir.zig | 22 +++++- 3 files changed, 94 insertions(+), 165 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index d6dd63ee57..829d6b83fd 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -400,6 +400,29 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { return result; } +fn asmSetCCRegister(self: *Self, reg: Register, cc: bits.Condition) !void { + _ = try self.addInst(.{ + .tag = .setcc, + .ops = .r_c, + .data = .{ .r_c = .{ + .r1 = reg, + .cc = cc, + } }, + }); +} + +fn asmCmovCCRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bits.Condition) !void { + _ = try self.addInst(.{ + .tag = .cmovcc, + .ops = .rr_c, + .data = .{ .rr_c = .{ + .r1 = reg1, + .r2 = reg2, + .cc = cc, + } }, + }); +} + fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, @@ -1346,15 +1369,7 @@ fn airMin(self: *Self, inst: Air.Inst.Index) !void { .unsigned => .b, .signed => .l, }; - _ = cc; - // _ = try self.addInst(.{ - // .tag = .cond_mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_mcv.register, - // .reg2 = lhs_reg, - // }), - // .data = .{ .cc = cc }, - // }); + try self.asmCmovCCRegisterRegister(dst_mcv.register, lhs_reg, cc); break :result dst_mcv; }; @@ -1554,14 +1569,7 @@ fn genSetStackTruncatedOverflowCompare( .signed => .o, .unsigned => .c, }; - _ = cc; - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = overflow_reg.to8(), - // }), - // .data = .{ .cc = cc }, - // }); + try self.asmSetCCRegister(overflow_reg.to8(), cc); const scratch_reg = temp_regs[1]; try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg }); @@ -1574,12 +1582,7 @@ fn genSetStackTruncatedOverflowCompare( ); const eq_reg = temp_regs[2]; - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = eq_reg.to8() }), - // .data = .{ .cc = .ne }, - // }); - + try self.asmSetCCRegister(eq_reg.to8(), .ne); try self.genBinOpMir( .@"or", Type.u8, @@ -1829,14 +1832,7 @@ fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCVa // }), // .data = undefined, // }); - // _ = try self.addInst(.{ - // .tag = .cond_mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = divisor.to64(), - // .reg2 = .rdx, - // }), - // .data = .{ .cc = .e }, - // }); + try self.asmCmovCCRegisterRegister(divisor.to64(), .rdx, .e); try self.genBinOpMir(.add, Type.isize, .{ .register = divisor }, .{ .register = .rax }); return MCValue{ .register = divisor }; } @@ -2881,13 +2877,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type const overflow_bit_ty = value_ty.structFieldType(1); const overflow_bit_offset = value_ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg.to8(), - // }), - // .data = .{ .cc = ro.eflags }, - // }); + try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); try self.genInlineMemcpyRegisterRegister( overflow_bit_ty, reg, @@ -3185,13 +3175,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { defer self.register_manager.unlockReg(reg_lock); const dst_reg = try self.register_manager.allocReg(inst, gp); - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg.to8(), - // }), - // .data = .{ .cc = ro.eflags }, - // }); + try self.asmSetCCRegister(dst_reg.to8(), ro.eflags); break :result MCValue{ .register = dst_reg.to8() }; }, else => unreachable, @@ -5577,13 +5561,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl const overflow_bit_ty = ty.structFieldType(1); const overflow_bit_offset = ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg.to8(), - // }), - // .data = .{ .cc = ro.eflags }, - // }); + try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); return self.genSetStack( overflow_bit_ty, @@ -6114,14 +6092,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .eflags => |cc| { - _ = cc; - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to8(), - // }), - // .data = .{ .cc = cc }, - // }); + return self.asmSetCCRegister(reg.to8(), cc); }, .immediate => |x| { if (x == 0) { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 35b8b59846..2b49a6051c 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -118,6 +118,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { => try emit.mirEncodeGeneric(tag, inst), // Pseudo-instructions + .cmovcc => try emit.mirCmovCC(inst), + .setcc => try emit.mirSetCC(inst), + .dbg_line => try emit.mirDbgLine(inst), .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst), @@ -200,9 +203,11 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(data.ri_u.imm) }, }, .ri64 => { - operands[0] = .{ .reg = data.rx.r1 }; const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data; - operands[1] = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }; + operands[0..2].* = .{ + .{ .reg = data.rx.r1 }, + .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, + }; }, else => unreachable, } @@ -215,6 +220,42 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } +fn mnemonicFromCC(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { + inline for (@typeInfo(bits.Condition).Enum.fields) |field| { + if (mem.eql(u8, field.name, @tagName(cc))) + return @field(Instruction.Mnemonic, basename ++ field.name); + } else unreachable; +} + +fn mirCmovCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .rr_c => { + const data = emit.mir.instructions.items(.data)[inst].rr_c; + const mnemonic = mnemonicFromCC("cmov", data.cc); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = data.r1 }, + .op2 = .{ .reg = data.r2 }, + }); + }, + else => unreachable, // TODO + } +} + +fn mirSetCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .r_c => { + const data = emit.mir.instructions.items(.data)[inst].r_c; + const mnemonic = mnemonicFromCC("set", data.cc); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = data.r1 }, + }); + }, + else => unreachable, // TODO + } +} + fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; @@ -333,107 +374,6 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) // }); // } -// fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .cond_set_byte); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// const cc = emit.mir.instructions.items(.data)[inst].cc; -// const mnemonic: Instruction.Mnemonic = switch (cc) { -// .a => .seta, -// .ae => .setae, -// .b => .setb, -// .be => .setbe, -// .c => .setc, -// .e => .sete, -// .g => .setg, -// .ge => .setge, -// .l => .setl, -// .le => .setle, -// .na => .setna, -// .nae => .setnae, -// .nb => .setnb, -// .nbe => .setnbe, -// .nc => .setnc, -// .ne => .setne, -// .ng => .setng, -// .nge => .setnge, -// .nl => .setnl, -// .nle => .setnle, -// .no => .setno, -// .np => .setnp, -// .ns => .setns, -// .nz => .setnz, -// .o => .seto, -// .p => .setp, -// .pe => .setpe, -// .po => .setpo, -// .s => .sets, -// .z => .setz, -// }; -// return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); -// } - -// fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .cond_mov); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// const cc = emit.mir.instructions.items(.data)[inst].cc; -// const mnemonic: Instruction.Mnemonic = switch (cc) { -// .a => .cmova, -// .ae => .cmovae, -// .b => .cmovb, -// .be => .cmovbe, -// .c => .cmovc, -// .e => .cmove, -// .g => .cmovg, -// .ge => .cmovge, -// .l => .cmovl, -// .le => .cmovle, -// .na => .cmovna, -// .nae => .cmovnae, -// .nb => .cmovnb, -// .nbe => .cmovnbe, -// .nc => .cmovnc, -// .ne => .cmovne, -// .ng => .cmovng, -// .nge => .cmovnge, -// .nl => .cmovnl, -// .nle => .cmovnle, -// .no => .cmovno, -// .np => .cmovnp, -// .ns => .cmovns, -// .nz => .cmovnz, -// .o => .cmovo, -// .p => .cmovp, -// .pe => .cmovpe, -// .po => .cmovpo, -// .s => .cmovs, -// .z => .cmovz, -// }; -// const op1: Instruction.Operand = .{ .reg = ops.reg1 }; - -// if (ops.flags == 0b00) { -// return emit.encode(mnemonic, .{ -// .op1 = op1, -// .op2 = .{ .reg = ops.reg2 }, -// }); -// } -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// const ptr_size: Memory.PtrSize = switch (ops.flags) { -// 0b00 => unreachable, -// 0b01 => .word, -// 0b10 => .dword, -// 0b11 => .qword, -// }; -// return emit.encode(mnemonic, .{ -// .op1 = op1, -// .op2 = .{ .mem = Memory.sib(ptr_size, .{ -// .base = ops.reg2, -// .disp = disp, -// }) }, -// }); -// } - // fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // const tag = emit.mir.instructions.items(.tag)[inst]; // assert(tag == .lea); diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 5f4ed05deb..02bc70614d 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -56,8 +56,6 @@ pub const Inst = struct { cqo, /// Logical compare cmp, - /// Conditional move - cmovcc, /// Unsigned division div, /// Store integer with truncation @@ -134,6 +132,9 @@ pub const Inst = struct { /// Unordered compare scalar double-precision floating-point values ucomisd, + /// Conditional move + cmovcc, + /// End of prologue dbg_prologue_end, /// Start of epilogue @@ -161,6 +162,12 @@ pub const Inst = struct { /// Register, register, register operands. /// Uses `rrr` payload. rrr, + /// Register with condition code (CC). + /// Uses `r_c` payload. + r_c, + /// Register, register with condition code (CC). + /// Uses `rr_c` payload. + rr_c, /// Register, immediate (sign-extended) operands. /// Uses `ri_s` payload. ri_s, @@ -241,6 +248,17 @@ pub const Inst = struct { r2: Register, r3: Register, }, + /// Register with condition code (CC). + r_c: struct { + r1: Register, + cc: bits.Condition, + }, + /// Register, register with condition code (CC). + rr_c: struct { + r1: Register, + r2: Register, + cc: bits.Condition, + }, /// Register, signed immediate. ri_s: struct { r1: Register, From 1bde522c2c6cae52c581458774ad1dfa479d8426 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 15:03:41 +0100 Subject: [PATCH 089/294] x86_64: add helper for Jcc instruction --- src/arch/x86_64/CodeGen.zig | 247 ++++++++++++++---------------------- src/arch/x86_64/Emit.zig | 85 +++++-------- 2 files changed, 126 insertions(+), 206 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 829d6b83fd..711dc29724 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4511,35 +4511,29 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { const abi_size = ty.abiSize(self.target.*); switch (mcv) { .eflags => |cc| { - _ = cc; - // return self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ - // .inst_cc = .{ - // .inst = undefined, - // // Here we map the opposites since the jump is to the false branch. - // .cc = cc.negate(), - // }, - // }, - // }); + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ + .inst_cc = .{ + .inst = undefined, + // Here we map the opposites since the jump is to the false branch. + .cc = cc.negate(), + }, + }, + }); }, .register => |reg| { - _ = reg; try self.spillEflagsIfOccupied(); - // _ = try self.addInst(.{ - // .tag = .@"test", - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - // .data = .{ .imm = 1 }, - // }); - // return self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .e, - // } }, - // }); + try self.asmRegisterImmediate(.@"test", reg, Immediate.u(1)); + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .e, + } }, + }); }, .immediate, .stack_offset, @@ -4961,22 +4955,17 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u }, } - // _ = try self.addInst(.{ - // .tag = .@"test", - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(cond_reg, abi_size), - // .reg2 = registerAlias(cond_reg, abi_size), - // }), - // .data = undefined, - // }); - // return self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .ne, - // } }, - // }); + const aliased_reg = registerAlias(cond_reg, abi_size); + try self.asmRegisterRegister(.@"test", aliased_reg, aliased_reg); + + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .ne, + } }, + }); }, .stack_offset => { try self.spillEflagsIfOccupied(); @@ -5189,9 +5178,9 @@ fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Bran fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { const next_inst = @intCast(u32, self.mir_instructions.len); switch (self.mir_instructions.items(.tag)[reloc]) { - // .cond_jmp => { - // self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; - // }, + .jcc => { + self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; + }, .jmp => { self.mir_instructions.items(.data)[reloc].inst = next_inst; }, @@ -5806,7 +5795,6 @@ fn genInlineMemcpy( const index_reg = regs[2].to64(); const count_reg = regs[3].to64(); const tmp_reg = regs[4].to8(); - _ = index_reg; _ = tmp_reg; switch (dst_ptr) { @@ -5825,15 +5813,11 @@ fn genInlineMemcpy( // }); }, .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - // .reg2 = reg, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .mov, + registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + reg, + ); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -5856,15 +5840,11 @@ fn genInlineMemcpy( // }); }, .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - // .reg2 = reg, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .mov, + registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + reg, + ); }, else => { return self.fail("TODO implement memcpy for setting stack when src is {}", .{src_ptr}); @@ -5873,30 +5853,24 @@ fn genInlineMemcpy( try self.genSetReg(Type.usize, count_reg, len); - // mov index_reg, 0 - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - // .data = .{ .imm = 0 }, - // }); + try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(0)); - // loop: - // cmp count, 0 - // const loop_start = try self.addInst(.{ - // .tag = .cmp, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - // .data = .{ .imm = 0 }, - // }); - - // je end - // const loop_reloc = try self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .e, - // } }, - // }); + const loop_start = try self.addInst(.{ + .tag = .cmp, + .ops = .ri_u, + .data = .{ .ri_u = .{ + .r1 = count_reg, + .imm = 0, + } }, + }); + const loop_reloc = try self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .e, + } }, + }); // mov tmp, [addr + index_reg] // _ = try self.addInst(.{ @@ -5918,29 +5892,16 @@ fn genInlineMemcpy( // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, // }); - // add index_reg, 1 - // _ = try self.addInst(.{ - // .tag = .add, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - // .data = .{ .imm = 1 }, - // }); + try self.asmRegisterImmediate(.add, index_reg, Immediate.u(1)); + try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); - // sub count, 1 - // _ = try self.addInst(.{ - // .tag = .sub, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - // .data = .{ .imm = 1 }, - // }); + _ = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = loop_start }, + }); - // jmp loop - // _ = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = loop_start }, - // }); - - // end: - // try self.performReloc(loop_reloc); + try self.performReloc(loop_reloc); } fn genInlineMemset( @@ -5982,15 +5943,11 @@ fn genInlineMemset( // }); }, .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - // .reg2 = reg, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .mov, + registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + reg, + ); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -6000,26 +5957,23 @@ fn genInlineMemset( try self.genSetReg(Type.usize, index_reg, len); try self.genBinOpMir(.sub, Type.usize, .{ .register = index_reg }, .{ .immediate = 1 }); - // loop: - // cmp index_reg, -1 - // const loop_start = try self.addInst(.{ - // .tag = .cmp, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = index_reg, - // .flags = 0b11, - // }), - // .data = .{ .imm_s = -1 }, - // }); + const loop_start = try self.addInst(.{ + .tag = .cmp, + .ops = .ri_s, + .data = .{ .ri_s = .{ + .r1 = index_reg, + .imm = -1, + } }, + }); - // je end - // const loop_reloc = try self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .e, - // } }, - // }); + const loop_reloc = try self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .e, + } }, + }); switch (value) { .immediate => |x| { @@ -6042,22 +5996,15 @@ fn genInlineMemset( else => return self.fail("TODO inline memset for value of type {}", .{value}), } - // sub index_reg, 1 - // _ = try self.addInst(.{ - // .tag = .sub, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - // .data = .{ .imm = 1 }, - // }); + try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1)); - // jmp loop - // _ = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = loop_start }, - // }); + _ = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = loop_start }, + }); - // end: - // try self.performReloc(loop_reloc); + try self.performReloc(loop_reloc); } fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 2b49a6051c..607fc00e41 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1,4 +1,3 @@ -//! //! This file contains the functionality for lowering x86_64 MIR into //! machine code @@ -118,8 +117,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { => try emit.mirEncodeGeneric(tag, inst), // Pseudo-instructions - .cmovcc => try emit.mirCmovCC(inst), - .setcc => try emit.mirSetCC(inst), + .cmovcc => try emit.mirCmovcc(inst), + .setcc => try emit.mirSetcc(inst), + .jcc => try emit.mirJcc(inst), .dbg_line => try emit.mirDbgLine(inst), .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), @@ -220,19 +220,19 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } -fn mnemonicFromCC(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { +fn mnemonicFromConditionCode(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { inline for (@typeInfo(bits.Condition).Enum.fields) |field| { if (mem.eql(u8, field.name, @tagName(cc))) return @field(Instruction.Mnemonic, basename ++ field.name); } else unreachable; } -fn mirCmovCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirCmovcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; switch (ops) { .rr_c => { const data = emit.mir.instructions.items(.data)[inst].rr_c; - const mnemonic = mnemonicFromCC("cmov", data.cc); + const mnemonic = mnemonicFromConditionCode("cmov", data.cc); return emit.encode(mnemonic, .{ .op1 = .{ .reg = data.r1 }, .op2 = .{ .reg = data.r2 }, @@ -242,12 +242,12 @@ fn mirCmovCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirSetCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirSetcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; switch (ops) { .r_c => { const data = emit.mir.instructions.items(.data)[inst].r_c; - const mnemonic = mnemonicFromCC("set", data.cc); + const mnemonic = mnemonicFromConditionCode("set", data.cc); return emit.encode(mnemonic, .{ .op1 = .{ .reg = data.r1 }, }); @@ -256,6 +256,27 @@ fn mirSetCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } +fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .inst_cc => { + const data = emit.mir.instructions.items(.data)[inst].inst_cc; + const mnemonic = mnemonicFromConditionCode("j", data.cc); + const source = emit.code.items.len; + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + try emit.relocs.append(emit.bin_file.allocator, .{ + .source = source, + .target = data.inst, + .offset = emit.code.items.len - 4, + .length = 6, + }); + }, + else => unreachable, // TODO + } +} + fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; @@ -326,54 +347,6 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) // } // } -// fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .cond_jmp); -// const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; -// const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { -// .a => .ja, -// .ae => .jae, -// .b => .jb, -// .be => .jbe, -// .c => .jc, -// .e => .je, -// .g => .jg, -// .ge => .jge, -// .l => .jl, -// .le => .jle, -// .na => .jna, -// .nae => .jnae, -// .nb => .jnb, -// .nbe => .jnbe, -// .nc => .jnc, -// .ne => .jne, -// .ng => .jng, -// .nge => .jnge, -// .nl => .jnl, -// .nle => .jnle, -// .no => .jno, -// .np => .jnp, -// .ns => .jns, -// .nz => .jnz, -// .o => .jo, -// .p => .jp, -// .pe => .jpe, -// .po => .jpo, -// .s => .js, -// .z => .jz, -// }; -// const source = emit.code.items.len; -// try emit.encode(mnemonic, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// try emit.relocs.append(emit.bin_file.allocator, .{ -// .source = source, -// .target = inst_cc.inst, -// .offset = emit.code.items.len - 4, -// .length = 6, -// }); -// } - // fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // const tag = emit.mir.instructions.items(.tag)[inst]; // assert(tag == .lea); From 9658ab676643ef5c2457ac4908c180e05dc7f729 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 18:38:54 +0100 Subject: [PATCH 090/294] x86_64: handle all instructions without introducing Memory operand --- src/arch/x86_64/CodeGen.zig | 463 ++++++++++++------------------------ src/arch/x86_64/Emit.zig | 111 ++++++++- 2 files changed, 256 insertions(+), 318 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 711dc29724..f5ee4d99eb 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -400,7 +400,7 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { return result; } -fn asmSetCCRegister(self: *Self, reg: Register, cc: bits.Condition) !void { +fn asmSetccRegister(self: *Self, reg: Register, cc: bits.Condition) !void { _ = try self.addInst(.{ .tag = .setcc, .ops = .r_c, @@ -411,7 +411,7 @@ fn asmSetCCRegister(self: *Self, reg: Register, cc: bits.Condition) !void { }); } -fn asmCmovCCRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bits.Condition) !void { +fn asmCmovccRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bits.Condition) !void { _ = try self.addInst(.{ .tag = .cmovcc, .ops = .rr_c, @@ -1369,7 +1369,7 @@ fn airMin(self: *Self, inst: Air.Inst.Index) !void { .unsigned => .b, .signed => .l, }; - try self.asmCmovCCRegisterRegister(dst_mcv.register, lhs_reg, cc); + try self.asmCmovccRegisterRegister(dst_mcv.register, lhs_reg, cc); break :result dst_mcv; }; @@ -1569,7 +1569,7 @@ fn genSetStackTruncatedOverflowCompare( .signed => .o, .unsigned => .c, }; - try self.asmSetCCRegister(overflow_reg.to8(), cc); + try self.asmSetccRegister(overflow_reg.to8(), cc); const scratch_reg = temp_regs[1]; try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg }); @@ -1582,7 +1582,7 @@ fn genSetStackTruncatedOverflowCompare( ); const eq_reg = temp_regs[2]; - try self.asmSetCCRegister(eq_reg.to8(), .ne); + try self.asmSetccRegister(eq_reg.to8(), .ne); try self.genBinOpMir( .@"or", Type.u8, @@ -1725,26 +1725,10 @@ fn genIntMulDivOpMir( try self.genSetReg(ty, .rax, lhs); } - _ = signedness; - // switch (signedness) { - // .signed => { - // _ = try self.addInst(.{ - // .tag = .cwd, - // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), - // .data = undefined, - // }); - // }, - // .unsigned => { - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rdx, - // .reg2 = .rdx, - // }), - // .data = undefined, - // }); - // }, - // } + switch (signedness) { + .signed => try self.asmNone(.cqo), + .unsigned => try self.asmRegisterRegister(.xor, .rdx, .rdx), + } const factor = switch (rhs) { .register => rhs, @@ -1754,35 +1738,28 @@ fn genIntMulDivOpMir( break :blk MCValue{ .register = reg }; }, }; - _ = factor; - _ = tag; - // switch (factor) { - // .register => |reg| { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - // .data = undefined, - // }); - // }, - // .stack_offset => |off| { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg2 = .rbp, - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // 8 => 0b11, - // else => unreachable, - // }, - // }), - // .data = .{ .disp = -off }, - // }); - // }, - // else => unreachable, - // } + switch (factor) { + .register => |reg| try self.asmRegister(tag, reg), + .stack_offset => |off| { + _ = off; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg2 = .rbp, + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // 8 => 0b11, + // else => unreachable, + // }, + // }), + // .data = .{ .disp = -off }, + // }); + }, + else => unreachable, + } } /// Always returns a register. @@ -1808,31 +1785,10 @@ fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCVa .unsigned => .div, }, Type.isize, signedness, .{ .register = dividend }, .{ .register = divisor }); - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = divisor.to64(), - // .reg2 = dividend.to64(), - // }), - // .data = undefined, - // }); - // _ = try self.addInst(.{ - // .tag = .sar, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = divisor.to64(), - // .flags = 0b10, - // }), - // .data = .{ .imm = 63 }, - // }); - // _ = try self.addInst(.{ - // .tag = .@"test", - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rdx, - // .reg2 = .rdx, - // }), - // .data = undefined, - // }); - try self.asmCmovCCRegisterRegister(divisor.to64(), .rdx, .e); + try self.asmRegisterRegister(.xor, divisor.to64(), dividend.to64()); + try self.asmRegisterImmediate(.sar, divisor.to64(), Immediate.u(63)); + try self.asmRegisterRegister(.@"test", .rdx, .rdx); + try self.asmCmovccRegisterRegister(divisor.to64(), .rdx, .e); try self.genBinOpMir(.add, Type.isize, .{ .register = divisor }, .{ .register = .rax }); return MCValue{ .register = divisor }; } @@ -2877,7 +2833,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type const overflow_bit_ty = value_ty.structFieldType(1); const overflow_bit_offset = value_ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); + try self.asmSetccRegister(tmp_reg.to8(), ro.eflags); try self.genInlineMemcpyRegisterRegister( overflow_bit_ty, reg, @@ -3151,14 +3107,11 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { }; const field_size = @intCast(u32, struct_field_ty.abiSize(self.target.*)); if (signedness == .signed and field_size < 8) { - // _ = try self.addInst(.{ - // .tag = .mov_sign_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_mcv.register, - // .reg2 = registerAlias(dst_mcv.register, field_size), - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .movsx, + dst_mcv.register, + registerAlias(dst_mcv.register, field_size), + ); } break :result dst_mcv; @@ -3175,7 +3128,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { defer self.register_manager.unlockReg(reg_lock); const dst_reg = try self.register_manager.allocReg(inst, gp); - try self.asmSetCCRegister(dst_reg.to8(), ro.eflags); + try self.asmSetccRegister(dst_reg.to8(), ro.eflags); break :result MCValue{ .register = dst_reg.to8() }; }, else => unreachable, @@ -3211,25 +3164,7 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi switch (shift) { .immediate => |imm| switch (imm) { 0 => return, - 1 => { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - // .data = undefined, - // }); - return; - }, - else => { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = 0b10, - // }), - // .data = .{ .imm = @intCast(u8, imm) }, - // }); - return; - }, + else => return self.asmRegisterImmediate(tag, registerAlias(reg, abi_size), Immediate.u(imm)), }, .register => |shift_reg| { if (shift_reg == .rcx) break :blk; @@ -3240,16 +3175,8 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi try self.register_manager.getReg(.rcx, null); try self.genSetReg(Type.u8, .rcx, shift); } - _ = abi_size; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister(tag, registerAlias(reg, abi_size), .cl); } /// Result is always a register. @@ -3620,43 +3547,38 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .register => |src_reg| switch (dst_ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, dst_ty)) { - // const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { - // .f32 => switch (mir_tag) { - // .add => Mir.Inst.Tag.add_f32, - // .cmp => Mir.Inst.Tag.cmp_f32, - // else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), - // }, - // .f64 => switch (mir_tag) { - // .add => Mir.Inst.Tag.add_f64, - // .cmp => Mir.Inst.Tag.cmp_f64, - // else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), - // }, - // else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = actual_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg.to128(), - // .reg2 = src_reg.to128(), - // }), - // .data = undefined, - // }); - return; + const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { + .f32 => switch (mir_tag) { + .add => .addss, + .cmp => .cmpss, + else => return self.fail( + "TODO genBinOpMir for f32 register-register with MIR tag {}", + .{mir_tag}, + ), + }, + .f64 => switch (mir_tag) { + .add => .addsd, + .cmp => .cmpsd, + else => return self.fail( + "TODO genBinOpMir for f64 register-register with MIR tag {}", + .{mir_tag}, + ), + }, + else => return self.fail( + "TODO genBinOpMir for float register-register and type {}", + .{dst_ty.fmtDebug()}, + ), + }; + try self.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128()); } return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{}); }, - else => { - _ = src_reg; - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - }, + else => try self.asmRegisterRegister( + mir_tag, + registerAlias(dst_reg, abi_size), + registerAlias(src_reg, abi_size), + ), }, .immediate => |imm| { _ = imm; @@ -3777,7 +3699,6 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu /// Does not support byte-size operands. fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) InnerError!void { const abi_size = @intCast(u32, dst_ty.abiSize(self.target.*)); - _ = abi_size; switch (dst_mcv) { .none => unreachable, .undef => unreachable, @@ -3792,18 +3713,11 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .dead, .unreach => unreachable, .ptr_stack_offset => unreachable, .register_overflow => unreachable, - .register => |src_reg| { - _ = src_reg; - // register, register - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - }, + .register => |src_reg| try self.asmRegisterRegister( + .imul, + registerAlias(dst_reg, abi_size), + registerAlias(src_reg, abi_size), + ), .immediate => |imm| { // TODO take into account the type's ABI size when selecting the register alias // register, immediate @@ -3992,20 +3906,12 @@ fn genVarDbgInfo( } fn airTrap(self: *Self) !void { - // _ = try self.addInst(.{ - // .tag = .ud, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = undefined, - // }); + try self.asmNone(.ud2); return self.finishAirBookkeeping(); } fn airBreakpoint(self: *Self) !void { - // _ = try self.addInst(.{ - // .tag = .interrupt, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = undefined, - // }); + try self.asmNone(.int3); return self.finishAirBookkeeping(); } @@ -4117,13 +4023,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (self.bin_file.cast(link.File.Elf)) |elf_file| { const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); - const got_addr = @intCast(i32, atom.getOffsetTableAddress(elf_file)); - _ = got_addr; - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - // .data = .{ .disp = got_addr }, - // }); + const got_addr = atom.getOffsetTableAddress(elf_file); + try self.asmImmediate(.call, Immediate.s(@intCast(i32, got_addr))); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; @@ -4133,14 +4034,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const atom_index = try macho_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; @@ -4150,14 +4044,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { const decl_block_index = try p9.seeDecl(func.owner_decl); const decl_block = p9.getDeclBlock(decl_block_index); @@ -4166,12 +4053,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = p9.bases.data; const got_index = decl_block.got_index.?; const fn_got_addr = got_addr + got_index * ptr_bytes; - _ = fn_got_addr; - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - // .data = .{ .disp = @intCast(i32, fn_got_addr) }, - // }); + try self.asmImmediate(.call, Immediate.s(@intCast(i32, fn_got_addr))); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; @@ -4191,14 +4073,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); @@ -4223,23 +4098,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier assert(ty.zigTypeTag() == .Pointer); const mcv = try self.resolveInst(callee); try self.genSetReg(Type.initTag(.usize), .rax, mcv); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } if (info.stack_byte_count > 0) { // Readjust the stack - // _ = try self.addInst(.{ - // .tag = .add, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - // .data = .{ .imm = info.stack_byte_count }, - // }); + try self.asmRegisterImmediate(.add, .rsp, Immediate.u(info.stack_byte_count)); } const result: MCValue = result: { @@ -4296,12 +4160,12 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - // const jmp_reloc = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = undefined }, - // }); - // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + const jmp_reloc = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = undefined }, + }); + try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4332,12 +4196,12 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - // const jmp_reloc = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = undefined }, - // }); - // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + const jmp_reloc = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = undefined }, + }); + try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4872,13 +4736,12 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const loop = self.air.extraData(Air.Block, ty_pl.payload); const body = self.air.extra[loop.end..][0..loop.data.body_len]; const jmp_target = @intCast(u32, self.mir_instructions.len); - _ = jmp_target; try self.genBody(body); - // _ = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = jmp_target }, - // }); + _ = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = jmp_target }, + }); return self.finishAirBookkeeping(); } @@ -4923,25 +4786,16 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u .none => unreachable, .undef => unreachable, .dead, .unreach => unreachable, - .immediate => |imm| { - _ = imm; - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - // .data = .{ .imm = @intCast(u32, imm) }, - // }); - }, - .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(cond_reg, abi_size), - // .reg2 = registerAlias(reg, abi_size), - // }), - // .data = undefined, - // }); - }, + .immediate => |imm| try self.asmRegisterImmediate( + .xor, + registerAlias(cond_reg, abi_size), + Immediate.u(imm), + ), + .register => |reg| try self.asmRegisterRegister( + .xor, + registerAlias(cond_reg, abi_size), + registerAlias(reg, abi_size), + ), .stack_offset => { if (abi_size <= 8) { const reg = try self.copyToTmpRegister(ty, case); @@ -5223,12 +5077,12 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined - // const jmp_reloc = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = undefined }, - // }); - // block_data.relocs.appendAssumeCapacity(jmp_reloc); + const jmp_reloc = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = undefined }, + }); + block_data.relocs.appendAssumeCapacity(jmp_reloc); } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { @@ -5463,11 +5317,15 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), - // }; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail( + "TODO genSetStackArg for register for type {}", + .{ty.fmtDebug()}, + ), + }; + _ = tag; _ = reg; // _ = try self.addInst(.{ // .tag = tag, @@ -5550,7 +5408,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl const overflow_bit_ty = ty.structFieldType(1); const overflow_bit_offset = ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); + try self.asmSetccRegister(tmp_reg.to8(), ro.eflags); return self.genSetStack( overflow_bit_ty, @@ -6039,7 +5897,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .eflags => |cc| { - return self.asmSetCCRegister(reg.to8(), cc); + return self.asmSetccRegister(reg.to8(), cc); }, .immediate => |x| { if (x == 0) { @@ -6069,63 +5927,38 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Int => switch (ty.intInfo(self.target.*).signedness) { .signed => { if (abi_size <= 4) { - // _ = try self.addInst(.{ - // .tag = .mov_sign_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - return; + return self.asmRegisterRegister( + .movsx, + reg.to64(), + registerAlias(src_reg, abi_size), + ); } }, .unsigned => { if (abi_size <= 2) { - // _ = try self.addInst(.{ - // .tag = .mov_zero_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - return; + return self.asmRegisterRegister( + .movzx, + reg.to64(), + registerAlias(src_reg, abi_size), + ); } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = src_reg.to128(), - // .flags = 0b10, - // }), - // .data = undefined, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), + }; + return self.asmRegisterRegister(tag, reg.to128(), src_reg.to128()); } - return self.fail("TODO genSetReg from register for float with no intrinsics", .{}); }, else => {}, } - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister(.mov, registerAlias(reg, abi_size), registerAlias(src_reg, abi_size)); }, .linker_load => { switch (ty.zigTypeTag()) { @@ -6214,7 +6047,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } else { // If this is RAX, we can use a direct load. // Otherwise, we need to load the address, then indirectly load the value. - if (reg.id() == 0) { + if (reg.to64() == .rax) { // movabs rax, ds:moffs64 // const payload = try self.addExtra(Mir.Imm64.encode(x)); // _ = try self.addInst(.{ diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 607fc00e41..4eff2d11a0 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -87,7 +87,6 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .imul, .int3, .mov, - .movsx, .movzx, .mul, .nop, @@ -116,7 +115,11 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .ucomisd, => try emit.mirEncodeGeneric(tag, inst), - // Pseudo-instructions + .call, + .jmp, + => try emit.mirCallJmp(inst), + + .movsx => try emit.mirMovsx(inst), .cmovcc => try emit.mirCmovcc(inst), .setcc => try emit.mirSetcc(inst), .jcc => try emit.mirJcc(inst), @@ -209,7 +212,7 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, }; }, - else => unreachable, + else => unreachable, // TODO } return emit.encode(mnemonic, .{ @@ -220,6 +223,28 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } +fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + const data = emit.mir.instructions.items(.data)[inst]; + + var op1: Instruction.Operand = .none; + var op2: Instruction.Operand = .none; + switch (ops) { + .rr => { + op1 = .{ .reg = data.rr.r1 }; + op2 = .{ .reg = data.rr.r2 }; + }, + else => unreachable, // TODO + } + + const mnemonic: Instruction.Mnemonic = if (op1.bitSize() == 64 and op2.bitSize() == 32) .movsxd else .movsx; + + return emit.encode(mnemonic, .{ + .op1 = op1, + .op2 = op2, + }); +} + fn mnemonicFromConditionCode(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { inline for (@typeInfo(bits.Condition).Enum.fields) |field| { if (mem.eql(u8, field.name, @tagName(cc))) @@ -277,6 +302,86 @@ fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } +fn mirCallJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const mnemonic: Instruction.Mnemonic = switch (tag) { + .call => .call, + .jmp => .jmp, + else => unreachable, + }; + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .inst => { + const target = emit.mir.instructions.items(.data)[inst].inst; + const source = emit.code.items.len; + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + try emit.relocs.append(emit.bin_file.allocator, .{ + .source = source, + .target = target, + .offset = emit.code.items.len - 4, + .length = 5, + }); + }, + .r => { + const reg = emit.mir.instructions.items(.data)[inst].r; + try emit.encode(mnemonic, .{ + .op1 = .{ .reg = reg }, + }); + }, + .imm_s => { + const imm = emit.mir.instructions.items(.data)[inst].imm_s; + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = Immediate.s(imm) }, + }); + }, + else => unreachable, // TODO + } +} + +// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .call_extern); +// const relocation = emit.mir.instructions.items(.data)[inst].relocation; + +// const offset = blk: { +// // callq +// try emit.encode(.call, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// break :blk @intCast(u32, emit.code.items.len) - 4; +// }; + +// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { +// // Add relocation to the decl. +// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = macho_file.getGlobalByIndex(relocation.sym_index); +// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ +// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { +// // Add relocation to the decl. +// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = coff_file.getGlobalByIndex(relocation.sym_index); +// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ +// .type = .direct, +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else { +// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); +// } +// } + fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; From 32708dd6e2f16b4b688b2deed22253ea36233c91 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 20:32:07 +0100 Subject: [PATCH 091/294] x86_64: add RM and MR helpers to codegen --- src/arch/x86_64/CodeGen.zig | 419 ++++++++++++++++-------------------- src/arch/x86_64/Emit.zig | 86 +++++--- src/arch/x86_64/Mir.zig | 147 ++++++++++++- src/arch/x86_64/bits.zig | 11 + 4 files changed, 397 insertions(+), 266 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f5ee4d99eb..4441e63aba 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -491,6 +491,72 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme }); } +fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => .m_sib, + .rip => .m_rip, + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .m_sib => .{ .payload = try self.addExtra(Mir.MemorySib.encode(m)) }, + .m_rip => .{ .payload = try self.addExtra(Mir.MemoryRip.encode(m)) }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + +fn asmRegisterMemory(self: *Self, tag: Mir.Inst.Tag, reg: Register, m: Memory) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => .rm_sib, + .rip => .rm_rip, + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .rm_sib => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemorySib.encode(m)), + } }, + .rm_rip => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemoryRip.encode(m)), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + +fn asmMemoryRegister(self: *Self, tag: Mir.Inst.Tag, m: Memory, reg: Register) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => .mr_sib, + .rip => .mr_rip, + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .mr_sib => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemorySib.encode(m)), + } }, + .mr_rip => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemoryRip.encode(m)), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn gen(self: *Self) InnerError!void { const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { @@ -1741,23 +1807,10 @@ fn genIntMulDivOpMir( switch (factor) { .register => |reg| try self.asmRegister(tag, reg), - .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg2 = .rbp, - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // 8 => 0b11, - // else => unreachable, - // }, - // }), - // .data = .{ .disp = -off }, - // }); - }, + .stack_offset => |off| try self.asmMemory(tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + })), else => unreachable, } } @@ -2222,19 +2275,10 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue { const addr_reg = try self.register_manager.allocReg(null, gp); switch (slice_mcv) { - .stack_offset => |off| { - _ = off; - // mov reg, [rbp - 8] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -@intCast(i32, off) }, - // }); - }, + .stack_offset => |off| try self.asmRegisterMemory(.mov, addr_reg.to64(), Memory.sib(.qword, .{ + .base = .rbp, + .disp = -off, + })), else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}), } // TODO we could allocate register here, but need to expect addr register and potentially @@ -2309,27 +2353,16 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { array_ty.abiAlignment(self.target.*), )); try self.genSetStack(array_ty, off, array, .{}); - // lea reg, [rbp] - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{ + .base = .rbp, + .disp = -off, + })); }, .stack_offset => |off| { - _ = off; - // lea reg, [rbp] - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{ + .base = .rbp, + .disp = -off, + })); }, .memory, .linker_load => { try self.loadMemPtrIntoRegister(addr_reg, Type.usize, array); @@ -2366,7 +2399,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock); const elem_ty = ptr_ty.elemType2(); - const elem_abi_size = elem_ty.abiSize(self.target.*); + const elem_abi_size = @intCast(u32, elem_ty.abiSize(self.target.*)); const index_ty = self.air.typeOf(bin_op.rhs); const index = try self.resolveInst(bin_op.rhs); const index_lock: ?RegisterLock = switch (index) { @@ -2386,16 +2419,14 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { if (elem_abi_size > 8) { return self.fail("TODO copy value with size {} from pointer", .{elem_abi_size}); } else { - // mov dst_mcv, [dst_mcv] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)), - // .reg2 = dst_mcv.register, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(dst_mcv.register, elem_abi_size), + Memory.sib(Memory.PtrSize.fromSize(elem_abi_size), .{ + .base = dst_mcv.register, + .disp = 0, + }), + ); break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) }; } }; @@ -2622,7 +2653,7 @@ fn reuseOperand( fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void { const elem_ty = ptr_ty.elemType(); - const abi_size = elem_ty.abiSize(self.target.*); + const abi_size = @intCast(u32, elem_ty.abiSize(self.target.*)); switch (ptr) { .none => unreachable, .undef => unreachable, @@ -2649,17 +2680,11 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .undef => unreachable, .eflags => unreachable, .register => |dst_reg| { - _ = dst_reg; - // mov dst_reg, [reg] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), - // .reg2 = reg, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(dst_reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg, .disp = 0 }), + ); }, .stack_offset => |off| { if (abi_size <= 8) { @@ -2874,17 +2899,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.loadMemPtrIntoRegister(addr_reg, ptr_ty, ptr); - // to get the actual address of the value we want to modify we have to go through the GOT - // mov reg, [reg] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = addr_reg.to64(), - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + // To get the actual address of the value we want to modify we have to go through the GOT + try self.asmRegisterMemory(.mov, addr_reg.to64(), Memory.sib(.qword, .{ + .base = addr_reg.to64(), + .disp = 0, + })); const new_ptr = MCValue{ .register = addr_reg.to64() }; @@ -2936,16 +2955,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type defer self.register_manager.unlockReg(tmp_reg_lock); try self.loadMemPtrIntoRegister(tmp_reg, value_ty, value); + try self.asmRegisterMemory(.mov, tmp_reg, Memory.sib(.qword, .{ + .base = tmp_reg, + .disp = 0, + })); - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg, - // .reg2 = tmp_reg, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } @@ -3603,15 +3617,11 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu if (off > math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + mir_tag, + registerAlias(dst_reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, } }, @@ -3629,16 +3639,10 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .dead, .unreach => unreachable, .register_overflow => unreachable, .register => |src_reg| { - _ = src_reg; - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .reg2 = registerAlias(src_reg, abi_size), - // .flags = 0b10, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmMemoryRegister(mir_tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), registerAlias(src_reg, abi_size)); }, .immediate => |imm| { _ = imm; @@ -3738,16 +3742,11 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M } }, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + .imul, + registerAlias(dst_reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, .memory => { return self.fail("TODO implement x86 multiply source memory", .{}); @@ -3770,17 +3769,11 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .register => |src_reg| { // copy dst to a register const dst_reg = try self.copyToTmpRegister(dst_ty, dst_mcv); - _ = src_reg; - // multiply into dst_reg - // register, register - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .imul, + registerAlias(dst_reg, abi_size), + registerAlias(src_reg, abi_size), + ); // copy dst_reg back out return self.genSetStack(dst_ty, off, .{ .register = dst_reg }, .{}); }, @@ -4006,11 +3999,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (info.stack_byte_count > 0) { // Adjust the stack - // _ = try self.addInst(.{ - // .tag = .sub, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - // .data = .{ .imm = info.stack_byte_count }, - // }); + try self.asmRegisterImmediate(.sub, .rsp, Immediate.u(info.stack_byte_count)); } // Due to incremental compilation, how function calls are generated depends @@ -4161,7 +4150,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. const jmp_reloc = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = undefined }, }); @@ -4197,7 +4186,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. const jmp_reloc = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = undefined }, }); @@ -4738,7 +4727,7 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const jmp_target = @intCast(u32, self.mir_instructions.len); try self.genBody(body); _ = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = jmp_target }, }); @@ -5035,7 +5024,7 @@ fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { .jcc => { self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; }, - .jmp => { + .jmp_reloc => { self.mir_instructions.items(.data)[reloc].inst = next_inst; }, else => unreachable, @@ -5078,7 +5067,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined const jmp_reloc = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = undefined }, }); @@ -5247,7 +5236,7 @@ fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void { } fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerError!void { - const abi_size = ty.abiSize(self.target.*); + const abi_size = @intCast(u32, ty.abiSize(self.target.*)); switch (mcv) { .dead => unreachable, .unreach, .none => return, @@ -5325,36 +5314,25 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .{ty.fmtDebug()}, ), }; - _ = tag; - _ = reg; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = switch (ty.tag()) { - // .f32 => .esp, - // .f64 => .rsp, - // else => unreachable, - // }, - // .reg2 = reg.to128(), - // .flags = 0b01, - // }), - // .data = .{ .disp = -stack_offset }, - // }); - return; + // TODO verify this + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmMemoryRegister(tag, Memory.sib(ptr_size, .{ + .base = .rsp, + .disp = -stack_offset, + }), reg.to128()); } return self.fail("TODO genSetStackArg for register with no intrinsics", .{}); }, else => { - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rsp, - // .reg2 = registerAlias(reg, @intCast(u32, abi_size)), - // .flags = 0b10, - // }), - // .data = .{ .disp = -stack_offset }, - // }); + try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rsp, + .disp = -stack_offset, + }), registerAlias(reg, abi_size)); }, } }, @@ -5507,25 +5485,23 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = switch (ty.tag()) { - // .f32 => base_reg.to32(), - // .f64 => base_reg.to64(), - // else => unreachable, - // }, - // .reg2 = reg.to128(), - // .flags = 0b01, - // }), - // .data = .{ .disp = -stack_offset }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail( + "TODO genSetStack for register for type {}", + .{ty.fmtDebug()}, + ), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmMemoryRegister(tag, Memory.sib(ptr_size, .{ + .base = base_reg.to64(), + .disp = -stack_offset, + }), reg.to128()); } return self.fail("TODO genSetStack for register for type float with no intrinsics", .{}); @@ -5590,16 +5566,10 @@ fn genInlineMemcpyRegisterRegister( var remainder = abi_size; while (remainder > 0) { const nearest_power_of_two = @as(u6, 1) << math.log2_int(u3, @intCast(u3, remainder)); - - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg, - // .reg2 = registerAlias(tmp_reg, nearest_power_of_two), - // .flags = 0b10, - // }), - // .data = .{ .disp = -next_offset }, - // }); + try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(nearest_power_of_two), .{ + .base = dst_reg, + .disp = -next_offset, + }), registerAlias(tmp_reg, nearest_power_of_two)); if (nearest_power_of_two > 1) { try self.genShiftBinOpMir(.shr, ty, tmp_reg, .{ @@ -5611,15 +5581,10 @@ fn genInlineMemcpyRegisterRegister( next_offset -= nearest_power_of_two; } } else { - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg, - // .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), - // .flags = 0b10, - // }), - // .data = .{ .disp = -offset }, - // }); + try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = dst_reg, + .disp = -offset, + }), registerAlias(src_reg, abi_size)); } } @@ -5660,15 +5625,10 @@ fn genInlineMemcpy( try self.loadMemPtrIntoRegister(dst_addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_addr_reg.to64(), - // .reg2 = opts.dest_stack_base orelse .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, dst_addr_reg.to64(), Memory.sib(.qword, .{ + .base = opts.dest_stack_base orelse .rbp, + .disp = -off, + })); }, .register => |reg| { try self.asmRegisterRegister( @@ -5754,7 +5714,7 @@ fn genInlineMemcpy( try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); _ = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = loop_start }, }); @@ -5857,7 +5817,7 @@ fn genInlineMemset( try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1)); _ = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = loop_start }, }); @@ -6045,19 +6005,20 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // .data = .{ .disp = @intCast(i32, x) }, // }); } else { - // If this is RAX, we can use a direct load. - // Otherwise, we need to load the address, then indirectly load the value. if (reg.to64() == .rax) { - // movabs rax, ds:moffs64 - // const payload = try self.addExtra(Mir.Imm64.encode(x)); - // _ = try self.addInst(.{ - // .tag = .movabs, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, // imm64 will become moffs64 - // }), - // .data = .{ .payload = payload }, - // }); + // If this is RAX, we can use a direct load. + // Otherwise, we need to load the address, then indirectly load the value. + var moffs: Mir.MemoryMoffs = .{ + .seg = @enumToInt(Register.ds), + .msb = undefined, + .lsb = undefined, + }; + moffs.encodeOffset(x); + _ = try self.addInst(.{ + .tag = .mov_moffs, + .ops = .rax_moffs, + .data = .{ .payload = try self.addExtra(moffs) }, + }); } else { // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. try self.genSetReg(ty, reg, MCValue{ .immediate = x }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 4eff2d11a0..fc1e345a5a 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -73,6 +73,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .adc, .add, .@"and", + .call, .cbw, .cwde, .cdqe, @@ -86,6 +87,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .idiv, .imul, .int3, + .jmp, + .lea, .mov, .movzx, .mul, @@ -115,9 +118,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .ucomisd, => try emit.mirEncodeGeneric(tag, inst), - .call, - .jmp, - => try emit.mirCallJmp(inst), + .jmp_reloc => try emit.mirJmpReloc(inst), + + .mov_moffs => try emit.mirMovMoffs(inst), .movsx => try emit.mirMovsx(inst), .cmovcc => try emit.mirCmovcc(inst), @@ -130,8 +133,6 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push_regs => try emit.mirPushPopRegisterList(.push, inst), .pop_regs => try emit.mirPushPopRegisterList(.pop, inst), - - else => return emit.fail("Implement MIR->Emit lowering for x86_64 for pseudo-inst: {}", .{tag}), } } @@ -212,6 +213,34 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, }; }, + .m_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data; + operands[0] = .{ .mem = Mir.MemorySib.decode(msib) }; + }, + .m_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data; + operands[0] = .{ .mem = Mir.MemoryRip.decode(mrip) }; + }, + .rm_sib, .mr_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; + const op1 = .{ .reg = data.rx.r1 }; + const op2 = .{ .mem = Mir.MemorySib.decode(msib) }; + switch (ops) { + .rm_sib => operands[0..2].* = .{ op1, op2 }, + .mr_sib => operands[0..2].* = .{ op2, op1 }, + else => unreachable, + } + }, + .rm_rip, .mr_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data; + const op1 = .{ .reg = data.rx.r1 }; + const op2 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + switch (ops) { + .rm_rip => operands[0..2].* = .{ op1, op2 }, + .mr_rip => operands[0..2].* = .{ op2, op1 }, + else => unreachable, + } + }, else => unreachable, // TODO } @@ -223,6 +252,29 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } +fn mirMovMoffs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + const payload = emit.mir.instructions.items(.data)[inst].payload; + const moffs = emit.mir.extraData(Mir.MemoryMoffs, payload).data; + const seg = @intToEnum(Register, moffs.seg); + const offset = moffs.decodeOffset(); + switch (ops) { + .rax_moffs => { + try emit.encode(.mov, .{ + .op1 = .{ .reg = .rax }, + .op2 = .{ .mem = Memory.moffs(seg, offset) }, + }); + }, + .moffs_rax => { + try emit.encode(.mov, .{ + .op1 = .{ .mem = Memory.moffs(seg, offset) }, + .op2 = .{ .reg = .rax }, + }); + }, + else => unreachable, + } +} + fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; const data = emit.mir.instructions.items(.data)[inst]; @@ -302,19 +354,13 @@ fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirCallJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - const mnemonic: Instruction.Mnemonic = switch (tag) { - .call => .call, - .jmp => .jmp, - else => unreachable, - }; +fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; switch (ops) { .inst => { const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; - try emit.encode(mnemonic, .{ + try emit.encode(.jmp, .{ .op1 = .{ .imm = Immediate.s(0) }, }); try emit.relocs.append(emit.bin_file.allocator, .{ @@ -324,19 +370,7 @@ fn mirCallJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .length = 5, }); }, - .r => { - const reg = emit.mir.instructions.items(.data)[inst].r; - try emit.encode(mnemonic, .{ - .op1 = .{ .reg = reg }, - }); - }, - .imm_s => { - const imm = emit.mir.instructions.items(.data)[inst].imm_s; - try emit.encode(mnemonic, .{ - .op1 = .{ .imm = Immediate.s(imm) }, - }); - }, - else => unreachable, // TODO + else => unreachable, } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 02bc70614d..40fd1953de 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -17,6 +17,7 @@ const encoder = @import("encoder.zig"); const Air = @import("../../Air.zig"); const CodeGen = @import("CodeGen.zig"); const IntegerBitSet = std.bit_set.IntegerBitSet; +const Memory = bits.Memory; const Register = bits.Register; instructions: std.MultiArrayList(Inst).Slice, @@ -135,6 +136,12 @@ pub const Inst = struct { /// Conditional move cmovcc, + /// Mov absolute to/from memory wrt segment register to/from rax + mov_moffs, + + /// Jump with relocation to another local MIR instruction + jmp_reloc, + /// End of prologue dbg_prologue_end, /// Start of epilogue @@ -186,24 +193,48 @@ pub const Inst = struct { /// Relative displacement operand. /// Uses `rel` payload. rel, - /// Register, memory operands. + /// Register, memory (SIB) operands. /// Uses `rx` payload. - rm, + rm_sib, + /// Register, memory (RIP) operands. + /// Uses `rx` payload. + rm_rip, /// Register, memory, immediate (unsigned) operands /// Uses `rx` payload. rmi_u, /// Register, memory, immediate (sign-extended) operands /// Uses `rx` payload. rmi_s, - /// Memory, immediate (unsigned) operands. - /// Uses `payload` payload. - mi_u, - /// Memory, immediate (sign-extend) operands. - /// Uses `payload` payload. - mi_s, - /// Memory, register operands. - /// Uses `payload` payload. - mr, + /// Single memory (SIB) operand. + /// Uses `payload` with extra data of type `MemorySib`. + m_sib, + /// Single memory (RIP) operand. + /// Uses `payload` with extra data of type `MemoryRip`. + m_rip, + /// Memory (SIB), immediate (unsigned) operands. + /// Uses `xi_u` payload with extra data of type `MemorySib`. + mi_u_sib, + /// Memory (RIP), immediate (unsigned) operands. + /// Uses `xi_u` payload with extra data of type `MemoryRip`. + mi_u_rip, + /// Memory (SIB), immediate (sign-extend) operands. + /// Uses `xi_s` payload with extra data of type `MemorySib`. + mi_s_sib, + /// Memory (RIP), immediate (sign-extend) operands. + /// Uses `xi_s` payload with extra data of type `MemoryRip`. + mi_s_rip, + /// Memory (SIB), register operands. + /// Uses `rx` payload with extra data of type `MemorySib`. + mr_sib, + /// Memory (RIP), register operands. + /// Uses `rx` payload with extra data of type `MemoryRip`. + mr_rip, + /// Rax, Memory moffs. + /// Uses `payload` with extra data of type `MemoryMoffs`. + rax_moffs, + /// Memory moffs, rax. + /// Uses `payload` with extra data of type `MemoryMoffs`. + moffs_rax, /// Lea into register with linker relocation. /// Uses `payload` payload with data of type `LeaRegisterReloc`. lea_r_reloc, @@ -274,6 +305,16 @@ pub const Inst = struct { r1: Register, payload: u32, }, + /// Custom payload followed by an unsigned immediate. + xi_u: struct { + payload: u32, + imm: u32, + }, + /// Custom payload followed by a signed immediate. + xi_s: struct { + payload: u32, + imm: i32, + }, /// Relocation for the linker where: /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target @@ -378,6 +419,90 @@ pub const Imm64 = struct { } }; +// TODO this can be further compacted using packed struct +pub const MemorySib = struct { + /// Size of the pointer. + ptr_size: u32, + /// Base register. -1 means null, or no base register. + base: i32, + /// Scale for index register. -1 means null, or no scale. + /// This has to be in sync with `index` field. + scale: i32, + /// Index register. -1 means null, or no index register. + /// This has to be in sync with `scale` field. + index: i32, + /// Displacement value. + disp: i32, + + pub fn encode(mem: Memory) MemorySib { + const sib = mem.sib; + return .{ + .ptr_size = @enumToInt(sib.ptr_size), + .base = if (sib.base) |r| @enumToInt(r) else -1, + .scale = if (sib.scale_index) |si| si.scale else -1, + .index = if (sib.scale_index) |si| @enumToInt(si.index) else -1, + .disp = sib.disp, + }; + } + + pub fn decode(msib: MemorySib) Memory { + const base: ?Register = if (msib.base == -1) null else @intToEnum(Register, msib.base); + const scale_index: ?Memory.ScaleIndex = if (msib.index == -1) null else .{ + .scale = @intCast(u4, msib.scale), + .index = @intToEnum(Register, msib.index), + }; + const mem: Memory = .{ .sib = .{ + .ptr_size = @intToEnum(Memory.PtrSize, msib.ptr_size), + .base = base, + .scale_index = scale_index, + .disp = msib.disp, + } }; + return mem; + } +}; + +pub const MemoryRip = struct { + /// Size of the pointer. + ptr_size: u32, + /// Displacement value. + disp: i32, + + pub fn encode(mem: Memory) MemoryRip { + return .{ + .ptr_size = @enumToInt(mem.rip.ptr_size), + .disp = mem.rip.disp, + }; + } + + pub fn decode(mrip: MemoryRip) Memory { + return .{ .rip = .{ + .ptr_size = @intToEnum(Memory.PtrSize, mrip.ptr_size), + .disp = mrip.disp, + } }; + } +}; + +pub const MemoryMoffs = struct { + /// Segment register. + seg: u32, + /// Absolute offset wrt to the segment register split between MSB and LSB parts much like + /// `Imm64` payload. + msb: u32, + lsb: u32, + + pub fn encodeOffset(moffs: *MemoryMoffs, v: u64) void { + moffs.msb = @truncate(u32, v >> 32); + moffs.lsb = @truncate(u32, v); + } + + pub fn decodeOffset(moffs: *const MemoryMoffs) u64 { + var res: u64 = 0; + res |= (@intCast(u64, moffs.msb) << 32); + res |= @intCast(u64, moffs.lsb); + return res; + } +}; + pub const DbgLineColumn = struct { line: u32, column: u32, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index ad9a6f7f23..b6ac9ec5a8 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -417,6 +417,17 @@ pub const Memory = union(enum) { qword, tbyte, + pub fn fromSize(size: u32) PtrSize { + return switch (size) { + 1 => .byte, + 2 => .word, + 4 => .dword, + 8 => .qword, + 10 => .tbyte, + else => unreachable, + }; + } + pub fn fromBitSize(bit_size: u64) PtrSize { return switch (bit_size) { 8 => .byte, From 4af8313f362e393f51af1bcefd0b91c3b1ce5611 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 21:47:11 +0100 Subject: [PATCH 092/294] x86_64: plug up all RM/MR references --- src/arch/x86_64/CodeGen.zig | 284 ++++++++++++++---------------------- 1 file changed, 112 insertions(+), 172 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 4441e63aba..f3f425d549 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -5834,14 +5834,11 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + .lea, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, .unreach, .none => return, // Nothing to do. .undef => { @@ -5927,40 +5924,31 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - // }; - - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = switch (ty.tag()) { - // .f32 => base_reg.to32(), - // .f64 => base_reg.to64(), - // else => unreachable, - // }, - // }), - // .data = .{ .disp = 0 }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsx, + else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmRegisterMemory(tag, reg.to128(), Memory.sib(ptr_size, .{ + .base = base_reg.to64(), + .disp = 0, + })); } return self.fail("TODO genSetReg from memory for float with no intrinsics", .{}); }, else => { try self.loadMemPtrIntoRegister(reg, Type.usize, mcv); - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = reg.to64(), - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0 }), + ); }, } }, @@ -5970,40 +5958,34 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - // }; - - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = switch (ty.tag()) { - // .f32 => base_reg.to32(), - // .f64 => base_reg.to64(), - // else => unreachable, - // }, - // }), - // .data = .{ .disp = 0 }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmRegisterMemory(tag, reg.to128(), Memory.sib(ptr_size, .{ + .base = base_reg.to64(), + .disp = 0, + })); } return self.fail("TODO genSetReg from memory for float with no intrinsics", .{}); }, else => { if (x <= math.maxInt(i32)) { - // mov reg, [ds:imm32] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = 0b01, - // }), - // .data = .{ .disp = @intCast(i32, x) }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .ds, + .disp = @intCast(i32, x), + }), + ); } else { if (reg.to64() == .rax) { // If this is RAX, we can use a direct load. @@ -6022,17 +6004,11 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } else { // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. try self.genSetReg(ty, reg, MCValue{ .immediate = x }); - - // mov reg, [reg + 0x0] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = reg.to64(), - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0 }), + ); } } }, @@ -6046,81 +6022,59 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Int => switch (ty.intInfo(self.target.*).signedness) { .signed => { if (abi_size <= 4) { - const flags: u2 = switch (abi_size) { - 1 => 0b01, - 2 => 0b10, - 4 => 0b11, - else => unreachable, - }; - _ = flags; - // _ = try self.addInst(.{ - // .tag = .mov_sign_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = .rbp, - // .flags = flags, - // }), - // .data = .{ .disp = -off }, - // }); - return; + return self.asmRegisterMemory( + .movsx, + reg.to64(), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), + ); } }, .unsigned => { if (abi_size <= 2) { - const flags: u2 = switch (abi_size) { - 1 => 0b01, - 2 => 0b10, - else => unreachable, - }; - _ = flags; - // _ = try self.addInst(.{ - // .tag = .mov_zero_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = .rbp, - // .flags = flags, - // }), - // .data = .{ .disp = -off }, - // }); - return; + return self.asmRegisterMemory( + .movzx, + reg.to64(), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), + ); } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = switch (ty.tag()) { - // .f32 => .ebp, - // .f64 => .rbp, - // else => unreachable, - // }, - // }), - // .data = .{ .disp = -off }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail( + "TODO genSetReg from stack offset for {}", + .{ty.fmtDebug()}, + ), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmRegisterMemory(tag, reg.to128(), Memory.sib(ptr_size, .{ + .base = .rbp, + .disp = -off, + })); } return self.fail("TODO genSetReg from stack offset for float with no intrinsics", .{}); }, else => {}, } - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, } } @@ -6184,7 +6138,16 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { const src_ty = self.air.typeOf(ty_op.operand); const dst_ty = self.air.typeOfIndex(inst); const operand = try self.resolveInst(ty_op.operand); - _ = dst_ty; + const src_abi_size = @intCast(u32, src_ty.abiSize(self.target.*)); + const dst_abi_size = @intCast(u32, dst_ty.abiSize(self.target.*)); + + switch (src_abi_size) { + 4, 8 => {}, + else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), + } + if (dst_abi_size > 8) { + return self.fail("TODO convert float with abiSize={}", .{dst_abi_size}); + } // move float src to ST(0) const stack_offset = switch (operand) { @@ -6192,42 +6155,24 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { else => blk: { const offset = @intCast(i32, try self.allocMem( inst, - @intCast(u32, src_ty.abiSize(self.target.*)), + src_abi_size, src_ty.abiAlignment(self.target.*), )); try self.genSetStack(src_ty, offset, operand, .{}); break :blk offset; }, }; - _ = stack_offset; - // _ = try self.addInst(.{ - // .tag = .fld, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .flags = switch (src_ty.abiSize(self.target.*)) { - // 4 => 0b01, - // 8 => 0b10, - // else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), - // }, - // }), - // .data = .{ .disp = -stack_offset }, - // }); + try self.asmMemory(.fld, Memory.sib(Memory.PtrSize.fromSize(src_abi_size), .{ + .base = .rbp, + .disp = -stack_offset, + })); // convert const stack_dst = try self.allocRegOrMem(inst, false); - // _ = try self.addInst(.{ - // .tag = .fisttp, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .flags = switch (dst_ty.abiSize(self.target.*)) { - // 1...2 => 0b00, - // 3...4 => 0b01, - // 5...8 => 0b10, - // else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), - // }, - // }), - // .data = .{ .disp = -stack_dst.stack_offset }, - // }); + try self.asmMemory(.fisttp, Memory.sib(Memory.PtrSize.fromSize(dst_abi_size), .{ + .base = .rbp, + .disp = -stack_dst.stack_offset, + })); return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none }); } @@ -6318,15 +6263,10 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { .linker_load, .memory => { const reg = try self.register_manager.allocReg(null, gp); try self.loadMemPtrIntoRegister(reg, src_ty, src_ptr); - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg, - // .reg2 = reg, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory(.mov, reg, Memory.sib(.qword, .{ + .base = reg, + .disp = 0, + })); break :blk MCValue{ .register = reg }; }, else => break :blk src_ptr, From 022b308d6a0a3d3cf178ddc6e887f24369f69deb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 22:15:58 +0100 Subject: [PATCH 093/294] x86_64: start converting MI references --- src/arch/x86_64/CodeGen.zig | 251 +++++++++++------------------------- src/arch/x86_64/Emit.zig | 28 ++++ 2 files changed, 106 insertions(+), 173 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f3f425d549..e9e7e1875b 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -509,6 +509,29 @@ fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { }); } +fn asmMemoryImmediate(self: *Self, tag: Mir.Inst.Tag, m: Memory, imm: Immediate) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => if (imm == .signed) .mi_s_sib else .mi_u_sib, + .rip => if (imm == .signed) .mi_s_rip else .mi_u_rip, + else => unreachable, + }; + const payload: u32 = switch (ops) { + .mi_s_sib, .mi_u_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .mi_s_rip, .mi_u_rip => try self.addExtra(Mir.MemoryRip.encode(m)), + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .mi_s_sib, .mi_s_rip => .{ .xi_s = .{ .imm = imm.signed, .payload = payload } }, + .mi_u_sib, .mi_u_rip => .{ .xi_u = .{ .imm = @intCast(u32, imm.unsigned), .payload = payload } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn asmRegisterMemory(self: *Self, tag: Mir.Inst.Tag, reg: Register, m: Memory) !void { const ops: Mir.Inst.Ops = switch (m) { .sib => .rm_sib, @@ -2776,7 +2799,7 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue } fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void { - const abi_size = value_ty.abiSize(self.target.*); + const abi_size = @intCast(u32, value_ty.abiSize(self.target.*)); switch (ptr) { .none => unreachable, .undef => unreachable, @@ -2807,28 +2830,12 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.genSetReg(value_ty, reg, value); }, .immediate => |imm| { - _ = imm; switch (abi_size) { 1, 2, 4 => { - // TODO this is wasteful! - // introduce new MIR tag specifically for mov [reg + 0], imm - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = 0, - // .operand = @truncate(u32, imm), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // else => unreachable, - // }, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = reg.to64(), + .disp = 0, + }), Immediate.u(@truncate(u32, imm))); }, 8 => { // TODO: optimization: if the imm is only using the lower @@ -2913,19 +2920,8 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = 0, - // // TODO check if this logic is correct - // .operand = @intCast(u32, imm), - // }); - const flags: u2 = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - 8 => 0b11, - else => unreachable, - }; - if (flags == 0b11) { + if (abi_size == 8) { + // TODO const top_bits: u32 = @intCast(u32, imm >> 32); const can_extend = if (value_ty.isUnsignedInt()) (top_bits == 0) and (imm & 0x8000_0000) == 0 @@ -2936,14 +2932,10 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO imm64 would get incorrectly sign extended", .{}); } } - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .flags = flags, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = addr_reg.to64(), + .disp = 0, + }), Immediate.u(@intCast(u32, imm))); }, .register => { return self.store(new_ptr, value, ptr_ty, value_ty); @@ -3595,12 +3587,12 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu ), }, .immediate => |imm| { - _ = imm; - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - // .data = .{ .imm = @intCast(u32, imm) }, - // }); + // TODO + try self.asmRegisterImmediate( + mir_tag, + registerAlias(dst_reg, abi_size), + Immediate.u(@intCast(u32, imm)), + ); }, .memory, .linker_load, @@ -3645,36 +3637,11 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu }), registerAlias(src_reg, abi_size)); }, .immediate => |imm| { - _ = imm; - // const tag: Mir.Inst.Tag = switch (mir_tag) { - // .add => .add_mem_imm, - // .@"or" => .or_mem_imm, - // .@"and" => .and_mem_imm, - // .sub => .sub_mem_imm, - // .xor => .xor_mem_imm, - // .cmp => .cmp_mem_imm, - // else => unreachable, - // }; - const flags: u2 = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - 8 => 0b11, - else => unreachable, - }; - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -off, - // .operand = @intCast(u32, imm), - // }); - _ = flags; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .flags = flags, - // }), - // .data = .{ .payload = payload }, - // }); + // TODO + try self.asmMemoryImmediate(mir_tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), Immediate.u(@intCast(u32, imm))); }, .memory, .stack_offset, @@ -5258,33 +5225,18 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE return self.genSetStackArg(ty, stack_offset, .{ .register = reg }); }, .immediate => |imm| { - _ = imm; switch (abi_size) { - // TODO - // 1, 2, 4 => { - // // We have a positive stack offset value but we want a twos complement negative - // // offset from rbp, which is at the top of the stack frame. - // // mov [rbp+offset], immediate - // const flags: u2 = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // else => unreachable, - // }; - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @intCast(u32, imm), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rsp, - // .flags = flags, - // }), - // .data = .{ .payload = payload }, - // }); - // }, - 1, 2, 4, 8 => { + 1, 2, 4 => { + // TODO + // We have a positive stack offset value but we want a twos complement negative + // offset from rbp, which is at the top of the stack frame. + // mov [rbp+offset], immediate + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rsp, + .disp = -stack_offset, + }), Immediate.u(@intCast(u32, imm))); + }, + 8 => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); }, @@ -5355,7 +5307,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE } fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: InlineMemcpyOpts) InnerError!void { - const abi_size = ty.abiSize(self.target.*); + const abi_size = @intCast(u32, ty.abiSize(self.target.*)); switch (mcv) { .dead => unreachable, .unreach, .none => return, // Nothing to do. @@ -5400,75 +5352,33 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl return self.genSetStack(ty, stack_offset, .{ .register = reg }, opts); }, .immediate => |x_big| { - _ = x_big; const base_reg = opts.dest_stack_base orelse .rbp; - _ = base_reg; + // TODO switch (abi_size) { 0 => { assert(ty.isError()); - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @truncate(u32, x_big), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = 0b00, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(.byte, .{ + .base = base_reg, + .disp = -stack_offset, + }), Immediate.u(@truncate(u32, x_big))); }, 1, 2, 4 => { - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @truncate(u32, x_big), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // else => unreachable, - // }, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = base_reg, + .disp = -stack_offset, + }), Immediate.u(@truncate(u32, x_big))); }, 8 => { // 64 bit write to memory would take two mov's anyways so we // insted just use two 32 bit writes to avoid register allocation - { - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset + 4, - // .operand = @truncate(u32, x_big >> 32), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = 0b10, - // }), - // .data = .{ .payload = payload }, - // }); - } - { - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @truncate(u32, x_big), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = 0b10, - // }), - // .data = .{ .payload = payload }, - // }); - } + try self.asmMemoryImmediate(.mov, Memory.sib(.dword, .{ + .base = base_reg, + .disp = -stack_offset + 4, + }), Immediate.u(@truncate(u32, x_big >> 32))); + try self.asmMemoryImmediate(.mov, Memory.sib(.dword, .{ + .base = base_reg, + .disp = -stack_offset, + }), Immediate.u(@truncate(u32, x_big))); }, else => { return self.fail("TODO implement set abi_size=large stack variable with immediate", .{}); @@ -5647,15 +5557,10 @@ fn genInlineMemcpy( try self.loadMemPtrIntoRegister(src_addr_reg, Type.usize, src_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = src_addr_reg.to64(), - // .reg2 = opts.source_stack_base orelse .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, src_addr_reg.to64(), Memory.sib(.qword, .{ + .base = opts.source_stack_base orelse .rbp, + .disp = -off, + })); }, .register => |reg| { try self.asmRegisterRegister( diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index fc1e345a5a..218e1c6f55 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -221,6 +221,34 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data; operands[0] = .{ .mem = Mir.MemoryRip.decode(mrip) }; }, + .mi_u_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.xi_u.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemorySib.decode(msib) }, + .{ .imm = Immediate.u(data.xi_u.imm) }, + }; + }, + .mi_s_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.xi_s.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemorySib.decode(msib) }, + .{ .imm = Immediate.s(data.xi_s.imm) }, + }; + }, + .mi_u_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_u.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemoryRip.decode(mrip) }, + .{ .imm = Immediate.u(data.xi_u.imm) }, + }; + }, + .mi_s_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_s.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemoryRip.decode(mrip) }, + .{ .imm = Immediate.s(data.xi_s.imm) }, + }; + }, .rm_sib, .mr_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; const op1 = .{ .reg = data.rx.r1 }; From d0e72125396c391172758d28c21a9b901dcecc68 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 23:56:55 +0100 Subject: [PATCH 094/294] x86_64: finish rolling out all MIR assembly helpers --- src/arch/x86_64/CodeGen.zig | 172 ++++++++++--------- src/arch/x86_64/Emit.zig | 328 ++++++++++++------------------------ src/arch/x86_64/Mir.zig | 46 +++-- 3 files changed, 225 insertions(+), 321 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index e9e7e1875b..9056b64dce 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -491,6 +491,37 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme }); } +fn asmRegisterRegisterImmediate( + self: *Self, + tag: Mir.Inst.Tag, + reg1: Register, + reg2: Register, + imm: Immediate, +) !void { + const ops: Mir.Inst.Ops = switch (imm) { + .signed => .rri_s, + .unsigned => .rri_u, + }; + const data: Mir.Inst.Data = switch (ops) { + .rri_s => .{ .rri_s = .{ + .r1 = reg1, + .r2 = reg2, + .imm = imm.signed, + } }, + .rri_u => .{ .rri_u = .{ + .r1 = reg1, + .r2 = reg2, + .imm = @intCast(u32, imm.unsigned), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { const ops: Mir.Inst.Ops = switch (m) { .sib => .m_sib, @@ -2767,27 +2798,20 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue const atom = try coff_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); break :blk coff_file.getAtom(atom).getSymbolIndex().?; } else unreachable; - const flags: u2 = switch (load_struct.type) { - .got => 0b00, - .direct => 0b01, - .import => 0b10, + const ops: Mir.Inst.Ops = switch (load_struct.type) { + .got => .got_reloc, + .direct => .direct_reloc, + .import => .import_reloc, }; - _ = abi_size; - _ = atom_index; - _ = flags; - // _ = try self.addInst(.{ - // .tag = .lea_pic, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = flags, - // }), - // .data = .{ - // .relocation = .{ - // .atom_index = atom_index, - // .sym_index = load_struct.sym_index, - // }, - // }, - // }); + _ = try self.addInst(.{ + .tag = .lea_linker, + .ops = ops, + .data = .{ .payload = try self.addExtra(Mir.LeaRegisterReloc{ + .reg = @enumToInt(registerAlias(reg, abi_size)), + .atom_index = atom_index, + .sym_index = load_struct.sym_index, + }) }, + }); }, .memory => |addr| { // TODO: in case the address fits in an imm32 we can use [ds:imm32] @@ -3690,18 +3714,15 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M registerAlias(src_reg, abi_size), ), .immediate => |imm| { - // TODO take into account the type's ABI size when selecting the register alias - // register, immediate if (math.minInt(i32) <= imm and imm <= math.maxInt(i32)) { - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg.to32(), - // .reg2 = dst_reg.to32(), - // .flags = 0b10, - // }), - // .data = .{ .imm = @intCast(u32, imm) }, - // }); + // TODO take into account the type's ABI size when selecting the register alias + // register, immediate + try self.asmRegisterRegisterImmediate( + .imul, + dst_reg.to32(), + dst_reg.to32(), + Immediate.u(@intCast(u32, imm)), + ); } else { // TODO verify we don't spill and assign to the same register as dst_mcv const src_reg = try self.copyToTmpRegister(dst_ty, src_mcv); @@ -4034,16 +4055,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; - _ = sym_index; - _ = atom_index; - // _ = try self.addInst(.{ - // .tag = .call_extern, - // .ops = undefined, - // .data = .{ .relocation = .{ - // .atom_index = atom_index, - // .sym_index = sym_index, - // } }, - // }); + _ = try self.addInst(.{ + .tag = .call_extern, + .ops = undefined, + .data = .{ .relocation = .{ + .atom_index = atom_index, + .sym_index = sym_index, + } }, + }); } else { return self.fail("TODO implement calling extern functions", .{}); } @@ -5528,7 +5547,6 @@ fn genInlineMemcpy( const index_reg = regs[2].to64(); const count_reg = regs[3].to64(); const tmp_reg = regs[4].to8(); - _ = tmp_reg; switch (dst_ptr) { .memory, .linker_load => { @@ -5575,7 +5593,6 @@ fn genInlineMemcpy( } try self.genSetReg(Type.usize, count_reg, len); - try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(0)); const loop_start = try self.addInst(.{ @@ -5595,26 +5612,22 @@ fn genInlineMemcpy( } }, }); - // mov tmp, [addr + index_reg] - // _ = try self.addInst(.{ - // .tag = .mov_scale_src, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg.to8(), - // .reg2 = src_addr_reg, - // }), - // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - // }); - - // mov [stack_offset + index_reg], tmp - // _ = try self.addInst(.{ - // .tag = .mov_scale_dst, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_addr_reg, - // .reg2 = tmp_reg.to8(), - // }), - // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - // }); - + try self.asmRegisterMemory(.mov, tmp_reg.to8(), Memory.sib(.byte, .{ + .base = src_addr_reg, + .scale_index = .{ + .scale = 1, + .index = index_reg, + }, + .disp = 0, + })); + try self.asmMemoryRegister(.mov, Memory.sib(.byte, .{ + .base = dst_addr_reg, + .scale_index = .{ + .scale = 1, + .index = index_reg, + }, + .disp = 0, + }), tmp_reg.to8()); try self.asmRegisterImmediate(.add, index_reg, Immediate.u(1)); try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); @@ -5655,15 +5668,10 @@ fn genInlineMemset( try self.loadMemPtrIntoRegister(addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = opts.dest_stack_base orelse .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{ + .base = opts.dest_stack_base orelse .rbp, + .disp = -off, + })); }, .register => |reg| { try self.asmRegisterRegister( @@ -5703,18 +5711,14 @@ fn genInlineMemset( if (x > math.maxInt(i32)) { return self.fail("TODO inline memset for value immediate larger than 32bits", .{}); } - // mov byte ptr [rbp + index_reg + stack_offset], imm - // _ = try self.addInst(.{ - // .tag = .mov_mem_index_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg, - // }), - // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( - // index_reg, - // 0, - // @intCast(u32, x), - // )) }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(.byte, .{ + .base = addr_reg, + .scale_index = .{ + .scale = 1, + .index = index_reg, + }, + .disp = 0, + }), Immediate.u(@intCast(u8, x))); }, else => return self.fail("TODO inline memset for value of type {}", .{value}), } diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 218e1c6f55..5b3b03e8ee 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -120,6 +120,10 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .jmp_reloc => try emit.mirJmpReloc(inst), + .call_extern => try emit.mirCallExtern(inst), + + .lea_linker => try emit.mirLeaLinker(inst), + .mov_moffs => try emit.mirMovMoffs(inst), .movsx => try emit.mirMovsx(inst), @@ -213,6 +217,16 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, }; }, + .rri_s => operands[0..3].* = .{ + .{ .reg = data.rri_s.r1 }, + .{ .reg = data.rri_s.r2 }, + .{ .imm = Immediate.s(data.rri_s.imm) }, + }, + .rri_u => operands[0..3].* = .{ + .{ .reg = data.rri_u.r1 }, + .{ .reg = data.rri_u.r2 }, + .{ .imm = Immediate.u(data.rri_u.imm) }, + }, .m_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data; operands[0] = .{ .mem = Mir.MemorySib.decode(msib) }; @@ -402,47 +416,44 @@ fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .call_extern); -// const relocation = emit.mir.instructions.items(.data)[inst].relocation; +fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const relocation = emit.mir.instructions.items(.data)[inst].relocation; -// const offset = blk: { -// // callq -// try emit.encode(.call, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// break :blk @intCast(u32, emit.code.items.len) - 4; -// }; + const offset = blk: { + try emit.encode(.call, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + break :blk @intCast(u32, emit.code.items.len) - 4; + }; -// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { -// // Add relocation to the decl. -// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = macho_file.getGlobalByIndex(relocation.sym_index); -// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ -// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { -// // Add relocation to the decl. -// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = coff_file.getGlobalByIndex(relocation.sym_index); -// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ -// .type = .direct, -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else { -// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); -// } -// } + if (emit.bin_file.cast(link.File.MachO)) |macho_file| { + // Add relocation to the decl. + const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; + const target = macho_file.getGlobalByIndex(relocation.sym_index); + try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ + .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), + .target = target, + .offset = offset, + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + // Add relocation to the decl. + const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; + const target = coff_file.getGlobalByIndex(relocation.sym_index); + try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ + .type = .direct, + .target = target, + .offset = offset, + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else { + return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); + } +} fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; @@ -474,194 +485,63 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) } } -// fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// switch (ops.flags) { -// 0b00 => { -// const target = emit.mir.instructions.items(.data)[inst].inst; -// const source = emit.code.items.len; -// try emit.encode(mnemonic, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// try emit.relocs.append(emit.bin_file.allocator, .{ -// .source = source, -// .target = target, -// .offset = emit.code.items.len - 4, -// .length = 5, -// }); -// }, -// 0b01 => { -// if (ops.reg1 == .none) { -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// return emit.encode(mnemonic, .{ -// .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, -// }); -// } -// return emit.encode(mnemonic, .{ -// .op1 = .{ .reg = ops.reg1 }, -// }); -// }, -// 0b10 => { -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// return emit.encode(mnemonic, .{ -// .op1 = .{ .mem = Memory.sib(.qword, .{ -// .base = ops.reg1, -// .disp = disp, -// }) }, -// }); -// }, -// 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), -// } -// } +fn mirLeaLinker(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + const payload = emit.mir.instructions.items(.data)[inst].payload; + const metadata = emit.mir.extraData(Mir.LeaRegisterReloc, payload).data; + const reg = @intToEnum(Register, metadata.reg); -// fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .lea); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// switch (ops.flags) { -// 0b00 => { -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; -// return emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ -// .base = src_reg, -// .disp = disp, -// }) }, -// }); -// }, -// 0b01 => { -// const start_offset = emit.code.items.len; -// try emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, -// }); -// const end_offset = emit.code.items.len; -// // Backpatch the displacement -// const payload = emit.mir.instructions.items(.data)[inst].payload; -// const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); -// const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); -// mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); -// }, -// 0b10 => { -// const payload = emit.mir.instructions.items(.data)[inst].payload; -// const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); -// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; -// const scale_index = Memory.ScaleIndex{ -// .scale = 1, -// .index = index_reg_disp.index, -// }; -// return emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ -// .base = src_reg, -// .scale_index = scale_index, -// .disp = index_reg_disp.disp, -// }) }, -// }); -// }, -// 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), -// } -// } + try emit.encode(.lea, .{ + .op1 = .{ .reg = reg }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(reg.bitSize()), 0) }, + }); -// fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .lea_pic); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// const relocation = emit.mir.instructions.items(.data)[inst].relocation; + const end_offset = emit.code.items.len; -// switch (ops.flags) { -// 0b00, 0b01, 0b10 => {}, -// else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), -// } - -// try emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, -// }); - -// const end_offset = emit.code.items.len; - -// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { -// const reloc_type = switch (ops.flags) { -// 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), -// 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), -// else => unreachable, -// }; -// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ -// .type = reloc_type, -// .target = .{ .sym_index = relocation.sym_index, .file = null }, -// .offset = @intCast(u32, end_offset - 4), -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { -// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ -// .type = switch (ops.flags) { -// 0b00 => .got, -// 0b01 => .direct, -// 0b10 => .import, -// else => unreachable, -// }, -// .target = switch (ops.flags) { -// 0b00, 0b01 => .{ .sym_index = relocation.sym_index, .file = null }, -// 0b10 => coff_file.getGlobalByIndex(relocation.sym_index), -// else => unreachable, -// }, -// .offset = @intCast(u32, end_offset - 4), -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else { -// return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); -// } -// } - -// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .call_extern); -// const relocation = emit.mir.instructions.items(.data)[inst].relocation; - -// const offset = blk: { -// // callq -// try emit.encode(.call, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// break :blk @intCast(u32, emit.code.items.len) - 4; -// }; - -// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { -// // Add relocation to the decl. -// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = macho_file.getGlobalByIndex(relocation.sym_index); -// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ -// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { -// // Add relocation to the decl. -// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = coff_file.getGlobalByIndex(relocation.sym_index); -// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ -// .type = .direct, -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else { -// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); -// } -// } + if (emit.bin_file.cast(link.File.MachO)) |macho_file| { + const reloc_type = switch (ops) { + .got_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), + .direct_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), + else => unreachable, + }; + const atom_index = macho_file.getAtomIndexForSymbol(.{ + .sym_index = metadata.atom_index, + .file = null, + }).?; + try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ + .type = reloc_type, + .target = .{ .sym_index = metadata.sym_index, .file = null }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + const atom_index = coff_file.getAtomIndexForSymbol(.{ + .sym_index = metadata.atom_index, + .file = null, + }).?; + try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ + .type = switch (ops) { + .got_reloc => .got, + .direct_reloc => .direct, + .import_reloc => .import, + else => unreachable, + }, + .target = switch (ops) { + .got_reloc, .direct_reloc => .{ .sym_index = metadata.sym_index, .file = null }, + .import_reloc => coff_file.getGlobalByIndex(metadata.sym_index), + else => unreachable, + }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else { + return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); + } +} fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 40fd1953de..6f0d578662 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -142,6 +142,13 @@ pub const Inst = struct { /// Jump with relocation to another local MIR instruction jmp_reloc, + /// Call to an extern symbol via linker relocation. + /// Uses `relocation` payload. + call_extern, + + /// Load effective address of a symbol not yet allocated in VM. + lea_linker, + /// End of prologue dbg_prologue_end, /// Start of epilogue @@ -169,6 +176,12 @@ pub const Inst = struct { /// Register, register, register operands. /// Uses `rrr` payload. rrr, + /// Register, register, immediate (sign-extended) operands. + /// Uses `rri_s` payload. + rri_s, + /// Register, register, immediate (unsigned) operands. + /// Uses `rri_u` payload. + rri_u, /// Register with condition code (CC). /// Uses `r_c` payload. r_c, @@ -199,12 +212,6 @@ pub const Inst = struct { /// Register, memory (RIP) operands. /// Uses `rx` payload. rm_rip, - /// Register, memory, immediate (unsigned) operands - /// Uses `rx` payload. - rmi_u, - /// Register, memory, immediate (sign-extended) operands - /// Uses `rx` payload. - rmi_s, /// Single memory (SIB) operand. /// Uses `payload` with extra data of type `MemorySib`. m_sib, @@ -250,6 +257,15 @@ pub const Inst = struct { rm_cc, /// Uses `reloc` payload. reloc, + /// Linker relocation - GOT indirection. + /// Uses `payload` payload with extra data of type `LeaRegisterReloc`. + got_reloc, + /// Linker relocation - direct reference. + /// Uses `payload` payload with extra data of type `LeaRegisterReloc`. + direct_reloc, + /// Linker relocation - imports table indirection (binding). + /// Uses `payload` payload with extra data of type `LeaRegisterReloc`. + import_reloc, }; pub const Data = union { @@ -279,6 +295,16 @@ pub const Inst = struct { r2: Register, r3: Register, }, + rri_s: struct { + r1: Register, + r2: Register, + imm: i32, + }, + rri_u: struct { + r1: Register, + r2: Register, + imm: u32, + }, /// Register with condition code (CC). r_c: struct { r1: Register, @@ -339,13 +365,7 @@ pub const Inst = struct { pub const LeaRegisterReloc = struct { /// Destination register. - reg: Register, - /// Type of the load. - load_type: enum(u2) { - got, - direct, - import, - }, + reg: u32, /// Index of the containing atom. atom_index: u32, /// Index into the linker's symbol table. From fe1fab4a8ee8d908ab592c63bbc35bbbaa1ed0bf Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 10 Mar 2023 19:23:01 +0100 Subject: [PATCH 095/294] x86_64: fix CALL emits for ELF and Plan9 --- src/arch/x86_64/CodeGen.zig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 9056b64dce..6ba81aecdd 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4001,7 +4001,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); const got_addr = atom.getOffsetTableAddress(elf_file); - try self.asmImmediate(.call, Immediate.s(@intCast(i32, got_addr))); + try self.asmMemory(.call, Memory.sib(.qword, .{ + .base = .ds, + .disp = @intCast(i32, got_addr), + })); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; @@ -4030,7 +4033,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = p9.bases.data; const got_index = decl_block.got_index.?; const fn_got_addr = got_addr + got_index * ptr_bytes; - try self.asmImmediate(.call, Immediate.s(@intCast(i32, fn_got_addr))); + try self.asmMemory(.call, Memory.sib(.qword, .{ + .base = .ds, + .disp = @intCast(i32, fn_got_addr), + })); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; From e34e7d5ad1a61c66c145af612e11b7c6500caf79 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 10 Mar 2023 19:32:48 +0100 Subject: [PATCH 096/294] x86_64: add missing decodings for .movsx --- src/arch/x86_64/CodeGen.zig | 2 +- src/arch/x86_64/Emit.zig | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 6ba81aecdd..cd2b90051d 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -5841,7 +5841,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { .f32 => .movss, - .f64 => .movsx, + .f64 => .movsd, else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), }; const ptr_size: Memory.PtrSize = switch (ty.tag()) { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5b3b03e8ee..c490dea497 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -328,6 +328,16 @@ fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { op1 = .{ .reg = data.rr.r1 }; op2 = .{ .reg = data.rr.r2 }; }, + .rm_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; + op1 = .{ .reg = data.rx.r1 }; + op2 = .{ .mem = Mir.MemorySib.decode(msib) }; + }, + .rm_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data; + op1 = .{ .reg = data.rx.r1 }; + op2 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + }, else => unreachable, // TODO } From 6e1da365038856d9fbff690f187dc0a5c0933440 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 10 Mar 2023 19:38:15 +0100 Subject: [PATCH 097/294] x86_64: PtrSize.fromSize() should take into account nonexact sizes too --- src/arch/x86_64/bits.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index b6ac9ec5a8..1828cdc08f 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -418,14 +418,18 @@ pub const Memory = union(enum) { tbyte, pub fn fromSize(size: u32) PtrSize { - return switch (size) { - 1 => .byte, - 2 => .word, - 4 => .dword, - 8 => .qword, - 10 => .tbyte, - else => unreachable, - }; + return if (size <= 1) + .byte + else if (size <= 2) + .word + else if (size <= 4) + .dword + else if (size <= 8) + .qword + else if (size == 10) + .tbyte + else + unreachable; } pub fn fromBitSize(bit_size: u64) PtrSize { From 21630ea17f1db8791c86ccb6b5e64c7390c52f61 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 00:09:24 +0100 Subject: [PATCH 098/294] x86_64: apply couple of tweaks and pass behavior tests --- src/arch/x86_64/CodeGen.zig | 59 ++++++++++++++++++++++++++----------- src/arch/x86_64/Emit.zig | 5 +++- src/arch/x86_64/bits.zig | 2 +- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index cd2b90051d..9ad89b48ba 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2856,10 +2856,14 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .immediate => |imm| { switch (abi_size) { 1, 2, 4 => { + const immediate = if (value_ty.isSignedInt()) + Immediate.s(@intCast(i32, @bitCast(i64, imm))) + else + Immediate.u(@truncate(u32, imm)); try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0, - }), Immediate.u(@truncate(u32, imm))); + }), immediate); }, 8 => { // TODO: optimization: if the imm is only using the lower @@ -3580,7 +3584,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { .f32 => switch (mir_tag) { .add => .addss, - .cmp => .cmpss, + .cmp => .ucomiss, else => return self.fail( "TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}, @@ -3588,7 +3592,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu }, .f64 => switch (mir_tag) { .add => .addsd, - .cmp => .cmpsd, + .cmp => .ucomisd, else => return self.fail( "TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}, @@ -3599,7 +3603,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .{dst_ty.fmtDebug()}, ), }; - try self.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128()); + return self.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128()); } return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{}); @@ -5255,11 +5259,14 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // TODO // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. - // mov [rbp+offset], immediate + const immediate = if (ty.isSignedInt()) + Immediate.s(@intCast(i32, @bitCast(i64, imm))) + else + Immediate.u(@intCast(u32, imm)); try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rsp, .disp = -stack_offset, - }), Immediate.u(@intCast(u32, imm))); + }), immediate); }, 8 => { const reg = try self.copyToTmpRegister(ty, mcv); @@ -5340,10 +5347,19 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl if (!self.wantSafety()) return; // The already existing value will do just fine. // TODO Upgrade this to a memset call when we have that available. - switch (ty.abiSize(self.target.*)) { - 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }, opts), - 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }, opts), - 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }, opts), + switch (abi_size) { + 1, 2, 4 => { + const value: u64 = switch (abi_size) { + 1 => 0xaa, + 2 => 0xaaaa, + 4 => 0xaaaaaaaa, + else => unreachable, + }; + return self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = opts.dest_stack_base orelse .rbp, + .disp = -stack_offset, + }), Immediate.u(value)); + }, 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }, opts), else => |x| return self.genInlineMemset( .{ .stack_offset = stack_offset }, @@ -5385,13 +5401,17 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl try self.asmMemoryImmediate(.mov, Memory.sib(.byte, .{ .base = base_reg, .disp = -stack_offset, - }), Immediate.u(@truncate(u32, x_big))); + }), Immediate.u(@truncate(u8, x_big))); }, 1, 2, 4 => { + const immediate = if (ty.isSignedInt()) + Immediate.s(@truncate(i32, @bitCast(i64, x_big))) + else + Immediate.u(@intCast(u32, x_big)); try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = base_reg, .disp = -stack_offset, - }), Immediate.u(@truncate(u32, x_big))); + }), immediate); }, 8 => { // 64 bit write to memory would take two mov's anyways so we @@ -5777,12 +5797,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // register is the fastest way to zero a register. return self.asmRegisterRegister(.xor, reg.to32(), reg.to32()); } - if (ty.isSignedInt() and x <= math.maxInt(i32)) { - return self.asmRegisterImmediate( - .mov, - registerAlias(reg, abi_size), - Immediate.s(@intCast(i32, @bitCast(i64, x))), - ); + if (ty.isSignedInt()) { + const signed_x = @bitCast(i64, x); + if (math.minInt(i32) <= signed_x and signed_x <= math.maxInt(i32)) { + return self.asmRegisterImmediate( + .mov, + registerAlias(reg, abi_size), + Immediate.s(@intCast(i32, signed_x)), + ); + } } return self.asmRegisterImmediate( .mov, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index c490dea497..a660e3f9ba 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -341,7 +341,10 @@ fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { else => unreachable, // TODO } - const mnemonic: Instruction.Mnemonic = if (op1.bitSize() == 64 and op2.bitSize() == 32) .movsxd else .movsx; + const mnemonic: Instruction.Mnemonic = switch (op1.bitSize()) { + 32, 64 => if (op2.bitSize() == 32) .movsxd else .movsx, + else => .movsx, + }; return emit.encode(mnemonic, .{ .op1 = op1, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index 1828cdc08f..d974070e5d 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -542,7 +542,7 @@ pub const Immediate = union(enum) { .signed => |x| switch (bit_size) { 1, 8 => @bitCast(u8, @intCast(i8, x)), 16 => @bitCast(u16, @intCast(i16, x)), - 32 => @bitCast(u32, @intCast(i32, x)), + 32, 64 => @bitCast(u32, x), else => unreachable, }, .unsigned => |x| switch (bit_size) { From 621fc36b55a882c562d9378d4cf1fd8a5e1a907c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 09:21:41 +0100 Subject: [PATCH 099/294] x86_64: add wrapper for .jmp_reloc --- src/arch/x86_64/CodeGen.zig | 48 +++++++++++-------------------------- src/arch/x86_64/Emit.zig | 28 +++++++++------------- src/arch/x86_64/Mir.zig | 4 +--- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 9ad89b48ba..fabca1d848 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -423,6 +423,14 @@ fn asmCmovccRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bi }); } +fn asmJmpReloc(self: *Self, target: Mir.Inst.Index) !Mir.Inst.Index { + return self.addInst(.{ + .tag = .jmp_reloc, + .ops = undefined, + .data = .{ .inst = target }, + }); +} + fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, @@ -4145,11 +4153,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = undefined }, - }); + const jmp_reloc = try self.asmJmpReloc(undefined); try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4181,11 +4185,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = undefined }, - }); + const jmp_reloc = try self.asmJmpReloc(undefined); try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4722,11 +4722,7 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const body = self.air.extra[loop.end..][0..loop.data.body_len]; const jmp_target = @intCast(u32, self.mir_instructions.len); try self.genBody(body); - _ = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = jmp_target }, - }); + _ = try self.asmJmpReloc(jmp_target); return self.finishAirBookkeeping(); } @@ -5062,11 +5058,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined - const jmp_reloc = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = undefined }, - }); + const jmp_reloc = try self.asmJmpReloc(undefined); block_data.relocs.appendAssumeCapacity(jmp_reloc); } @@ -5656,13 +5648,7 @@ fn genInlineMemcpy( }), tmp_reg.to8()); try self.asmRegisterImmediate(.add, index_reg, Immediate.u(1)); try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); - - _ = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = loop_start }, - }); - + _ = try self.asmJmpReloc(loop_start); try self.performReloc(loop_reloc); } @@ -5750,13 +5736,7 @@ fn genInlineMemset( } try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1)); - - _ = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = loop_start }, - }); - + _ = try self.asmJmpReloc(loop_start); try self.performReloc(loop_reloc); } diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index a660e3f9ba..6e92e81882 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -410,23 +410,17 @@ fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst]; - switch (ops) { - .inst => { - const target = emit.mir.instructions.items(.data)[inst].inst; - const source = emit.code.items.len; - try emit.encode(.jmp, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = target, - .offset = emit.code.items.len - 4, - .length = 5, - }); - }, - else => unreachable, - } + const target = emit.mir.instructions.items(.data)[inst].inst; + const source = emit.code.items.len; + try emit.encode(.jmp, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + try emit.relocs.append(emit.bin_file.allocator, .{ + .source = source, + .target = target, + .offset = emit.code.items.len - 4, + .length = 5, + }); } fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 6f0d578662..5de8ad1410 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -140,6 +140,7 @@ pub const Inst = struct { mov_moffs, /// Jump with relocation to another local MIR instruction + /// Uses `inst` payload. jmp_reloc, /// Call to an extern symbol via linker relocation. @@ -242,9 +243,6 @@ pub const Inst = struct { /// Memory moffs, rax. /// Uses `payload` with extra data of type `MemoryMoffs`. moffs_rax, - /// Lea into register with linker relocation. - /// Uses `payload` payload with data of type `LeaRegisterReloc`. - lea_r_reloc, /// References another Mir instruction directly. /// Uses `inst` payload. inst, From c9a153c7978b363a252f83878f75bd875fe6ae5e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 09:29:53 +0100 Subject: [PATCH 100/294] x86_64: add .dead pseudo-instruction to mark an unused MIR instruction --- src/arch/x86_64/CodeGen.zig | 16 ++++++++-------- src/arch/x86_64/Emit.zig | 2 ++ src/arch/x86_64/Mir.zig | 4 ++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index fabca1d848..feb78ca7fc 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -630,8 +630,8 @@ fn gen(self: *Self) InnerError!void { // TODO During semantic analysis, check if there are no function calls. If there // are none, here we can omit the part where we subtract and then add rsp. const backpatch_stack_sub = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); @@ -657,8 +657,8 @@ fn gen(self: *Self) InnerError!void { // Push callee-preserved regs that were used actually in use. const backpatch_push_callee_preserved_regs = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); @@ -688,8 +688,8 @@ fn gen(self: *Self) InnerError!void { // Pop saved callee-preserved regs. const backpatch_pop_callee_preserved_regs = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); @@ -701,8 +701,8 @@ fn gen(self: *Self) InnerError!void { // Maybe add rsp, x if required. This is backpatched later. const backpatch_stack_add = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 6e92e81882..161c436323 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -137,6 +137,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push_regs => try emit.mirPushPopRegisterList(.push, inst), .pop_regs => try emit.mirPushPopRegisterList(.pop, inst), + + .dead => {}, } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 5de8ad1410..4cde4dd240 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -163,6 +163,10 @@ pub const Inst = struct { /// Pop registers /// Uses `payload` payload with data of type `SaveRegisterList`. pop_regs, + + /// Tombstone + /// Emitter should skip this instruction. + dead, }; pub const Ops = enum(u8) { From 0a8b5c20aa2402361a4e5698100902e47bd2c7c6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 09:37:54 +0100 Subject: [PATCH 101/294] x86_64: add wrapper for .jcc with relocation --- src/arch/x86_64/CodeGen.zig | 64 ++++++++++--------------------------- 1 file changed, 17 insertions(+), 47 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index feb78ca7fc..21c7adec3c 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -431,6 +431,17 @@ fn asmJmpReloc(self: *Self, target: Mir.Inst.Index) !Mir.Inst.Index { }); } +fn asmJccReloc(self: *Self, target: Mir.Inst.Index, cc: bits.Condition) !Mir.Inst.Index { + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = target, + .cc = cc, + } }, + }); +} + fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, @@ -4360,29 +4371,13 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { const abi_size = ty.abiSize(self.target.*); switch (mcv) { .eflags => |cc| { - return self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ - .inst_cc = .{ - .inst = undefined, - // Here we map the opposites since the jump is to the false branch. - .cc = cc.negate(), - }, - }, - }); + // Here we map the opposites since the jump is to the false branch. + return self.asmJccReloc(undefined, cc.negate()); }, .register => |reg| { try self.spillEflagsIfOccupied(); try self.asmRegisterImmediate(.@"test", reg, Immediate.u(1)); - return self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + return self.asmJccReloc(undefined, .e); }, .immediate, .stack_offset, @@ -4792,15 +4787,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u const aliased_reg = registerAlias(cond_reg, abi_size); try self.asmRegisterRegister(.@"test", aliased_reg, aliased_reg); - - return self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .ne, - } }, - }); + return self.asmJccReloc(undefined, .ne); }, .stack_offset => { try self.spillEflagsIfOccupied(); @@ -5612,7 +5599,6 @@ fn genInlineMemcpy( try self.genSetReg(Type.usize, count_reg, len); try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(0)); - const loop_start = try self.addInst(.{ .tag = .cmp, .ops = .ri_u, @@ -5621,15 +5607,7 @@ fn genInlineMemcpy( .imm = 0, } }, }); - const loop_reloc = try self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); - + const loop_reloc = try self.asmJccReloc(undefined, .e); try self.asmRegisterMemory(.mov, tmp_reg.to8(), Memory.sib(.byte, .{ .base = src_addr_reg, .scale_index = .{ @@ -5708,15 +5686,7 @@ fn genInlineMemset( .imm = -1, } }, }); - - const loop_reloc = try self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + const loop_reloc = try self.asmJccReloc(undefined, .e); switch (value) { .immediate => |x| { From fb38e3d6b29f71834c7ea4ef21e4d3f607c03777 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 16:32:30 +0100 Subject: [PATCH 102/294] x86_64: simplify immediate handling at MIR level --- src/arch/x86_64/CodeGen.zig | 77 +++++++++---------- src/arch/x86_64/Emit.zig | 142 +++++++++++++++++++----------------- src/arch/x86_64/Mir.zig | 55 +++++--------- 3 files changed, 128 insertions(+), 146 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 21c7adec3c..df06b90e4a 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -460,15 +460,13 @@ fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void { fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void { const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u; - const data: Mir.Inst.Data = switch (ops) { - .imm_s => .{ .imm_s = imm.signed }, - .imm_u => .{ .imm_u = @intCast(u32, imm.unsigned) }, - else => unreachable, - }; _ = try self.addInst(.{ .tag = tag, .ops = ops, - .data = data, + .data = .{ .imm = switch (imm) { + .signed => |x| @bitCast(u32, x), + .unsigned => |x| @intCast(u32, x), + } }, }); } @@ -489,11 +487,11 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme .unsigned => |x| if (x <= math.maxInt(u32)) .ri_u else .ri64, }; const data: Mir.Inst.Data = switch (ops) { - .ri_s => .{ .ri_s = .{ + .ri_s => .{ .ri = .{ .r1 = reg, - .imm = imm.signed, + .imm = @bitCast(u32, imm.signed), } }, - .ri_u => .{ .ri_u = .{ + .ri_u => .{ .ri = .{ .r1 = reg, .imm = @intCast(u32, imm.unsigned), } }, @@ -522,12 +520,12 @@ fn asmRegisterRegisterImmediate( .unsigned => .rri_u, }; const data: Mir.Inst.Data = switch (ops) { - .rri_s => .{ .rri_s = .{ + .rri_s => .{ .rri = .{ .r1 = reg1, .r2 = reg2, - .imm = imm.signed, + .imm = @bitCast(u32, imm.signed), } }, - .rri_u => .{ .rri_u = .{ + .rri_u => .{ .rri = .{ .r1 = reg1, .r2 = reg2, .imm = @intCast(u32, imm.unsigned), @@ -547,11 +545,11 @@ fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { .rip => .m_rip, else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .m_sib => .{ .payload = try self.addExtra(Mir.MemorySib.encode(m)) }, - .m_rip => .{ .payload = try self.addExtra(Mir.MemoryRip.encode(m)) }, + const data: Mir.Inst.Data = .{ .payload = switch (ops) { + .m_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .m_rip => try self.addExtra(Mir.MemoryRip.encode(m)), else => unreachable, - }; + } }; _ = try self.addInst(.{ .tag = tag, .ops = ops, @@ -570,10 +568,11 @@ fn asmMemoryImmediate(self: *Self, tag: Mir.Inst.Tag, m: Memory, imm: Immediate) .mi_s_rip, .mi_u_rip => try self.addExtra(Mir.MemoryRip.encode(m)), else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .mi_s_sib, .mi_s_rip => .{ .xi_s = .{ .imm = imm.signed, .payload = payload } }, - .mi_u_sib, .mi_u_rip => .{ .xi_u = .{ .imm = @intCast(u32, imm.unsigned), .payload = payload } }, - else => unreachable, + const data: Mir.Inst.Data = .{ + .xi = .{ .payload = payload, .imm = switch (imm) { + .signed => |x| @bitCast(u32, x), + .unsigned => |x| @intCast(u32, x), + } }, }; _ = try self.addInst(.{ .tag = tag, @@ -588,16 +587,12 @@ fn asmRegisterMemory(self: *Self, tag: Mir.Inst.Tag, reg: Register, m: Memory) ! .rip => .rm_rip, else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .rm_sib => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemorySib.encode(m)), + const data: Mir.Inst.Data = .{ + .rx = .{ .r1 = reg, .payload = switch (ops) { + .rm_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .rm_rip => try self.addExtra(Mir.MemoryRip.encode(m)), + else => unreachable, } }, - .rm_rip => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemoryRip.encode(m)), - } }, - else => unreachable, }; _ = try self.addInst(.{ .tag = tag, @@ -612,16 +607,12 @@ fn asmMemoryRegister(self: *Self, tag: Mir.Inst.Tag, m: Memory, reg: Register) ! .rip => .mr_rip, else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .mr_sib => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemorySib.encode(m)), + const data: Mir.Inst.Data = .{ + .rx = .{ .r1 = reg, .payload = switch (ops) { + .mr_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .mr_rip => try self.addExtra(Mir.MemoryRip.encode(m)), + else => unreachable, } }, - .mr_rip => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemoryRip.encode(m)), - } }, - else => unreachable, }; _ = try self.addInst(.{ .tag = tag, @@ -733,7 +724,7 @@ fn gen(self: *Self) InnerError!void { self.mir_instructions.set(backpatch_stack_sub, .{ .tag = .sub, .ops = .ri_u, - .data = .{ .ri_u = .{ + .data = .{ .ri = .{ .r1 = .rsp, .imm = aligned_stack_end, } }, @@ -741,7 +732,7 @@ fn gen(self: *Self) InnerError!void { self.mir_instructions.set(backpatch_stack_add, .{ .tag = .add, .ops = .ri_u, - .data = .{ .ri_u = .{ + .data = .{ .ri = .{ .r1 = .rsp, .imm = aligned_stack_end, } }, @@ -5602,7 +5593,7 @@ fn genInlineMemcpy( const loop_start = try self.addInst(.{ .tag = .cmp, .ops = .ri_u, - .data = .{ .ri_u = .{ + .data = .{ .ri = .{ .r1 = count_reg, .imm = 0, } }, @@ -5681,9 +5672,9 @@ fn genInlineMemset( const loop_start = try self.addInst(.{ .tag = .cmp, .ops = .ri_s, - .data = .{ .ri_s = .{ + .data = .{ .ri = .{ .r1 = index_reg, - .imm = -1, + .imm = @bitCast(u32, @as(i32, -1)), } }, }); const loop_reloc = try self.asmJccReloc(undefined, .e); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 161c436323..5a7f2ef224 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -194,105 +194,115 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const ops = emit.mir.instructions.items(.ops)[inst]; const data = emit.mir.instructions.items(.data)[inst]; - var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; + var op1: Instruction.Operand = .none; + var op2: Instruction.Operand = .none; + var op3: Instruction.Operand = .none; + var op4: Instruction.Operand = .none; + switch (ops) { .none => {}, - .imm_s => operands[0] = .{ .imm = Immediate.s(data.imm_s) }, - .imm_u => operands[0] = .{ .imm = Immediate.u(data.imm_u) }, - .r => operands[0] = .{ .reg = data.r }, - .rr => operands[0..2].* = .{ - .{ .reg = data.rr.r1 }, - .{ .reg = data.rr.r2 }, + .imm_s => op1 = .{ .imm = Immediate.s(@bitCast(i32, data.imm)) }, + .imm_u => op1 = .{ .imm = Immediate.u(data.imm) }, + .r => op1 = .{ .reg = data.r }, + .rr => { + op1 = .{ .reg = data.rr.r1 }; + op2 = .{ .reg = data.rr.r2 }; }, - .ri_s => operands[0..2].* = .{ - .{ .reg = data.ri_s.r1 }, - .{ .imm = Immediate.s(data.ri_s.imm) }, - }, - .ri_u => operands[0..2].* = .{ - .{ .reg = data.ri_u.r1 }, - .{ .imm = Immediate.u(data.ri_u.imm) }, + .ri_s, .ri_u => { + const imm = switch (ops) { + .ri_s => Immediate.s(@bitCast(i32, data.ri.imm)), + .ri_u => Immediate.u(data.ri.imm), + else => unreachable, + }; + op1 = .{ .reg = data.ri.r1 }; + op2 = .{ .imm = imm }; }, .ri64 => { const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data; - operands[0..2].* = .{ - .{ .reg = data.rx.r1 }, - .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, + op1 = .{ .reg = data.rx.r1 }; + op2 = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }; + }, + .rri_s, .rri_u => { + const imm = switch (ops) { + .rri_s => Immediate.s(@bitCast(i32, data.rri.imm)), + .rri_u => Immediate.u(data.rri.imm), + else => unreachable, }; - }, - .rri_s => operands[0..3].* = .{ - .{ .reg = data.rri_s.r1 }, - .{ .reg = data.rri_s.r2 }, - .{ .imm = Immediate.s(data.rri_s.imm) }, - }, - .rri_u => operands[0..3].* = .{ - .{ .reg = data.rri_u.r1 }, - .{ .reg = data.rri_u.r2 }, - .{ .imm = Immediate.u(data.rri_u.imm) }, + op1 = .{ .reg = data.rri.r1 }; + op2 = .{ .reg = data.rri.r2 }; + op3 = .{ .imm = imm }; }, .m_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data; - operands[0] = .{ .mem = Mir.MemorySib.decode(msib) }; + op1 = .{ .mem = Mir.MemorySib.decode(msib) }; }, .m_rip => { const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data; - operands[0] = .{ .mem = Mir.MemoryRip.decode(mrip) }; + op1 = .{ .mem = Mir.MemoryRip.decode(mrip) }; }, - .mi_u_sib => { - const msib = emit.mir.extraData(Mir.MemorySib, data.xi_u.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemorySib.decode(msib) }, - .{ .imm = Immediate.u(data.xi_u.imm) }, + .mi_s_sib, .mi_u_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.xi.payload).data; + const imm = switch (ops) { + .mi_s_sib => Immediate.s(@bitCast(i32, data.xi.imm)), + .mi_u_sib => Immediate.u(data.xi.imm), + else => unreachable, }; + op1 = .{ .mem = Mir.MemorySib.decode(msib) }; + op2 = .{ .imm = imm }; }, - .mi_s_sib => { - const msib = emit.mir.extraData(Mir.MemorySib, data.xi_s.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemorySib.decode(msib) }, - .{ .imm = Immediate.s(data.xi_s.imm) }, - }; - }, - .mi_u_rip => { - const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_u.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemoryRip.decode(mrip) }, - .{ .imm = Immediate.u(data.xi_u.imm) }, - }; - }, - .mi_s_rip => { - const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_s.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemoryRip.decode(mrip) }, - .{ .imm = Immediate.s(data.xi_s.imm) }, + .mi_u_rip, .mi_s_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi.payload).data; + const imm = switch (ops) { + .mi_s_rip => Immediate.s(@bitCast(i32, data.xi.imm)), + .mi_u_rip => Immediate.u(data.xi.imm), + else => unreachable, }; + op1 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + op2 = .{ .imm = imm }; }, .rm_sib, .mr_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; - const op1 = .{ .reg = data.rx.r1 }; - const op2 = .{ .mem = Mir.MemorySib.decode(msib) }; + const op_r = .{ .reg = data.rx.r1 }; + const op_m = .{ .mem = Mir.MemorySib.decode(msib) }; switch (ops) { - .rm_sib => operands[0..2].* = .{ op1, op2 }, - .mr_sib => operands[0..2].* = .{ op2, op1 }, + .rm_sib => { + op1 = op_r; + op2 = op_m; + }, + .mr_sib => { + op1 = op_m; + op2 = op_r; + }, else => unreachable, } }, .rm_rip, .mr_rip => { const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data; - const op1 = .{ .reg = data.rx.r1 }; - const op2 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + const op_r = .{ .reg = data.rx.r1 }; + const op_m = .{ .mem = Mir.MemoryRip.decode(mrip) }; switch (ops) { - .rm_rip => operands[0..2].* = .{ op1, op2 }, - .mr_rip => operands[0..2].* = .{ op2, op1 }, + .rm_sib => { + op1 = op_r; + op2 = op_m; + }, + .mr_sib => { + op1 = op_m; + op2 = op_r; + }, else => unreachable, } }, - else => unreachable, // TODO + else => return emit.fail("TODO handle generic encoding: {s}, {s}", .{ + @tagName(mnemonic), + @tagName(ops), + }), } return emit.encode(mnemonic, .{ - .op1 = operands[0], - .op2 = operands[1], - .op3 = operands[2], - .op4 = operands[3], + .op1 = op1, + .op2 = op2, + .op3 = op3, + .op4 = op4, }); } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 4cde4dd240..2f611258fd 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -182,10 +182,10 @@ pub const Inst = struct { /// Uses `rrr` payload. rrr, /// Register, register, immediate (sign-extended) operands. - /// Uses `rri_s` payload. + /// Uses `rri` payload. rri_s, /// Register, register, immediate (unsigned) operands. - /// Uses `rri_u` payload. + /// Uses `rri` payload. rri_u, /// Register with condition code (CC). /// Uses `r_c` payload. @@ -194,22 +194,22 @@ pub const Inst = struct { /// Uses `rr_c` payload. rr_c, /// Register, immediate (sign-extended) operands. - /// Uses `ri_s` payload. + /// Uses `ri` payload. ri_s, /// Register, immediate (unsigned) operands. - /// Uses `ri_u` payload. + /// Uses `ri` payload. ri_u, /// Register, 64-bit unsigned immediate operands. /// Uses `rx` payload with payload type `Imm64`. ri64, /// Immediate (sign-extended) operand. - /// Uses `imm_s` payload. + /// Uses `imm` payload. imm_s, /// Immediate (unsigned) operand. - /// Uses `imm_u` payload. + /// Uses `imm` payload. imm_u, /// Relative displacement operand. - /// Uses `rel` payload. + /// Uses `imm` payload. rel, /// Register, memory (SIB) operands. /// Uses `rx` payload. @@ -224,16 +224,16 @@ pub const Inst = struct { /// Uses `payload` with extra data of type `MemoryRip`. m_rip, /// Memory (SIB), immediate (unsigned) operands. - /// Uses `xi_u` payload with extra data of type `MemorySib`. + /// Uses `xi` payload with extra data of type `MemorySib`. mi_u_sib, /// Memory (RIP), immediate (unsigned) operands. - /// Uses `xi_u` payload with extra data of type `MemoryRip`. + /// Uses `xi` payload with extra data of type `MemoryRip`. mi_u_rip, /// Memory (SIB), immediate (sign-extend) operands. - /// Uses `xi_s` payload with extra data of type `MemorySib`. + /// Uses `xi` payload with extra data of type `MemorySib`. mi_s_sib, /// Memory (RIP), immediate (sign-extend) operands. - /// Uses `xi_s` payload with extra data of type `MemoryRip`. + /// Uses `xi` payload with extra data of type `MemoryRip`. mi_s_rip, /// Memory (SIB), register operands. /// Uses `rx` payload with extra data of type `MemorySib`. @@ -281,12 +281,8 @@ pub const Inst = struct { /// A condition code for use with EFLAGS register. cc: bits.Condition, }, - /// A 32-bit signed immediate value. - imm_s: i32, - /// A 32-bit unsigned immediate value. - imm_u: u32, - /// A 32-bit signed relative offset value. - rel: i32, + /// A 32-bit immediate value. + imm: u32, r: Register, rr: struct { r1: Register, @@ -297,12 +293,7 @@ pub const Inst = struct { r2: Register, r3: Register, }, - rri_s: struct { - r1: Register, - r2: Register, - imm: i32, - }, - rri_u: struct { + rri: struct { r1: Register, r2: Register, imm: u32, @@ -318,13 +309,8 @@ pub const Inst = struct { r2: Register, cc: bits.Condition, }, - /// Register, signed immediate. - ri_s: struct { - r1: Register, - imm: i32, - }, - /// Register, unsigned immediate. - ri_u: struct { + /// Register, immediate. + ri: struct { r1: Register, imm: u32, }, @@ -333,16 +319,11 @@ pub const Inst = struct { r1: Register, payload: u32, }, - /// Custom payload followed by an unsigned immediate. - xi_u: struct { + /// Custom payload followed by an immediate. + xi: struct { payload: u32, imm: u32, }, - /// Custom payload followed by a signed immediate. - xi_s: struct { - payload: u32, - imm: i32, - }, /// Relocation for the linker where: /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target From f279ccb8078db9df92f02e23f70486f1950b92ed Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 19:33:22 +0100 Subject: [PATCH 103/294] x86_64: rename asmNone to asmOpOnly --- src/arch/x86_64/CodeGen.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index df06b90e4a..3b7dc0db57 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -442,7 +442,7 @@ fn asmJccReloc(self: *Self, target: Mir.Inst.Index, cc: bits.Condition) !Mir.Ins }); } -fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { +fn asmOpOnly(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, .ops = .none, @@ -709,7 +709,7 @@ fn gen(self: *Self) InnerError!void { }); try self.asmRegister(.pop, .rbp); - try self.asmNone(.ret); + try self.asmOpOnly(.ret); // Adjust the stack if (self.max_end_stack > math.maxInt(i32)) { @@ -1856,7 +1856,7 @@ fn genIntMulDivOpMir( } switch (signedness) { - .signed => try self.asmNone(.cqo), + .signed => try self.asmOpOnly(.cqo), .unsigned => try self.asmRegisterRegister(.xor, .rdx, .rdx), } @@ -3901,12 +3901,12 @@ fn genVarDbgInfo( } fn airTrap(self: *Self) !void { - try self.asmNone(.ud2); + try self.asmOpOnly(.ud2); return self.finishAirBookkeeping(); } fn airBreakpoint(self: *Self) !void { - try self.asmNone(.int3); + try self.asmOpOnly(.int3); return self.finishAirBookkeeping(); } @@ -5109,7 +5109,7 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { - try self.asmNone(.syscall); + try self.asmOpOnly(.syscall); } else if (mem.indexOf(u8, ins, "push")) |_| { const arg = ins[4..]; if (mem.indexOf(u8, arg, "$")) |l| { From 433558a92f005d3ad68528c62ffd6006c48b80bd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 19:50:23 +0100 Subject: [PATCH 104/294] x86_64: clean up --- src/arch/x86_64/Emit.zig | 4 ++-- src/arch/x86_64/Encoding.zig | 2 +- src/arch/x86_64/Mir.zig | 11 ++++------- src/arch/x86_64/bits.zig | 7 ------- src/arch/x86_64/encoder.zig | 5 +++-- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5a7f2ef224..32699d35cb 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -177,12 +177,12 @@ fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { op3: Instruction.Operand = .none, op4: Instruction.Operand = .none, }) InnerError!void { - const inst = Instruction.new(mnemonic, .{ + const inst = try Instruction.new(mnemonic, .{ .op1 = ops.op1, .op2 = ops.op2, .op3 = ops.op3, .op4 = ops.op4, - }) catch unreachable; + }); return inst.encode(emit.code.writer()); } diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 94f816eaa1..c6a8d044c3 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -121,7 +121,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { .encoding = encoding, }; var cwriter = std.io.countingWriter(std.io.null_writer); - inst.encode(cwriter.writer()) catch unreachable; + inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM. return cwriter.bytes_written; } }; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 2f611258fd..3951108e3a 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -24,9 +24,6 @@ instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. extra: []const u32, -pub const Mnemonic = encoder.Instruction.Mnemonic; -pub const Operand = encoder.Instruction.Operand; - pub const Inst = struct { tag: Tag, ops: Ops, @@ -69,8 +66,6 @@ pub const Inst = struct { imul, /// int3, - /// Conditional jump - jcc, /// Jump jmp, /// Load effective address @@ -99,8 +94,6 @@ pub const Inst = struct { sar, /// Integer subtraction with borrow sbb, - /// Set byte on condition - setcc, /// Logical shift left shl, /// Logical shift right @@ -135,6 +128,10 @@ pub const Inst = struct { /// Conditional move cmovcc, + /// Conditional jump + jcc, + /// Set byte on condition + setcc, /// Mov absolute to/from memory wrt segment register to/from rax mov_moffs, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index d974070e5d..043e589af4 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -242,13 +242,6 @@ pub const Register = enum(u7) { }; } - pub fn isRexInvalid(reg: Register) bool { - return switch (@enumToInt(reg)) { - @enumToInt(Register.ah)...@enumToInt(Register.bh) => true, - else => false, - }; - } - pub fn enc(reg: Register) u4 { const base = switch (@enumToInt(reg)) { // zig fmt: off diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 292b61ee21..9206b621bc 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -1,5 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; +const log = std.log.scoped(.x86_64_encoder); const math = std.math; const bits = @import("bits.zig"); @@ -106,7 +107,7 @@ pub const Instruction = struct { .op3 = args.op3, .op4 = args.op4, })) orelse { - std.log.warn("{s} {s} {s} {s} {s}", .{ + log.debug("no encoding found for: {s} {s} {s} {s} {s}", .{ @tagName(mnemonic), @tagName(Encoding.Op.fromOperand(args.op1)), @tagName(Encoding.Op.fromOperand(args.op2)), @@ -115,7 +116,7 @@ pub const Instruction = struct { }); return error.InvalidInstruction; }; - std.log.debug("{}", .{encoding}); + log.debug("selected encoding: {}", .{encoding}); return .{ .op1 = args.op1, .op2 = args.op2, From 707a74655be9fd702cb1be84baa719b29435fcf7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 08:41:44 +0100 Subject: [PATCH 105/294] x86_64: downstream encoder/assembler tests --- src/arch/x86_64/encoder.zig | 1489 +++++++++++++++++++++++++++++++++++ 1 file changed, 1489 insertions(+) diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 9206b621bc..7e29f95069 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -2,6 +2,7 @@ const std = @import("std"); const assert = std.debug.assert; const log = std.log.scoped(.x86_64_encoder); const math = std.math; +const testing = std.testing; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); @@ -784,3 +785,1491 @@ pub const Rex = struct { return rex.w or rex.r or rex.x or rex.b; } }; + +// Tests +fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { + assert(expected.len > 0); + if (std.mem.eql(u8, expected, given)) return; + const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); + defer testing.allocator.free(expected_fmt); + const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); + defer testing.allocator.free(given_fmt); + const idx = std.mem.indexOfDiff(u8, expected_fmt, given_fmt).?; + var padding = try testing.allocator.alloc(u8, idx + 5); + defer testing.allocator.free(padding); + std.mem.set(u8, padding, ' '); + std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ + assembly, + expected_fmt, + given_fmt, + padding, + }); + return error.TestFailed; +} + +const TestEncode = struct { + buffer: [32]u8 = undefined, + index: usize = 0, + + fn encode(enc: *TestEncode, mnemonic: Instruction.Mnemonic, args: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, + }) !void { + var stream = std.io.fixedBufferStream(&enc.buffer); + var count_writer = std.io.countingWriter(stream.writer()); + const inst = try Instruction.new(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }); + try inst.encode(count_writer.writer()); + enc.index = count_writer.bytes_written; + } + + fn code(enc: TestEncode) []const u8 { + return enc.buffer[0..enc.index]; + } +}; + +test "encode" { + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + const inst = try Instruction.new(.mov, .{ + .op1 = .{ .reg = .rbx }, + .op2 = .{ .imm = Immediate.u(4) }, + }); + try inst.encode(buf.writer()); + try testing.expectEqualSlices(u8, &.{ 0x48, 0xc7, 0xc3, 0x4, 0x0, 0x0, 0x0 }, buf.items); +} + +test "lower I encoding" { + var enc = TestEncode{}; + + try enc.encode(.push, .{ .op1 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x6A\x10", enc.code(), "push 0x10"); + + try enc.encode(.push, .{ .op1 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x66\x68\x00\x10", enc.code(), "push 0x1000"); + + try enc.encode(.push, .{ .op1 = .{ .imm = Immediate.u(0x10000000) } }); + try expectEqualHexStrings("\x68\x00\x00\x00\x10", enc.code(), "push 0x10000000"); + + try enc.encode(.adc, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10000000) } }); + try expectEqualHexStrings("\x48\x15\x00\x00\x00\x10", enc.code(), "adc rax, 0x10000000"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .al }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x04\x10", enc.code(), "add al, 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10"); + + try enc.encode(.sbb, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x66\x1D\x10\x00", enc.code(), "sbb ax, 0x10"); + + try enc.encode(.xor, .{ .op1 = .{ .reg = .al }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x34\x10", enc.code(), "xor al, 0x10"); +} + +test "lower MI encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12 }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .r12, + .disp = 0, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x41\xC6\x04\x24\x10", enc.code(), "mov BYTE PTR [r12], 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12 }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12 }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", enc.code(), "mov rax, 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .r11, + .disp = 0, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", enc.code(), "mov DWORD PTR [r11], 0x10"); + + try enc.encode(.mov, .{ + .op1 = .{ .mem = Memory.rip(.qword, 0x10) }, + .op2 = .{ .imm = Immediate.u(0x10) }, + }); + try expectEqualHexStrings( + "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", + enc.code(), + "mov QWORD PTR [rip + 0x10], 0x10", + ); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -8, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\xc7\x45\xf8\x10\x00\x00\x00", enc.code(), "mov QWORD PTR [rbp - 8], 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -2, + }) }, .op2 = .{ .imm = Immediate.s(-16) } }); + try expectEqualHexStrings("\x66\xC7\x45\xFE\xF0\xFF", enc.code(), "mov WORD PTR [rbp - 2], -16"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .rbp, + .disp = -1, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\xC6\x45\xFF\x10", enc.code(), "mov BYTE PTR [rbp - 1], 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .ds, + .disp = 0x10000000, + .scale_index = .{ + .scale = 2, + .index = .rcx, + }, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00", + enc.code(), + "mov QWORD PTR [rcx*2 + 0x10000000], 0x10", + ); + + try enc.encode(.adc, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .rbp, + .disp = -0x10, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x80\x55\xF0\x10", enc.code(), "adc BYTE PTR [rbp - 0x10], 0x10"); + + try enc.encode(.adc, .{ .op1 = .{ .mem = Memory.rip(.qword, 0) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\x15\x00\x00\x00\x00\x10", enc.code(), "adc QWORD PTR [rip], 0x10"); + + try enc.encode(.adc, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\xD0\x10", enc.code(), "adc rax, 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .rdx, + .disp = -8, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x83\x42\xF8\x10", enc.code(), "add DWORD PTR [rdx - 8], 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -0x10, + }) }, .op2 = .{ .imm = Immediate.s(-0x10) } }); + try expectEqualHexStrings("\x48\x83\x45\xF0\xF0", enc.code(), "add QWORD PTR [rbp - 0x10], -0x10"); + + try enc.encode(.@"and", .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .ds, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x83\x24\x25\x00\x00\x00\x10\x10", + enc.code(), + "and DWORD PTR ds:0x10000000, 0x10", + ); + + try enc.encode(.@"and", .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .es, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x26\x83\x24\x25\x00\x00\x00\x10\x10", + enc.code(), + "and DWORD PTR es:0x10000000, 0x10", + ); + + try enc.encode(.@"and", .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .r12, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x41\x83\xA4\x24\x00\x00\x00\x10\x10", + enc.code(), + "and DWORD PTR [r12 + 0x10000000], 0x10", + ); + + try enc.encode(.sub, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .r11, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x41\x83\xAB\x00\x00\x00\x10\x10", + enc.code(), + "sub DWORD PTR [r11 + 0x10000000], 0x10", + ); +} + +test "lower RM encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .r11, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x49\x8b\x03", enc.code(), "mov rax, QWORD PTR [r11]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rbx }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .ds, + .disp = 0x10, + }) } }); + try expectEqualHexStrings("\x48\x8B\x1C\x25\x10\x00\x00\x00", enc.code(), "mov rbx, QWORD PTR ds:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -4, + }) } }); + try expectEqualHexStrings("\x48\x8B\x45\xFC", enc.code(), "mov rax, QWORD PTR [rbp - 4]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 1, + .index = .rcx, + }, + .disp = -8, + }) } }); + try expectEqualHexStrings("\x48\x8B\x44\x0D\xF8", enc.code(), "mov rax, QWORD PTR [rbp + rcx*1 - 8]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.sib(.dword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 4, + .index = .rdx, + }, + .disp = -4, + }) } }); + try expectEqualHexStrings("\x8B\x44\x95\xFC", enc.code(), "mov eax, dword ptr [rbp + rdx*4 - 4]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 8, + .index = .rcx, + }, + .disp = -8, + }) } }); + try expectEqualHexStrings("\x48\x8B\x44\xCD\xF8", enc.code(), "mov rax, QWORD PTR [rbp + rcx*8 - 8]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r8b }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = .rsi, + .scale_index = .{ + .scale = 1, + .index = .rcx, + }, + .disp = -24, + }) } }); + try expectEqualHexStrings("\x44\x8A\x44\x0E\xE8", enc.code(), "mov r8b, BYTE PTR [rsi + rcx*1 - 24]"); + + // TODO this mnemonic needs cleanup as some prefixes are obsolete. + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .cs } }); + try expectEqualHexStrings("\x48\x8C\xC8", enc.code(), "mov rax, cs"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -16, + }) }, .op2 = .{ .reg = .fs } }); + try expectEqualHexStrings("\x48\x8C\x65\xF0", enc.code(), "mov QWORD PTR [rbp - 16], fs"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12w }, .op2 = .{ .reg = .cs } }); + try expectEqualHexStrings("\x66\x41\x8C\xCC", enc.code(), "mov r12w, cs"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -16, + }) }, .op2 = .{ .reg = .fs } }); + try expectEqualHexStrings("\x66\x8C\x65\xF0", enc.code(), "mov WORD PTR [rbp - 16], fs"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .reg = .bx } }); + try expectEqualHexStrings("\x0F\xBF\xC3", enc.code(), "movsx eax, bx"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .reg = .bl } }); + try expectEqualHexStrings("\x0F\xBE\xC3", enc.code(), "movsx eax, bl"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .reg = .bl } }); + try expectEqualHexStrings("\x66\x0F\xBE\xC3", enc.code(), "movsx ax, bl"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x0F\xBF\x45\x00", enc.code(), "movsx eax, BYTE PTR [rbp]"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = null, + .scale_index = .{ + .index = .rax, + .scale = 2, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x0F\xBE\x04\x45\x00\x00\x00\x00", enc.code(), "movsx eax, BYTE PTR [rax * 2]"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .mem = Memory.rip(.byte, 0x10) } }); + try expectEqualHexStrings("\x66\x0F\xBE\x05\x10\x00\x00\x00", enc.code(), "movsx ax, BYTE PTR [rip + 0x10]"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .bx } }); + try expectEqualHexStrings("\x48\x0F\xBF\xC3", enc.code(), "movsx rax, bx"); + + try enc.encode(.movsxd, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .ebx } }); + try expectEqualHexStrings("\x48\x63\xC3", enc.code(), "movsxd rax, ebx"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.rip(.qword, 0x10) } }); + try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", enc.code(), "lea rax, QWORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.rip(.dword, 0x10) } }); + try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", enc.code(), "lea rax, DWORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.rip(.dword, 0x10) } }); + try expectEqualHexStrings("\x8D\x05\x10\x00\x00\x00", enc.code(), "lea eax, DWORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.rip(.word, 0x10) } }); + try expectEqualHexStrings("\x8D\x05\x10\x00\x00\x00", enc.code(), "lea eax, WORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .mem = Memory.rip(.byte, 0x10) } }); + try expectEqualHexStrings("\x66\x8D\x05\x10\x00\x00\x00", enc.code(), "lea ax, BYTE PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .rsi }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 1, + .index = .rcx, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x48\x8D\x74\x0D\x00", enc.code(), "lea rsi, QWORD PTR [rbp + rcx*1 + 0]"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .ds, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x4C\x03\x1C\x25\x00\x00\x00\x10", enc.code(), "add r11, QWORD PTR ds:0x10000000"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .r12b }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = .ds, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x44\x02\x24\x25\x00\x00\x00\x10", enc.code(), "add r11b, BYTE PTR ds:0x10000000"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .r12b }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = .fs, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x64\x44\x02\x24\x25\x00\x00\x00\x10", enc.code(), "add r11b, BYTE PTR fs:0x10000000"); + + try enc.encode(.sub, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .r13, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x4D\x2B\x9D\x00\x00\x00\x10", enc.code(), "sub r11, QWORD PTR [r13 + 0x10000000]"); + + try enc.encode(.sub, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .r12, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x4D\x2B\x9C\x24\x00\x00\x00\x10", enc.code(), "sub r11, QWORD PTR [r12 + 0x10000000]"); + + try enc.encode(.imul, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x4D\x0F\xAF\xDC", enc.code(), "mov r11, r12"); +} + +test "lower RMI encoding" { + var enc = TestEncode{}; + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .r11 }, + .op2 = .{ .reg = .r12 }, + .op3 = .{ .imm = Immediate.s(-2) }, + }); + try expectEqualHexStrings("\x4D\x6B\xDC\xFE", enc.code(), "imul r11, r12, -2"); + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .r11 }, + .op2 = .{ .mem = Memory.rip(.qword, -16) }, + .op3 = .{ .imm = Immediate.s(-1024) }, + }); + try expectEqualHexStrings( + "\x4C\x69\x1D\xF0\xFF\xFF\xFF\x00\xFC\xFF\xFF", + enc.code(), + "imul r11, QWORD PTR [rip - 16], -1024", + ); + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .bx }, + .op2 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -16, + }) }, + .op3 = .{ .imm = Immediate.s(-1024) }, + }); + try expectEqualHexStrings( + "\x66\x69\x5D\xF0\x00\xFC", + enc.code(), + "imul bx, WORD PTR [rbp - 16], -1024", + ); + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .bx }, + .op2 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -16, + }) }, + .op3 = .{ .imm = Immediate.u(1024) }, + }); + try expectEqualHexStrings( + "\x66\x69\x5D\xF0\x00\x04", + enc.code(), + "imul bx, WORD PTR [rbp - 16], 1024", + ); +} + +test "lower MR encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .rbx } }); + try expectEqualHexStrings("\x48\x89\xD8", enc.code(), "mov rax, rbx"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -4, + }) }, .op2 = .{ .reg = .r11 } }); + try expectEqualHexStrings("\x4c\x89\x5d\xfc", enc.code(), "mov QWORD PTR [rbp - 4], r11"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.rip(.qword, 0x10) }, .op2 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", enc.code(), "mov QWORD PTR [rip + 0x10], r12"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .r11, + .scale_index = .{ + .scale = 2, + .index = .r12, + }, + .disp = 0x10, + }) }, .op2 = .{ .reg = .r13 } }); + try expectEqualHexStrings("\x4F\x89\x6C\x63\x10", enc.code(), "mov QWORD PTR [r11 + 2 * r12 + 0x10], r13"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.rip(.word, -0x10) }, .op2 = .{ .reg = .r12w } }); + try expectEqualHexStrings("\x66\x44\x89\x25\xF0\xFF\xFF\xFF", enc.code(), "mov WORD PTR [rip - 0x10], r12w"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .r11, + .scale_index = .{ + .scale = 2, + .index = .r12, + }, + .disp = 0x10, + }) }, .op2 = .{ .reg = .r13b } }); + try expectEqualHexStrings("\x47\x88\x6C\x63\x10", enc.code(), "mov BYTE PTR [r11 + 2 * r12 + 0x10], r13b"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .ds, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12b } }); + try expectEqualHexStrings("\x44\x00\x24\x25\x00\x00\x00\x10", enc.code(), "add BYTE PTR ds:0x10000000, r12b"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .ds, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12d } }); + try expectEqualHexStrings("\x44\x01\x24\x25\x00\x00\x00\x10", enc.code(), "add DWORD PTR [ds:0x10000000], r12d"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .gs, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12d } }); + try expectEqualHexStrings("\x65\x44\x01\x24\x25\x00\x00\x00\x10", enc.code(), "add DWORD PTR [gs:0x10000000], r12d"); + + try enc.encode(.sub, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .r11, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x4D\x29\xA3\x00\x00\x00\x10", enc.code(), "sub QWORD PTR [r11 + 0x10000000], r12"); +} + +test "lower M encoding" { + var enc = TestEncode{}; + + try enc.encode(.call, .{ .op1 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x41\xFF\xD4", enc.code(), "call r12"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .r12, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x41\xFF\x14\x24", enc.code(), "call QWORD PTR [r12]"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = null, + .scale_index = .{ + .index = .r11, + .scale = 2, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x42\xFF\x14\x5D\x00\x00\x00\x00", enc.code(), "call QWORD PTR [r11 * 2]"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = null, + .scale_index = .{ + .index = .r12, + .scale = 2, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x42\xFF\x14\x65\x00\x00\x00\x00", enc.code(), "call QWORD PTR [r12 * 2]"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .gs, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x65\xFF\x14\x25\x00\x00\x00\x00", enc.code(), "call gs:0x0"); + + try enc.encode(.call, .{ .op1 = .{ .imm = Immediate.s(0) } }); + try expectEqualHexStrings("\xE8\x00\x00\x00\x00", enc.code(), "call 0x0"); + + try enc.encode(.push, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = 0, + }) } }); + try expectEqualHexStrings("\xFF\x75\x00", enc.code(), "push QWORD PTR [rbp]"); + + try enc.encode(.push, .{ .op1 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x66\xFF\x75\x00", enc.code(), "push QWORD PTR [rbp]"); + + try enc.encode(.pop, .{ .op1 = .{ .mem = Memory.rip(.qword, 0) } }); + try expectEqualHexStrings("\x8F\x05\x00\x00\x00\x00", enc.code(), "pop QWORD PTR [rip]"); + + try enc.encode(.pop, .{ .op1 = .{ .mem = Memory.rip(.word, 0) } }); + try expectEqualHexStrings("\x66\x8F\x05\x00\x00\x00\x00", enc.code(), "pop WORD PTR [rbp]"); + + try enc.encode(.imul, .{ .op1 = .{ .reg = .rax } }); + try expectEqualHexStrings("\x48\xF7\xE8", enc.code(), "imul rax"); + + try enc.encode(.imul, .{ .op1 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x49\xF7\xEC", enc.code(), "imul r12"); +} + +test "lower O encoding" { + var enc = TestEncode{}; + + try enc.encode(.push, .{ .op1 = .{ .reg = .rax } }); + try expectEqualHexStrings("\x50", enc.code(), "push rax"); + + try enc.encode(.push, .{ .op1 = .{ .reg = .r12w } }); + try expectEqualHexStrings("\x66\x41\x54", enc.code(), "push r12w"); + + try enc.encode(.pop, .{ .op1 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x41\x5c", enc.code(), "pop r12"); +} + +test "lower OI encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x1000000000000000) } }); + try expectEqualHexStrings( + "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", + enc.code(), + "movabs rax, 0x1000000000000000", + ); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .imm = Immediate.u(0x1000000000000000) } }); + try expectEqualHexStrings( + "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", + enc.code(), + "movabs r11, 0x1000000000000000", + ); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11d }, .op2 = .{ .imm = Immediate.u(0x10000000) } }); + try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", enc.code(), "mov r11d, 0x10000000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11w }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x66\x41\xBB\x00\x10", enc.code(), "mov r11w, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11b }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x41\xB3\x10", enc.code(), "mov r11b, 0x10"); +} + +test "lower FD/TD encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.moffs(.cs, 0x10) } }); + try expectEqualHexStrings("\x2E\x48\xA1\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs rax, cs:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.moffs(.fs, 0x10) } }); + try expectEqualHexStrings("\x64\xA1\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs eax, fs:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .mem = Memory.moffs(.gs, 0x10) } }); + try expectEqualHexStrings("\x65\x66\xA1\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs ax, gs:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .al }, .op2 = .{ .mem = Memory.moffs(.ds, 0x10) } }); + try expectEqualHexStrings("\xA0\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs al, ds:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.cs, 0x10) }, .op2 = .{ .reg = .rax } }); + try expectEqualHexStrings("\x2E\x48\xA3\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs cs:0x10, rax"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.fs, 0x10) }, .op2 = .{ .reg = .eax } }); + try expectEqualHexStrings("\x64\xA3\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs fs:0x10, eax"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.gs, 0x10) }, .op2 = .{ .reg = .ax } }); + try expectEqualHexStrings("\x65\x66\xA3\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs gs:0x10, ax"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.ds, 0x10) }, .op2 = .{ .reg = .al } }); + try expectEqualHexStrings("\xA2\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs ds:0x10, al"); +} + +test "lower NP encoding" { + var enc = TestEncode{}; + + try enc.encode(.int3, .{}); + try expectEqualHexStrings("\xCC", enc.code(), "int3"); + + try enc.encode(.nop, .{}); + try expectEqualHexStrings("\x90", enc.code(), "nop"); + + try enc.encode(.ret, .{}); + try expectEqualHexStrings("\xC3", enc.code(), "ret"); + + try enc.encode(.syscall, .{}); + try expectEqualHexStrings("\x0f\x05", enc.code(), "syscall"); +} + +fn invalidInstruction(mnemonic: Instruction.Mnemonic, args: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) !void { + const err = Instruction.new(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }); + try testing.expectError(error.InvalidInstruction, err); +} + +test "invalid instruction" { + try invalidInstruction(.call, .{ .op1 = .{ .reg = .eax } }); + try invalidInstruction(.call, .{ .op1 = .{ .reg = .ax } }); + try invalidInstruction(.call, .{ .op1 = .{ .reg = .al } }); + try invalidInstruction(.call, .{ .op1 = .{ .mem = Memory.rip(.dword, 0) } }); + try invalidInstruction(.call, .{ .op1 = .{ .mem = Memory.rip(.word, 0) } }); + try invalidInstruction(.call, .{ .op1 = .{ .mem = Memory.rip(.byte, 0) } }); + try invalidInstruction(.mov, .{ .op1 = .{ .mem = Memory.rip(.word, 0x10) }, .op2 = .{ .reg = .r12 } }); + try invalidInstruction(.lea, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .rbx } }); + try invalidInstruction(.lea, .{ .op1 = .{ .reg = .al }, .op2 = .{ .mem = Memory.rip(.byte, 0) } }); + try invalidInstruction(.pop, .{ .op1 = .{ .reg = .r12b } }); + try invalidInstruction(.pop, .{ .op1 = .{ .reg = .r12d } }); + try invalidInstruction(.push, .{ .op1 = .{ .reg = .r12b } }); + try invalidInstruction(.push, .{ .op1 = .{ .reg = .r12d } }); + try invalidInstruction(.push, .{ .op1 = .{ .imm = Immediate.u(0x1000000000000000) } }); +} + +fn cannotEncode(mnemonic: Instruction.Mnemonic, args: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) !void { + try testing.expectError(error.CannotEncode, Instruction.new(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + })); +} + +test "cannot encode" { + try cannotEncode(.@"test", .{ + .op1 = .{ .mem = Memory.sib(.byte, .{ .base = .r12, .disp = 0 }) }, + .op2 = .{ .reg = .ah }, + }); + try cannotEncode(.@"test", .{ + .op1 = .{ .reg = .r11b }, + .op2 = .{ .reg = .bh }, + }); + try cannotEncode(.mov, .{ + .op1 = .{ .reg = .sil }, + .op2 = .{ .reg = .ah }, + }); +} + +const Assembler = struct { + it: Tokenizer, + + const Tokenizer = struct { + input: []const u8, + pos: usize = 0, + + const Error = error{InvalidToken}; + + const Token = struct { + id: Id, + start: usize, + end: usize, + + const Id = enum { + eof, + + space, + new_line, + + colon, + comma, + open_br, + close_br, + plus, + minus, + star, + + string, + numeral, + }; + }; + + const Iterator = struct {}; + + fn next(it: *Tokenizer) !Token { + var result = Token{ + .id = .eof, + .start = it.pos, + .end = it.pos, + }; + + var state: enum { + start, + space, + new_line, + string, + numeral, + numeral_hex, + } = .start; + + while (it.pos < it.input.len) : (it.pos += 1) { + const ch = it.input[it.pos]; + switch (state) { + .start => switch (ch) { + ',' => { + result.id = .comma; + it.pos += 1; + break; + }, + ':' => { + result.id = .colon; + it.pos += 1; + break; + }, + '[' => { + result.id = .open_br; + it.pos += 1; + break; + }, + ']' => { + result.id = .close_br; + it.pos += 1; + break; + }, + '+' => { + result.id = .plus; + it.pos += 1; + break; + }, + '-' => { + result.id = .minus; + it.pos += 1; + break; + }, + '*' => { + result.id = .star; + it.pos += 1; + break; + }, + ' ', '\t' => state = .space, + '\n', '\r' => state = .new_line, + 'a'...'z', 'A'...'Z' => state = .string, + '0'...'9' => state = .numeral, + else => return error.InvalidToken, + }, + + .space => switch (ch) { + ' ', '\t' => {}, + else => { + result.id = .space; + break; + }, + }, + + .new_line => switch (ch) { + '\n', '\r', ' ', '\t' => {}, + else => { + result.id = .new_line; + break; + }, + }, + + .string => switch (ch) { + 'a'...'z', 'A'...'Z', '0'...'9' => {}, + else => { + result.id = .string; + break; + }, + }, + + .numeral => switch (ch) { + 'x' => state = .numeral_hex, + '0'...'9' => {}, + else => { + result.id = .numeral; + break; + }, + }, + + .numeral_hex => switch (ch) { + 'a'...'f' => {}, + '0'...'9' => {}, + else => { + result.id = .numeral; + break; + }, + }, + } + } + + if (it.pos >= it.input.len) { + switch (state) { + .string => result.id = .string, + .numeral, .numeral_hex => result.id = .numeral, + else => {}, + } + } + + result.end = it.pos; + return result; + } + + fn seekTo(it: *Tokenizer, pos: usize) void { + it.pos = pos; + } + }; + + pub fn init(input: []const u8) Assembler { + return .{ + .it = Tokenizer{ .input = input }, + }; + } + + pub fn assemble(as: *Assembler, writer: anytype) !void { + while (try as.next()) |parsed_inst| { + const inst = try Instruction.new(parsed_inst.mnemonic, .{ + .op1 = parsed_inst.ops[0], + .op2 = parsed_inst.ops[1], + .op3 = parsed_inst.ops[2], + .op4 = parsed_inst.ops[3], + }); + try inst.encode(writer); + } + } + + const ParseResult = struct { + mnemonic: Instruction.Mnemonic, + ops: [4]Instruction.Operand, + }; + + const ParseError = error{ + UnexpectedToken, + InvalidMnemonic, + InvalidOperand, + InvalidRegister, + InvalidPtrSize, + InvalidMemoryOperand, + InvalidScaleIndex, + } || Tokenizer.Error || std.fmt.ParseIntError; + + fn next(as: *Assembler) ParseError!?ParseResult { + try as.skip(2, .{ .space, .new_line }); + const mnemonic_tok = as.expect(.string) catch |err| switch (err) { + error.UnexpectedToken => return if (try as.peek() == .eof) null else err, + else => return err, + }; + const mnemonic = mnemonicFromString(as.source(mnemonic_tok)) orelse + return error.InvalidMnemonic; + try as.skip(1, .{.space}); + + const rules = .{ + .{}, + .{.register}, + .{.memory}, + .{.immediate}, + .{ .register, .register }, + .{ .register, .memory }, + .{ .memory, .register }, + .{ .register, .immediate }, + .{ .memory, .immediate }, + .{ .register, .register, .immediate }, + .{ .register, .memory, .immediate }, + }; + + const pos = as.it.pos; + inline for (rules) |rule| { + var ops = [4]Instruction.Operand{ .none, .none, .none, .none }; + if (as.parseOperandRule(rule, &ops)) { + return .{ + .mnemonic = mnemonic, + .ops = ops, + }; + } else |_| { + as.it.seekTo(pos); + } + } + + return error.InvalidOperand; + } + + fn source(as: *Assembler, token: Tokenizer.Token) []const u8 { + return as.it.input[token.start..token.end]; + } + + fn peek(as: *Assembler) Tokenizer.Error!Tokenizer.Token.Id { + const pos = as.it.pos; + const next_tok = try as.it.next(); + const id = next_tok.id; + as.it.seekTo(pos); + return id; + } + + fn expect(as: *Assembler, id: Tokenizer.Token.Id) ParseError!Tokenizer.Token { + const next_tok_id = try as.peek(); + if (next_tok_id == id) return as.it.next(); + return error.UnexpectedToken; + } + + fn skip(as: *Assembler, comptime num: comptime_int, tok_ids: [num]Tokenizer.Token.Id) Tokenizer.Error!void { + outer: while (true) { + const pos = as.it.pos; + const next_tok = try as.it.next(); + inline for (tok_ids) |tok_id| { + if (next_tok.id == tok_id) continue :outer; + } + as.it.seekTo(pos); + break; + } + } + + fn mnemonicFromString(bytes: []const u8) ?Instruction.Mnemonic { + const ti = @typeInfo(Instruction.Mnemonic).Enum; + inline for (ti.fields) |field| { + if (std.mem.eql(u8, bytes, field.name)) { + return @field(Instruction.Mnemonic, field.name); + } + } + return null; + } + + fn parseOperandRule(as: *Assembler, rule: anytype, ops: *[4]Instruction.Operand) ParseError!void { + inline for (rule, 0..) |cond, i| { + comptime assert(i < 4); + if (i > 0) { + _ = try as.expect(.comma); + try as.skip(1, .{.space}); + } + if (@typeInfo(@TypeOf(cond)) != .EnumLiteral) { + @compileError("invalid condition in the rule: " ++ @typeName(@TypeOf(cond))); + } + switch (cond) { + .register => { + const reg_tok = try as.expect(.string); + const reg = registerFromString(as.source(reg_tok)) orelse + return error.InvalidOperand; + ops[i] = .{ .reg = reg }; + }, + .memory => { + const mem = try as.parseMemory(); + ops[i] = .{ .mem = mem }; + }, + .immediate => { + const is_neg = if (as.expect(.minus)) |_| true else |_| false; + const imm_tok = try as.expect(.numeral); + const imm: Immediate = if (is_neg) blk: { + const imm = try std.fmt.parseInt(i32, as.source(imm_tok), 0); + break :blk .{ .signed = imm * -1 }; + } else .{ .unsigned = try std.fmt.parseInt(u64, as.source(imm_tok), 0) }; + ops[i] = .{ .imm = imm }; + }, + else => @compileError("unhandled enum literal " ++ @tagName(cond)), + } + try as.skip(1, .{.space}); + } + + try as.skip(1, .{.space}); + const tok = try as.it.next(); + switch (tok.id) { + .new_line, .eof => {}, + else => return error.InvalidOperand, + } + } + + fn registerFromString(bytes: []const u8) ?Register { + const ti = @typeInfo(Register).Enum; + inline for (ti.fields) |field| { + if (std.mem.eql(u8, bytes, field.name)) { + return @field(Register, field.name); + } + } + return null; + } + + fn parseMemory(as: *Assembler) ParseError!Memory { + const ptr_size: ?Memory.PtrSize = blk: { + const pos = as.it.pos; + const ptr_size = as.parsePtrSize() catch |err| switch (err) { + error.UnexpectedToken => { + as.it.seekTo(pos); + break :blk null; + }, + else => return err, + }; + break :blk ptr_size; + }; + + try as.skip(1, .{.space}); + + // Supported rules and orderings. + const rules = .{ + .{ .open_br, .base, .close_br }, // [ base ] + .{ .open_br, .base, .plus, .disp, .close_br }, // [ base + disp ] + .{ .open_br, .base, .minus, .disp, .close_br }, // [ base - disp ] + .{ .open_br, .disp, .plus, .base, .close_br }, // [ disp + base ] + .{ .open_br, .base, .plus, .index, .close_br }, // [ base + index ] + .{ .open_br, .base, .plus, .index, .star, .scale, .close_br }, // [ base + index * scale ] + .{ .open_br, .index, .star, .scale, .plus, .base, .close_br }, // [ index * scale + base ] + .{ .open_br, .base, .plus, .index, .star, .scale, .plus, .disp, .close_br }, // [ base + index * scale + disp ] + .{ .open_br, .base, .plus, .index, .star, .scale, .minus, .disp, .close_br }, // [ base + index * scale - disp ] + .{ .open_br, .index, .star, .scale, .plus, .base, .plus, .disp, .close_br }, // [ index * scale + base + disp ] + .{ .open_br, .index, .star, .scale, .plus, .base, .minus, .disp, .close_br }, // [ index * scale + base - disp ] + .{ .open_br, .disp, .plus, .index, .star, .scale, .plus, .base, .close_br }, // [ disp + index * scale + base ] + .{ .open_br, .disp, .plus, .base, .plus, .index, .star, .scale, .close_br }, // [ disp + base + index * scale ] + .{ .open_br, .base, .plus, .disp, .plus, .index, .star, .scale, .close_br }, // [ base + disp + index * scale ] + .{ .open_br, .base, .minus, .disp, .plus, .index, .star, .scale, .close_br }, // [ base - disp + index * scale ] + .{ .open_br, .base, .plus, .disp, .plus, .scale, .star, .index, .close_br }, // [ base + disp + scale * index ] + .{ .open_br, .base, .minus, .disp, .plus, .scale, .star, .index, .close_br }, // [ base - disp + scale * index ] + .{ .open_br, .rip, .plus, .disp, .close_br }, // [ rip + disp ] + .{ .open_br, .rip, .minus, .disp, .close_br }, // [ rig - disp ] + .{ .base, .colon, .disp }, // seg:disp + }; + + const pos = as.it.pos; + inline for (rules) |rule| { + if (as.parseMemoryRule(rule)) |res| { + if (res.rip) { + if (res.base != null or res.scale_index != null or res.offset != null) + return error.InvalidMemoryOperand; + return Memory.rip(ptr_size orelse .qword, res.disp orelse 0); + } + if (res.base) |base| { + if (res.rip) + return error.InvalidMemoryOperand; + if (res.offset) |offset| { + if (res.scale_index != null or res.disp != null) + return error.InvalidMemoryOperand; + return Memory.moffs(base, offset); + } + return Memory.sib(ptr_size orelse .qword, .{ + .base = base, + .scale_index = res.scale_index, + .disp = res.disp orelse 0, + }); + } + return error.InvalidMemoryOperand; + } else |_| { + as.it.seekTo(pos); + } + } + + return error.InvalidOperand; + } + + const MemoryParseResult = struct { + rip: bool = false, + base: ?Register = null, + scale_index: ?Memory.ScaleIndex = null, + disp: ?i32 = null, + offset: ?u64 = null, + }; + + fn parseMemoryRule(as: *Assembler, rule: anytype) ParseError!MemoryParseResult { + var res: MemoryParseResult = .{}; + inline for (rule, 0..) |cond, i| { + if (@typeInfo(@TypeOf(cond)) != .EnumLiteral) { + @compileError("unsupported condition type in the rule: " ++ @typeName(@TypeOf(cond))); + } + switch (cond) { + .open_br, .close_br, .plus, .minus, .star, .colon => { + _ = try as.expect(cond); + }, + .base => { + const tok = try as.expect(.string); + res.base = registerFromString(as.source(tok)) orelse return error.InvalidMemoryOperand; + }, + .rip => { + const tok = try as.expect(.string); + if (!std.mem.eql(u8, as.source(tok), "rip")) return error.InvalidMemoryOperand; + res.rip = true; + }, + .index => { + const tok = try as.expect(.string); + const index = registerFromString(as.source(tok)) orelse + return error.InvalidMemoryOperand; + if (res.scale_index) |*si| { + si.index = index; + } else { + res.scale_index = .{ .scale = 1, .index = index }; + } + }, + .scale => { + const tok = try as.expect(.numeral); + const scale = try std.fmt.parseInt(u2, as.source(tok), 0); + if (res.scale_index) |*si| { + si.scale = scale; + } else { + res.scale_index = .{ .scale = scale, .index = undefined }; + } + }, + .disp => { + const tok = try as.expect(.numeral); + const is_neg = blk: { + if (i > 0) { + if (rule[i - 1] == .minus) break :blk true; + } + break :blk false; + }; + if (std.fmt.parseInt(i32, as.source(tok), 0)) |disp| { + res.disp = if (is_neg) -1 * disp else disp; + } else |err| switch (err) { + error.Overflow => { + if (is_neg) return err; + if (res.base) |base| { + if (base.class() != .segment) return err; + } + const offset = try std.fmt.parseInt(u64, as.source(tok), 0); + res.offset = offset; + }, + else => return err, + } + }, + else => @compileError("unhandled operand output type: " ++ @tagName(cond)), + } + try as.skip(1, .{.space}); + } + return res; + } + + fn parsePtrSize(as: *Assembler) ParseError!Memory.PtrSize { + const size = try as.expect(.string); + try as.skip(1, .{.space}); + const ptr = try as.expect(.string); + + const size_raw = as.source(size); + const ptr_raw = as.source(ptr); + const len = size_raw.len + ptr_raw.len + 1; + var buf: ["qword ptr".len]u8 = undefined; + if (len > buf.len) return error.InvalidPtrSize; + + for (size_raw, 0..) |c, i| { + buf[i] = std.ascii.toLower(c); + } + buf[size_raw.len] = ' '; + for (ptr_raw, 0..) |c, i| { + buf[size_raw.len + i + 1] = std.ascii.toLower(c); + } + + const slice = buf[0..len]; + if (std.mem.eql(u8, slice, "qword ptr")) return .qword; + if (std.mem.eql(u8, slice, "dword ptr")) return .dword; + if (std.mem.eql(u8, slice, "word ptr")) return .word; + if (std.mem.eql(u8, slice, "byte ptr")) return .byte; + if (std.mem.eql(u8, slice, "tbyte ptr")) return .tbyte; + return error.InvalidPtrSize; + } +}; + +test "assemble" { + const input = + \\int3 + \\mov rax, rbx + \\mov qword ptr [rbp], rax + \\mov qword ptr [rbp - 16], rax + \\mov qword ptr [16 + rbp], rax + \\mov rax, 0x10 + \\mov byte ptr [rbp - 0x10], 0x10 + \\mov word ptr [rbp + r12], r11w + \\mov word ptr [rbp + r12 * 2], r11w + \\mov word ptr [rbp + r12 * 2 - 16], r11w + \\mov dword ptr [rip - 16], r12d + \\mov rax, fs:0x0 + \\mov rax, gs:0x1000000000000000 + \\movzx r12, al + \\imul r12, qword ptr [rbp - 16], 6 + \\jmp 0x0 + \\jc 0x0 + \\jb 0x0 + \\sal rax, 1 + \\sal rax, 63 + \\shl rax, 63 + \\sar rax, 63 + \\shr rax, 63 + \\test byte ptr [rbp - 16], r12b + \\sal r12, cl + \\mul qword ptr [rip - 16] + \\div r12 + \\idiv byte ptr [rbp - 16] + \\cwde + \\cbw + \\cdqe + \\test byte ptr [rbp], ah + \\test byte ptr [r12], spl + \\cdq + \\cwd + \\cqo + \\test bl, 0x1 + \\mov rbx,0x8000000000000000 + \\movss xmm0, dword ptr [rbp] + \\movss xmm0, xmm1 + \\movss dword ptr [rbp - 16 + rax * 2], xmm7 + \\movss dword ptr [rbp - 16 + rax * 2], xmm8 + \\movss xmm15, xmm9 + \\movsd xmm8, qword ptr [rbp - 16] + \\movsd qword ptr [rbp - 8], xmm0 + \\movq xmm8, qword ptr [rbp - 16] + \\movq qword ptr [rbp - 16], xmm8 + \\ucomisd xmm0, qword ptr [rbp - 16] + \\fisttp qword ptr [rbp - 16] + \\fisttp word ptr [rip + 32] + \\fisttp dword ptr [rax] + \\fld tbyte ptr [rbp] + \\fld dword ptr [rbp] + \\xor bl, 0xff + \\ud2 + \\add rsp, -1 + \\add rsp, 0xff + \\mov sil, byte ptr [rax + rcx * 1] + \\ + ; + + // zig fmt: off + const expected = &[_]u8{ + 0xCC, + 0x48, 0x89, 0xD8, + 0x48, 0x89, 0x45, 0x00, + 0x48, 0x89, 0x45, 0xF0, + 0x48, 0x89, 0x45, 0x10, + 0x48, 0xC7, 0xC0, 0x10, 0x00, 0x00, 0x00, + 0xC6, 0x45, 0xF0, 0x10, + 0x66, 0x46, 0x89, 0x5C, 0x25, 0x00, + 0x66, 0x46, 0x89, 0x5C, 0x65, 0x00, + 0x66, 0x46, 0x89, 0x5C, 0x65, 0xF0, + 0x44, 0x89, 0x25, 0xF0, 0xFF, 0xFF, 0xFF, + 0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x65, 0x48, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x4C, 0x0F, 0xB6, 0xE0, + 0x4C, 0x6B, 0x65, 0xF0, 0x06, + 0xE9, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x82, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x82, 0x00, 0x00, 0x00, 0x00, + 0x48, 0xD1, 0xE0, + 0x48, 0xC1, 0xE0, 0x3F, + 0x48, 0xC1, 0xE0, 0x3F, + 0x48, 0xC1, 0xF8, 0x3F, + 0x48, 0xC1, 0xE8, 0x3F, + 0x44, 0x84, 0x65, 0xF0, + 0x49, 0xD3, 0xE4, + 0x48, 0xF7, 0x25, 0xF0, 0xFF, 0xFF, 0xFF, + 0x49, 0xF7, 0xF4, + 0xF6, 0x7D, 0xF0, + 0x98, + 0x66, 0x98, + 0x48, 0x98, + 0x84, 0x65, 0x00, + 0x41, 0x84, 0x24, 0x24, + 0x99, + 0x66, 0x99, + 0x48, 0x99, + 0xF6, 0xC3, 0x01, + 0x48, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xF3, 0x0F, 0x10, 0x45, 0x00, + 0xF3, 0x0F, 0x10, 0xC1, + 0xF3, 0x0F, 0x11, 0x7C, 0x45, 0xF0, + 0xF3, 0x44, 0x0F, 0x11, 0x44, 0x45, 0xF0, + 0xF3, 0x45, 0x0F, 0x10, 0xF9, + 0xF2, 0x44, 0x0F, 0x10, 0x45, 0xF0, + 0xF2, 0x0F, 0x11, 0x45, 0xF8, + 0xF3, 0x44, 0x0F, 0x7E, 0x45, 0xF0, + 0x66, 0x44, 0x0F, 0xD6, 0x45, 0xF0, + 0x66, 0x0F, 0x2E, 0x45, 0xF0, + 0xDD, 0x4D, 0xF0, + 0xDF, 0x0D, 0x20, 0x00, 0x00, 0x00, + 0xDB, 0x08, + 0xDB, 0x6D, 0x00, + 0xD9, 0x45, 0x00, + 0x80, 0xF3, 0xFF, + 0x0F, 0x0B, + 0x48, 0x83, 0xC4, 0xFF, + 0x48, 0x81, 0xC4, 0xFF, 0x00, 0x00, 0x00, + 0x40, 0x8A, 0x34, 0x08, + }; + // zig fmt: on + + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(expected, output.items, input); +} + +test "assemble - Jcc" { + const mnemonics = [_]struct { Instruction.Mnemonic, u8 }{ + .{ .ja, 0x87 }, + .{ .jae, 0x83 }, + .{ .jb, 0x82 }, + .{ .jbe, 0x86 }, + .{ .jc, 0x82 }, + .{ .je, 0x84 }, + .{ .jg, 0x8f }, + .{ .jge, 0x8d }, + .{ .jl, 0x8c }, + .{ .jle, 0x8e }, + .{ .jna, 0x86 }, + .{ .jnae, 0x82 }, + .{ .jnb, 0x83 }, + .{ .jnbe, 0x87 }, + .{ .jnc, 0x83 }, + .{ .jne, 0x85 }, + .{ .jng, 0x8e }, + .{ .jnge, 0x8c }, + .{ .jnl, 0x8d }, + .{ .jnle, 0x8f }, + .{ .jno, 0x81 }, + .{ .jnp, 0x8b }, + .{ .jns, 0x89 }, + .{ .jnz, 0x85 }, + .{ .jo, 0x80 }, + .{ .jp, 0x8a }, + .{ .jpe, 0x8a }, + .{ .jpo, 0x8b }, + .{ .js, 0x88 }, + .{ .jz, 0x84 }, + }; + + inline for (&mnemonics) |mnemonic| { + const input = @tagName(mnemonic[0]) ++ " 0x0"; + const expected = [_]u8{ 0x0f, mnemonic[1], 0x0, 0x0, 0x0, 0x0 }; + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(&expected, output.items, input); + } +} + +test "assemble - SETcc" { + const mnemonics = [_]struct { Instruction.Mnemonic, u8 }{ + .{ .seta, 0x97 }, + .{ .setae, 0x93 }, + .{ .setb, 0x92 }, + .{ .setbe, 0x96 }, + .{ .setc, 0x92 }, + .{ .sete, 0x94 }, + .{ .setg, 0x9f }, + .{ .setge, 0x9d }, + .{ .setl, 0x9c }, + .{ .setle, 0x9e }, + .{ .setna, 0x96 }, + .{ .setnae, 0x92 }, + .{ .setnb, 0x93 }, + .{ .setnbe, 0x97 }, + .{ .setnc, 0x93 }, + .{ .setne, 0x95 }, + .{ .setng, 0x9e }, + .{ .setnge, 0x9c }, + .{ .setnl, 0x9d }, + .{ .setnle, 0x9f }, + .{ .setno, 0x91 }, + .{ .setnp, 0x9b }, + .{ .setns, 0x99 }, + .{ .setnz, 0x95 }, + .{ .seto, 0x90 }, + .{ .setp, 0x9a }, + .{ .setpe, 0x9a }, + .{ .setpo, 0x9b }, + .{ .sets, 0x98 }, + .{ .setz, 0x94 }, + }; + + inline for (&mnemonics) |mnemonic| { + const input = @tagName(mnemonic[0]) ++ " al"; + const expected = [_]u8{ 0x0f, mnemonic[1], 0xC0 }; + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(&expected, output.items, input); + } +} + +test "assemble - CMOVcc" { + const mnemonics = [_]struct { Instruction.Mnemonic, u8 }{ + .{ .cmova, 0x47 }, + .{ .cmovae, 0x43 }, + .{ .cmovb, 0x42 }, + .{ .cmovbe, 0x46 }, + .{ .cmovc, 0x42 }, + .{ .cmove, 0x44 }, + .{ .cmovg, 0x4f }, + .{ .cmovge, 0x4d }, + .{ .cmovl, 0x4c }, + .{ .cmovle, 0x4e }, + .{ .cmovna, 0x46 }, + .{ .cmovnae, 0x42 }, + .{ .cmovnb, 0x43 }, + .{ .cmovnbe, 0x47 }, + .{ .cmovnc, 0x43 }, + .{ .cmovne, 0x45 }, + .{ .cmovng, 0x4e }, + .{ .cmovnge, 0x4c }, + .{ .cmovnl, 0x4d }, + .{ .cmovnle, 0x4f }, + .{ .cmovno, 0x41 }, + .{ .cmovnp, 0x4b }, + .{ .cmovns, 0x49 }, + .{ .cmovnz, 0x45 }, + .{ .cmovo, 0x40 }, + .{ .cmovp, 0x4a }, + .{ .cmovpe, 0x4a }, + .{ .cmovpo, 0x4b }, + .{ .cmovs, 0x48 }, + .{ .cmovz, 0x44 }, + }; + + inline for (&mnemonics) |mnemonic| { + const input = @tagName(mnemonic[0]) ++ " rax, rbx"; + const expected = [_]u8{ 0x48, 0x0f, mnemonic[1], 0xC3 }; + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(&expected, output.items, input); + } +} From 955e394792af69fc8c795802c0165e5c86f5ea73 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 08:47:23 +0100 Subject: [PATCH 106/294] x86_64: fix 32bit build issues in the encoder --- src/arch/x86_64/Encoding.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index c6a8d044c3..7b00679b92 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -105,6 +105,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { } if (count == 0) return null; + if (count == 1) return candidates[0]; const EncodingLength = struct { fn estimate(encoding: Encoding, params: struct { @@ -112,7 +113,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { op2: Instruction.Operand, op3: Instruction.Operand, op4: Instruction.Operand, - }) !usize { + }) usize { var inst = Instruction{ .op1 = params.op1, .op2 = params.op2, @@ -122,7 +123,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { }; var cwriter = std.io.countingWriter(std.io.null_writer); inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM. - return cwriter.bytes_written; + return @intCast(usize, cwriter.bytes_written); } }; @@ -138,7 +139,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { else => {}, } - const len = try EncodingLength.estimate(candidate, .{ + const len = EncodingLength.estimate(candidate, .{ .op1 = args.op1, .op2 = args.op2, .op3 = args.op3, From a097779b611577b75475336ee282615984f77edf Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 11 Mar 2023 14:52:38 +0100 Subject: [PATCH 107/294] std: Add ArrayList.insertAssumeCapacity() Also test and document that inserting at list.items.len is allowed. --- lib/std/array_list.zig | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 13aad53019..fb11e2e755 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -141,11 +141,21 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { return cloned; } - /// Insert `item` at index `n` by moving `list[n .. list.len]` to make room. + /// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room. + /// If `n` is equal to the length of the list this operation is equivalent to append. /// This operation is O(N). /// Invalidates pointers if additional memory is needed. pub fn insert(self: *Self, n: usize, item: T) Allocator.Error!void { try self.ensureUnusedCapacity(1); + self.insertAssumeCapacity(n, item); + } + + /// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room. + /// If `n` is equal to the length of the list this operation is equivalent to append. + /// This operation is O(N). + /// Asserts that there is enough capacity for the new item. + pub fn insertAssumeCapacity(self: *Self, n: usize, item: T) void { + assert(self.items.len < self.capacity); self.items.len += 1; mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. self.items.len - 1]); @@ -609,12 +619,21 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ return cloned; } - /// Insert `item` at index `n`. Moves `list[n .. list.len]` - /// to higher indices to make room. + /// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room. + /// If `n` is equal to the length of the list this operation is equivalent to append. /// This operation is O(N). /// Invalidates pointers if additional memory is needed. pub fn insert(self: *Self, allocator: Allocator, n: usize, item: T) Allocator.Error!void { try self.ensureUnusedCapacity(allocator, 1); + self.insertAssumeCapacity(n, item); + } + + /// Insert `item` at index `n`. Moves `list[n .. list.len]` to higher indices to make room. + /// If `n` is equal to the length of the list this operation is equivalent to append. + /// This operation is O(N). + /// Asserts that there is enough capacity for the new item. + pub fn insertAssumeCapacity(self: *Self, n: usize, item: T) void { + assert(self.items.len < self.capacity); self.items.len += 1; mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. self.items.len - 1]); @@ -1309,9 +1328,9 @@ test "std.ArrayList/ArrayListUnmanaged.insert" { var list = ArrayList(i32).init(a); defer list.deinit(); - try list.append(1); + try list.insert(0, 1); try list.append(2); - try list.append(3); + try list.insert(2, 3); try list.insert(0, 5); try testing.expect(list.items[0] == 5); try testing.expect(list.items[1] == 1); @@ -1322,9 +1341,9 @@ test "std.ArrayList/ArrayListUnmanaged.insert" { var list = ArrayListUnmanaged(i32){}; defer list.deinit(a); - try list.append(a, 1); + try list.insert(a, 0, 1); try list.append(a, 2); - try list.append(a, 3); + try list.insert(a, 2, 3); try list.insert(a, 0, 5); try testing.expect(list.items[0] == 5); try testing.expect(list.items[1] == 1); From 948926c513befd95dc1ff90fe05329245f1c81db Mon Sep 17 00:00:00 2001 From: mlugg Date: Sat, 11 Mar 2023 14:26:56 +0000 Subject: [PATCH 108/294] Sema: improve error message when calling non-member function as method Resolves: #14880 --- src/Sema.zig | 21 +++++++++++++------ ...hod_call_with_first_arg_type_primitive.zig | 3 ++- ...ll_with_first_arg_type_wrong_container.zig | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 41685890f7..6c24746da8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -23605,10 +23605,13 @@ fn fieldCallBind( } // If we get here, we need to look for a decl in the struct type instead. - switch (concrete_ty.zigTypeTag()) { - .Struct, .Opaque, .Union, .Enum => { + const found_decl = switch (concrete_ty.zigTypeTag()) { + .Struct, .Opaque, .Union, .Enum => found_decl: { if (concrete_ty.getNamespace()) |namespace| { - if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { + if (try sema.namespaceLookup(block, src, namespace, field_name)) |decl_idx| { + try sema.addReferencedBy(block, src, decl_idx); + const inst = try sema.analyzeDeclRef(decl_idx); + const decl_val = try sema.analyzeLoad(block, src, inst, src); const decl_type = sema.typeOf(decl_val); if (decl_type.zigTypeTag() == .Fn and @@ -23625,7 +23628,7 @@ fn fieldCallBind( first_param_type.ptrSize() == .C) and first_param_type.childType().eql(concrete_ty, sema.mod))) { - // zig fmt: on + // zig fmt: on // TODO: bound fn calls on rvalues should probably // generate a by-value argument somehow. const ty = Type.Tag.bound_fn.init(); @@ -23664,16 +23667,22 @@ fn fieldCallBind( return sema.addConstant(ty, value); } } + break :found_decl decl_idx; } } + break :found_decl null; }, - else => {}, - } + else => null, + }; const msg = msg: { const msg = try sema.errMsg(block, src, "no field or member function named '{s}' in '{}'", .{ field_name, concrete_ty.fmt(sema.mod) }); errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, concrete_ty); + if (found_decl) |decl_idx| { + const decl = sema.mod.declPtr(decl_idx); + try sema.mod.errNoteNonLazy(decl.srcLoc(), msg, "'{s}' is not a member function", .{field_name}); + } break :msg msg; }; return sema.failWithOwnedErrorMsg(msg); diff --git a/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig b/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig index 1cecac6fac..2a5167adf2 100644 --- a/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig +++ b/test/cases/compile_errors/method_call_with_first_arg_type_primitive.zig @@ -2,7 +2,7 @@ const Foo = struct { x: i32, fn init(x: i32) Foo { - return Foo { + return Foo{ .x = x, }; } @@ -20,3 +20,4 @@ export fn f() void { // // :14:9: error: no field or member function named 'init' in 'tmp.Foo' // :1:13: note: struct declared here +// :4:5: note: 'init' is not a member function diff --git a/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig b/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig index ad481a6158..0653bda3ea 100644 --- a/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig +++ b/test/cases/compile_errors/method_call_with_first_arg_type_wrong_container.zig @@ -29,3 +29,4 @@ export fn foo() void { // // :23:6: error: no field or member function named 'init' in 'tmp.List' // :1:18: note: struct declared here +// :5:9: note: 'init' is not a member function From c93e0d86187cb589d6726acd36f741f3d87a96be Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 9 Mar 2023 21:34:06 +0000 Subject: [PATCH 109/294] Sema: @extern fixes * There was an edge case where the arena could be destroyed twice on error: once from the arena itself and once from the decl destruction. * The type of the created decl was incorrect (it should have been the pointer child type), but it's not required anyway, so it's now just initialized to anyopaque (which more accurately reflects what's actually at that memory, since e.g. [*]T may correspond to nothing). * A runtime bitcast of the pointer was performed, meaning @extern didn't work at comptime. This is unnecessary: the decl_ref can just be initialized with the correct pointer type. --- src/Sema.zig | 59 +++++++++++++++--------------- test/standalone.zig | 1 + test/standalone/extern/build.zig | 20 ++++++++++ test/standalone/extern/exports.zig | 12 ++++++ test/standalone/extern/main.zig | 21 +++++++++++ 5 files changed, 84 insertions(+), 29 deletions(-) create mode 100644 test/standalone/extern/build.zig create mode 100644 test/standalone/extern/exports.zig create mode 100644 test/standalone/extern/main.zig diff --git a/src/Sema.zig b/src/Sema.zig index 6c24746da8..aef07b7988 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -22287,7 +22287,6 @@ fn zirBuiltinExtern( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; - const src = LazySrcLoc.nodeOffset(extra.node); const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; @@ -22315,39 +22314,41 @@ fn zirBuiltinExtern( const new_decl = sema.mod.declPtr(new_decl_index); new_decl.name = try sema.gpa.dupeZ(u8, options.name); - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - const new_decl_arena_allocator = new_decl_arena.allocator(); + { + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); - const new_var = try new_decl_arena_allocator.create(Module.Var); - errdefer new_decl_arena_allocator.destroy(new_var); + const new_var = try new_decl_arena_allocator.create(Module.Var); + new_var.* = .{ + .owner_decl = sema.owner_decl_index, + .init = Value.initTag(.unreachable_value), + .is_extern = true, + .is_mutable = false, + .is_threadlocal = options.is_thread_local, + .is_weak_linkage = options.linkage == .Weak, + .lib_name = null, + }; - new_var.* = .{ - .owner_decl = sema.owner_decl_index, - .init = Value.initTag(.unreachable_value), - .is_extern = true, - .is_mutable = false, - .is_threadlocal = options.is_thread_local, - .is_weak_linkage = options.linkage == .Weak, - .lib_name = null, - }; + new_decl.src_line = sema.owner_decl.src_line; + // We only access this decl through the decl_ref with the correct type created + // below, so this type doesn't matter + new_decl.ty = Type.Tag.init(.anyopaque); + new_decl.val = try Value.Tag.variable.create(new_decl_arena_allocator, new_var); + new_decl.@"align" = 0; + new_decl.@"linksection" = null; + new_decl.has_tv = true; + new_decl.analysis = .complete; + new_decl.generation = sema.mod.generation; - new_decl.src_line = sema.owner_decl.src_line; - new_decl.ty = try ty.copy(new_decl_arena_allocator); - new_decl.val = try Value.Tag.variable.create(new_decl_arena_allocator, new_var); - new_decl.@"align" = 0; - new_decl.@"linksection" = null; - new_decl.has_tv = true; - new_decl.analysis = .complete; - new_decl.generation = sema.mod.generation; + try new_decl.finalizeNewArena(&new_decl_arena); + } - const arena_state = try new_decl_arena_allocator.create(std.heap.ArenaAllocator.State); - arena_state.* = new_decl_arena.state; - new_decl.value_arena = arena_state; + try sema.mod.declareDeclDependency(sema.owner_decl_index, new_decl_index); + try sema.ensureDeclAnalyzed(new_decl_index); - const ref = try sema.analyzeDeclRef(new_decl_index); - try sema.requireRuntimeBlock(block, src, null); - return block.addBitCast(ty, ref); + const ref = try Value.Tag.decl_ref.create(sema.arena, new_decl_index); + return sema.addConstant(ty, ref); } fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void { diff --git a/test/standalone.zig b/test/standalone.zig index 965139235c..7aa4d81f97 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -107,6 +107,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{}); cases.addBuildFile("test/standalone/issue_12588/build.zig", .{}); cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{}); + cases.addBuildFile("test/standalone/extern/build.zig", .{}); cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{}); cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{}); diff --git a/test/standalone/extern/build.zig b/test/standalone/extern/build.zig new file mode 100644 index 0000000000..8a44a6ca8f --- /dev/null +++ b/test/standalone/extern/build.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + + const obj = b.addObject(.{ + .name = "exports", + .root_source_file = .{ .path = "exports.zig" }, + .target = .{}, + .optimize = optimize, + }); + const main = b.addTest(.{ + .root_source_file = .{ .path = "main.zig" }, + .optimize = optimize, + }); + main.addObject(obj); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&main.step); +} diff --git a/test/standalone/extern/exports.zig b/test/standalone/extern/exports.zig new file mode 100644 index 0000000000..93351c4581 --- /dev/null +++ b/test/standalone/extern/exports.zig @@ -0,0 +1,12 @@ +var hidden: u32 = 0; +export fn updateHidden(val: u32) void { + hidden = val; +} +export fn getHidden() u32 { + return hidden; +} + +const T = extern struct { x: u32 }; + +export var mut_val: f64 = 1.23; +export const const_val: T = .{ .x = 42 }; diff --git a/test/standalone/extern/main.zig b/test/standalone/extern/main.zig new file mode 100644 index 0000000000..4cbed184c3 --- /dev/null +++ b/test/standalone/extern/main.zig @@ -0,0 +1,21 @@ +const assert = @import("std").debug.assert; + +const updateHidden = @extern(*const fn (u32) callconv(.C) void, .{ .name = "updateHidden" }); +const getHidden = @extern(*const fn () callconv(.C) u32, .{ .name = "getHidden" }); + +const T = extern struct { x: u32 }; + +test { + var mut_val_ptr = @extern(*f64, .{ .name = "mut_val" }); + var const_val_ptr = @extern(*const T, .{ .name = "const_val" }); + + assert(getHidden() == 0); + updateHidden(123); + assert(getHidden() == 123); + + assert(mut_val_ptr.* == 1.23); + mut_val_ptr.* = 10.0; + assert(mut_val_ptr.* == 10.0); + + assert(const_val_ptr.x == 42); +} From a8bd55e0853f7f80c9cd843ec54813425e4276bc Mon Sep 17 00:00:00 2001 From: mlugg Date: Tue, 7 Mar 2023 22:24:50 +0000 Subject: [PATCH 110/294] translate-c: translate extern unknown-length arrays using @extern Resolves: #14743 --- src/translate_c.zig | 16 +++++++++++++++- src/translate_c/ast.zig | 28 ++++++++++++++++++++++++++++ test/translate_c.zig | 8 ++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index 5b2b1c2df5..698f750afb 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -784,9 +784,9 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co const qual_type = var_decl.getTypeSourceInfo_getType(); const storage_class = var_decl.getStorageClass(); - const is_const = qual_type.isConstQualified(); const has_init = var_decl.hasInit(); const decl_init = var_decl.getInit(); + var is_const = qual_type.isConstQualified(); // In C extern variables with initializers behave like Zig exports. // extern int foo = 2; @@ -843,6 +843,20 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co // std.mem.zeroes(T) init_node = try Tag.std_mem_zeroes.create(c.arena, type_node); + } else if (qual_type.getTypeClass() == .IncompleteArray) { + // Oh no, an extern array of unknown size! These are really fun because there's no + // direct equivalent in Zig. To translate correctly, we'll have to create a C-pointer + // to the data initialized via @extern. + + const name_str = try std.fmt.allocPrint(c.arena, "\"{s}\"", .{var_name}); + init_node = try Tag.builtin_extern.create(c.arena, .{ + .type = type_node, + .name = try Tag.string_literal.create(c.arena, name_str), + }); + + // Since this is really a pointer to the underlying data, we tweak a few properties. + is_extern = false; + is_const = true; } const linksection_string = blk: { diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 81a19eb39d..688235c2d3 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -158,6 +158,8 @@ pub const Node = extern union { vector_zero_init, /// @shuffle(type, a, b, mask) shuffle, + /// @extern(ty, .{ .name = n }) + builtin_extern, /// @import("std").zig.c_translation.MacroArithmetic.(lhs, rhs) macro_arithmetic, @@ -373,6 +375,7 @@ pub const Node = extern union { .field_access => Payload.FieldAccess, .string_slice => Payload.StringSlice, .shuffle => Payload.Shuffle, + .builtin_extern => Payload.Extern, .macro_arithmetic => Payload.MacroArithmetic, }; } @@ -718,6 +721,14 @@ pub const Payload = struct { }, }; + pub const Extern = struct { + base: Payload, + data: struct { + type: Node, + name: Node, + }, + }; + pub const MacroArithmetic = struct { base: Payload, data: struct { @@ -1409,6 +1420,22 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { payload.mask_vector, }); }, + .builtin_extern => { + const payload = node.castTag(.builtin_extern).?.data; + + var info_inits: [1]Payload.ContainerInitDot.Initializer = .{ + .{ .name = "name", .value = payload.name }, + }; + var info_payload: Payload.ContainerInitDot = .{ + .base = .{ .tag = .container_init_dot }, + .data = &info_inits, + }; + + return renderBuiltinCall(c, "@extern", &.{ + payload.type, + .{ .ptr_otherwise = &info_payload.base }, + }); + }, .macro_arithmetic => { const payload = node.castTag(.macro_arithmetic).?.data; const op = @tagName(payload.op); @@ -2348,6 +2375,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .div_exact, .offset_of, .shuffle, + .builtin_extern, .static_local_var, .mut_str, .macro_arithmetic, diff --git a/test/translate_c.zig b/test/translate_c.zig index 92dc3038c0..4d65bddb39 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3948,4 +3948,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); } + + cases.add("extern array of unknown length", + \\extern int foo[]; + , &[_][]const u8{ + \\const foo: [*c]c_int = @extern([*c]c_int, .{ + \\ .name = "foo", + \\}); + }); } From ac434fd8cc1105912d29f209e2f9f67e5af3a744 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 22:06:22 +0100 Subject: [PATCH 111/294] x86_64: avoid inline for-loops when scanning the encodings table --- src/arch/x86_64/Encoding.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 7b00679b92..a51f954aed 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -68,7 +68,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { // TODO work out what is the maximum number of variants we can actually find in one swoop. var candidates: [10]Encoding = undefined; var count: usize = 0; - inline for (table) |entry| { + for (table) |entry| { const enc = Encoding{ .mnemonic = entry[0], .op_en = entry[1], @@ -162,7 +162,7 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct { legacy: LegacyPrefixes, rex: Rex, }, modrm_ext: ?u3) ?Encoding { - inline for (table) |entry| { + for (table) |entry| { const enc = Encoding{ .mnemonic = entry[0], .op_en = entry[1], From e9fc0aba4c3b0855e580ff3afe7251920ae3dcf9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 22:08:29 +0100 Subject: [PATCH 112/294] x86_64: add missing source files to CMakeLists.txt --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46d9c701b0..5afea9354e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,9 +559,12 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/arch/wasm/Mir.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/CodeGen.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Emit.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Encoding.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Mir.zig" - "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/abi.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/encoder.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/encodings.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" "${CMAKE_SOURCE_DIR}/src/clang_options.zig" "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig" From 10c74631b381b6b7d84def90a3a747192f2793a0 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 12 Mar 2023 16:50:04 +0100 Subject: [PATCH 113/294] langref: add missing comma in assembly expressions --- doc/langref.html.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 7044fe977f..9cee35caa2 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7457,20 +7457,20 @@ pub const STDOUT_FILENO = 1; pub fn syscall1(number: usize, arg1: usize) usize { return asm volatile ("syscall" - : [ret] "={rax}" (-> usize) + : [ret] "={rax}" (-> usize), : [number] "{rax}" (number), - [arg1] "{rdi}" (arg1) + [arg1] "{rdi}" (arg1), : "rcx", "r11" ); } pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize { return asm volatile ("syscall" - : [ret] "={rax}" (-> usize) + : [ret] "={rax}" (-> usize), : [number] "{rax}" (number), [arg1] "{rdi}" (arg1), [arg2] "{rsi}" (arg2), - [arg3] "{rdx}" (arg3) + [arg3] "{rdx}" (arg3), : "rcx", "r11" ); } @@ -7519,14 +7519,14 @@ pub fn syscall1(number: usize, arg1: usize) usize { // type is the result type of the inline assembly expression. // If it is a value binding, then `%[ret]` syntax would be used // to refer to the register bound to the value. - (-> usize) + (-> usize), // Next is the list of inputs. // The constraint for these inputs means, "when the assembly code is // executed, $rax shall have the value of `number` and $rdi shall have // the value of `arg1`". Any number of input parameters is allowed, // including none. : [number] "{rax}" (number), - [arg1] "{rdi}" (arg1) + [arg1] "{rdi}" (arg1), // Next is the list of clobbers. These declare a set of registers whose // values will not be preserved by the execution of this assembly code. // These do not include output or input registers. The special clobber From 1d96a17af473d5ca79ecc7b64bbf2e899b5de3b4 Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Mon, 13 Mar 2023 08:06:27 +0100 Subject: [PATCH 114/294] crypto.aescrypto.encrypt: do not add the round key in an asm block (#14899) Apple M1/M2 have an EOR3 instruction that can XOR 2 operands with another one, and LLVM knows how to take advantage of it. However, two EOR can't be automatically combined into an EOR3 if one of them is in an assembly block. That simple change speeds up ciphers doing an AES round immediately followed by a XOR operation on Apple Silicon. Before: aegis-128l mac: 12534 MiB/s aegis-256 mac: 6722 MiB/s aegis-128l: 10634 MiB/s aegis-256: 6133 MiB/s aes128-gcm: 3890 MiB/s aes256-gcm: 3122 MiB/s aes128-ocb: 2832 MiB/s aes256-ocb: 2057 MiB/s After: aegis-128l mac: 15667 MiB/s aegis-256 mac: 8240 MiB/s aegis-128l: 12656 MiB/s aegis-256: 7214 MiB/s aes128-gcm: 3976 MiB/s aes256-gcm: 3202 MiB/s aes128-ocb: 2835 MiB/s aes256-ocb: 2118 MiB/s --- lib/std/crypto/aes/armcrypto.zig | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/std/crypto/aes/armcrypto.zig b/lib/std/crypto/aes/armcrypto.zig index 3f4faf1b14..a6574c372a 100644 --- a/lib/std/crypto/aes/armcrypto.zig +++ b/lib/std/crypto/aes/armcrypto.zig @@ -32,62 +32,54 @@ pub const Block = struct { /// Encrypt a block with a round key. pub inline fn encrypt(block: Block, round_key: Block) Block { return Block{ - .repr = asm ( + .repr = (asm ( \\ mov %[out].16b, %[in].16b \\ aese %[out].16b, %[zero].16b \\ aesmc %[out].16b, %[out].16b - \\ eor %[out].16b, %[out].16b, %[rk].16b : [out] "=&x" (-> BlockVec), : [in] "x" (block.repr), - [rk] "x" (round_key.repr), [zero] "x" (zero), - ), + )) ^ round_key.repr, }; } /// Encrypt a block with the last round key. pub inline fn encryptLast(block: Block, round_key: Block) Block { return Block{ - .repr = asm ( + .repr = (asm ( \\ mov %[out].16b, %[in].16b \\ aese %[out].16b, %[zero].16b - \\ eor %[out].16b, %[out].16b, %[rk].16b : [out] "=&x" (-> BlockVec), : [in] "x" (block.repr), - [rk] "x" (round_key.repr), [zero] "x" (zero), - ), + )) ^ round_key.repr, }; } /// Decrypt a block with a round key. pub inline fn decrypt(block: Block, inv_round_key: Block) Block { return Block{ - .repr = asm ( + .repr = (asm ( \\ mov %[out].16b, %[in].16b \\ aesd %[out].16b, %[zero].16b \\ aesimc %[out].16b, %[out].16b - \\ eor %[out].16b, %[out].16b, %[rk].16b : [out] "=&x" (-> BlockVec), : [in] "x" (block.repr), - [rk] "x" (inv_round_key.repr), [zero] "x" (zero), - ), + )) ^ inv_round_key.repr, }; } /// Decrypt a block with the last round key. pub inline fn decryptLast(block: Block, inv_round_key: Block) Block { return Block{ - .repr = asm ( + .repr = (asm ( \\ mov %[out].16b, %[in].16b \\ aesd %[out].16b, %[zero].16b - \\ eor %[out].16b, %[out].16b, %[rk].16b : [out] "=&x" (-> BlockVec), : [in] "x" (block.repr), - [rk] "x" (inv_round_key.repr), [zero] "x" (zero), - ), + )) ^ inv_round_key.repr, }; } From adc6dec26b8ba9f79aabc4b69ae689acf4c6767d Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Sun, 12 Mar 2023 13:08:15 -0400 Subject: [PATCH 115/294] Sema: avoid panic on callconv(.C) generic return type Fixes #14854 --- src/Sema.zig | 2 +- test/behavior.zig | 1 + test/behavior/bugs/14854.zig | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 test/behavior/bugs/14854.zig diff --git a/src/Sema.zig b/src/Sema.zig index aef07b7988..e6652a5d66 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8782,7 +8782,7 @@ fn funcCommon( }; return sema.failWithOwnedErrorMsg(msg); } - if (!Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) { + if (!ret_poison and !Type.fnCallingConventionAllowsZigTypes(cc_resolved) and !try sema.validateExternType(return_type, .ret_ty)) { const msg = msg: { const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{ return_type.fmt(sema.mod), @tagName(cc_resolved), diff --git a/test/behavior.zig b/test/behavior.zig index 4f8ad67203..ed731377d8 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -141,6 +141,7 @@ test { _ = @import("behavior/bugs/13664.zig"); _ = @import("behavior/bugs/13714.zig"); _ = @import("behavior/bugs/13785.zig"); + _ = @import("behavior/bugs/14854.zig"); _ = @import("behavior/byteswap.zig"); _ = @import("behavior/byval_arg_var.zig"); _ = @import("behavior/call.zig"); diff --git a/test/behavior/bugs/14854.zig b/test/behavior/bugs/14854.zig new file mode 100644 index 0000000000..b34dd49406 --- /dev/null +++ b/test/behavior/bugs/14854.zig @@ -0,0 +1,13 @@ +const testing = @import("std").testing; + +test { + try testing.expect(getGeneric(u8, getU8) == 123); +} + +fn getU8() callconv(.C) u8 { + return 123; +} + +fn getGeneric(comptime T: type, supplier: fn () callconv(.C) T) T { + return supplier(); +} From 4942e4e8701b9a137d7f30fcb8ac1bc2d46f2a99 Mon Sep 17 00:00:00 2001 From: Hashi364 <49736221+Kiyoshi364@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:47:20 -0300 Subject: [PATCH 116/294] Resolve docs inconsistency with Overflow builtins In 41 (Undefined Behavior) . 5 (Integer Overflow) . 3 (Builtin Overflow Functions), it is stated that > These builtins return a bool of whether or not overflow occurred, as well as returning the overflowed bits: > * @addWithOverflow > * @subWithOverflow > * @mulWithOverflow > * @shlWithOverflow but in their definition says that it returns a `tuple`/`struct`. Example; `@addWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }` Co-authored-by: zooster --- doc/langref.html.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 9cee35caa2..991fd0c3e6 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9804,8 +9804,8 @@ pub fn main() !void { {#header_close#} {#header_open|Builtin Overflow Functions#}

    - These builtins return a {#syntax#}bool{#endsyntax#} of whether or not overflow - occurred, as well as returning the overflowed bits: + These builtins return a tuple containing whether there was an overflow + (as a {#syntax#}u1{#endsyntax#}) and the possibly overflowed bits of the operation:

    • {#link|@addWithOverflow#}
    • From 962299157840979ba659d478785f5ed0759d5401 Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Mon, 13 Mar 2023 22:18:26 +0100 Subject: [PATCH 117/294] Add configurable side channels mitigations; enable them on soft AES (#13739) * Add configurable side channels mitigations; enable them on soft AES Our software AES implementation doesn't have any mitigations against side channels. Go's generic implementation is not protected at all either, and even OpenSSL only has minimal mitigations. Full mitigations against cache-based attacks (bitslicing, fixslicing) come at a huge performance cost, making AES-based primitives pretty much useless for many applications. They also don't offer any protection against other classes of side channel attacks. In practice, partially protected, or even unprotected implementations are not as bad as it sounds. Exploiting these side channels requires an attacker that is able to submit many plaintexts/ciphertexts and perform accurate measurements. Noisy measurements can still be exploited, but require a significant amount of attempts. Wether this is exploitable or not depends on the platform, application and the attacker's proximity. So, some libraries made the choice of minimal mitigations and some use better mitigations in spite of the performance hit. It's a tradeoff (security vs performance), and there's no one-size-fits all implementation. What applies to AES applies to other cryptographic primitives. For example, RSA signatures are very sensible to fault attacks, regardless of them using the CRT or not. A mitigation is to verify every produced signature. That also comes with a performance cost. Wether to do it or not depends on wether fault attacks are part of the threat model or not. Thanks to Zig's comptime, we can try to address these different requirements. This PR adds a `side_channels_protection` global, that can later be complemented with `fault_attacks_protection` and possibly other knobs. It can have 4 different values: - `none`: which doesn't enable additional mitigations. "Additional", because it only disables mitigations that don't have a big performance cost. For example, checking authentication tags will still be done in constant time. - `basic`: which enables mitigations protecting against attacks in a common scenario, where an attacker doesn't have physical access to the device, cannot run arbitrary code on the same thread, and cannot conduct brute-force attacks without being throttled. - `medium`: which enables additional mitigations, offering practical protection in a shared environement. - `full`: which enables all the mitigations we have. The tradeoff is that the more mitigations we enable, the bigger the performance hit will be. But this let applications choose what's best for their use case. `medium` is the default. Currently, this only affects software AES, but that setting can later be used by other primitives. For AES, our implementation is a traditional table-based, with 4 32-bit tables and a sbox. Lookups in that table have been replaced by function calls. These functions can add a configurable noise level, making cache-based attacks more difficult to conduct. In the `none` mitigation level, the behavior is exactly the same as before. Performance also remains the same. In other levels, we compress the T tables into a single one, and read data from multiple cache lines (all of them in `full` mode), for all bytes in parallel. More precise measurements and way more attempts become necessary in order to find correlations. In addition, we use distinct copies of the sbox for key expansion and encryption, so that they don't share the same L1 cache entries. The best known attacks target the first two AES round, or the last one. While future attacks may improve on this, AES achieves full diffusion after 4 rounds. So, we can relax the mitigations after that. This is what this implementation does, enabling mitigations again for the last two rounds. In `full` mode, all the rounds are protected. The protection assumes that lookups within a cache line are secret. The cachebleed attack showed that it can be circumvented, but that requires an attacker to be able to abuse hyperthreading and run code on the same core as the encryption, which is rarely a practical scenario. Still, the current AES API allows us to transparently switch to using fixslicing/bitslicing later when the `full` mitigation level is enabled. * Software AES: use little-endian representation. Virtually all platforms are little-endian these days, so optimizing for big-endian CPUs doesn't make sense any more. --- lib/std/crypto.zig | 27 +++ lib/std/crypto/aes/soft.zig | 363 ++++++++++++++++++++++++++++++------ 2 files changed, 328 insertions(+), 62 deletions(-) diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index f46e7b1022..b469620002 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -1,3 +1,5 @@ +const root = @import("root"); + /// Authenticated Encryption with Associated Data pub const aead = struct { pub const aegis = struct { @@ -183,6 +185,31 @@ pub const errors = @import("crypto/errors.zig"); pub const tls = @import("crypto/tls.zig"); pub const Certificate = @import("crypto/Certificate.zig"); +/// Global configuration of cryptographic implementations in the standard library. +pub const config = struct { + /// Side-channels mitigations. + pub const SideChannelsMitigations = enum { + /// No additional side-channel mitigations are applied. + /// This is the fastest mode. + none, + /// The `basic` mode protects against most practical attacks, provided that the + /// application or implements proper defenses against brute-force attacks. + /// It offers a good balance between performance and security. + basic, + /// The `medium` mode offers increased resilience against side-channel attacks, + /// making most attacks unpractical even on shared/low latency environements. + /// This is the default mode. + medium, + /// The `full` mode offers the highest level of protection against side-channel attacks. + /// Note that this doesn't cover all possible attacks (especially power analysis or + /// thread-local attacks such as cachebleed), and that the performance impact is significant. + full, + }; + + /// This is a global configuration that applies to all cryptographic implementations. + pub const side_channels_mitigations: SideChannelsMitigations = if (@hasDecl(root, "side_channels_mitigations")) root.side_channels_mitigations else .medium; +}; + test { _ = aead.aegis.Aegis128L; _ = aead.aegis.Aegis256; diff --git a/lib/std/crypto/aes/soft.zig b/lib/std/crypto/aes/soft.zig index d8bd3d4ac0..4a300961c6 100644 --- a/lib/std/crypto/aes/soft.zig +++ b/lib/std/crypto/aes/soft.zig @@ -1,11 +1,11 @@ -// Based on Go stdlib implementation - const std = @import("../../std.zig"); const math = std.math; const mem = std.mem; const BlockVec = [4]u32; +const side_channels_mitigations = std.crypto.config.side_channels_mitigations; + /// A single AES block. pub const Block = struct { pub const block_length: usize = 16; @@ -15,20 +15,20 @@ pub const Block = struct { /// Convert a byte sequence into an internal representation. pub inline fn fromBytes(bytes: *const [16]u8) Block { - const s0 = mem.readIntBig(u32, bytes[0..4]); - const s1 = mem.readIntBig(u32, bytes[4..8]); - const s2 = mem.readIntBig(u32, bytes[8..12]); - const s3 = mem.readIntBig(u32, bytes[12..16]); + const s0 = mem.readIntLittle(u32, bytes[0..4]); + const s1 = mem.readIntLittle(u32, bytes[4..8]); + const s2 = mem.readIntLittle(u32, bytes[8..12]); + const s3 = mem.readIntLittle(u32, bytes[12..16]); return Block{ .repr = BlockVec{ s0, s1, s2, s3 } }; } /// Convert the internal representation of a block into a byte sequence. pub inline fn toBytes(block: Block) [16]u8 { var bytes: [16]u8 = undefined; - mem.writeIntBig(u32, bytes[0..4], block.repr[0]); - mem.writeIntBig(u32, bytes[4..8], block.repr[1]); - mem.writeIntBig(u32, bytes[8..12], block.repr[2]); - mem.writeIntBig(u32, bytes[12..16], block.repr[3]); + mem.writeIntLittle(u32, bytes[0..4], block.repr[0]); + mem.writeIntLittle(u32, bytes[4..8], block.repr[1]); + mem.writeIntLittle(u32, bytes[8..12], block.repr[2]); + mem.writeIntLittle(u32, bytes[12..16], block.repr[3]); return bytes; } @@ -50,32 +50,93 @@ pub const Block = struct { const s2 = block.repr[2]; const s3 = block.repr[3]; - const t0 = round_key.repr[0] ^ table_encrypt[0][@truncate(u8, s0 >> 24)] ^ table_encrypt[1][@truncate(u8, s1 >> 16)] ^ table_encrypt[2][@truncate(u8, s2 >> 8)] ^ table_encrypt[3][@truncate(u8, s3)]; - const t1 = round_key.repr[1] ^ table_encrypt[0][@truncate(u8, s1 >> 24)] ^ table_encrypt[1][@truncate(u8, s2 >> 16)] ^ table_encrypt[2][@truncate(u8, s3 >> 8)] ^ table_encrypt[3][@truncate(u8, s0)]; - const t2 = round_key.repr[2] ^ table_encrypt[0][@truncate(u8, s2 >> 24)] ^ table_encrypt[1][@truncate(u8, s3 >> 16)] ^ table_encrypt[2][@truncate(u8, s0 >> 8)] ^ table_encrypt[3][@truncate(u8, s1)]; - const t3 = round_key.repr[3] ^ table_encrypt[0][@truncate(u8, s3 >> 24)] ^ table_encrypt[1][@truncate(u8, s0 >> 16)] ^ table_encrypt[2][@truncate(u8, s1 >> 8)] ^ table_encrypt[3][@truncate(u8, s2)]; + var x: [4]u32 = undefined; + x = table_lookup(&table_encrypt, @truncate(u8, s0), @truncate(u8, s1 >> 8), @truncate(u8, s2 >> 16), @truncate(u8, s3 >> 24)); + var t0 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = table_lookup(&table_encrypt, @truncate(u8, s1), @truncate(u8, s2 >> 8), @truncate(u8, s3 >> 16), @truncate(u8, s0 >> 24)); + var t1 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = table_lookup(&table_encrypt, @truncate(u8, s2), @truncate(u8, s3 >> 8), @truncate(u8, s0 >> 16), @truncate(u8, s1 >> 24)); + var t2 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = table_lookup(&table_encrypt, @truncate(u8, s3), @truncate(u8, s0 >> 8), @truncate(u8, s1 >> 16), @truncate(u8, s2 >> 24)); + var t3 = x[0] ^ x[1] ^ x[2] ^ x[3]; + + t0 ^= round_key.repr[0]; + t1 ^= round_key.repr[1]; + t2 ^= round_key.repr[2]; + t3 ^= round_key.repr[3]; + + return Block{ .repr = BlockVec{ t0, t1, t2, t3 } }; + } + + /// Encrypt a block with a round key *WITHOUT ANY PROTECTION AGAINST SIDE CHANNELS* + pub inline fn encryptUnprotected(block: Block, round_key: Block) Block { + const s0 = block.repr[0]; + const s1 = block.repr[1]; + const s2 = block.repr[2]; + const s3 = block.repr[3]; + + var x: [4]u32 = undefined; + x = .{ + table_encrypt[0][@truncate(u8, s0)], + table_encrypt[1][@truncate(u8, s1 >> 8)], + table_encrypt[2][@truncate(u8, s2 >> 16)], + table_encrypt[3][@truncate(u8, s3 >> 24)], + }; + var t0 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = .{ + table_encrypt[0][@truncate(u8, s1)], + table_encrypt[1][@truncate(u8, s2 >> 8)], + table_encrypt[2][@truncate(u8, s3 >> 16)], + table_encrypt[3][@truncate(u8, s0 >> 24)], + }; + var t1 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = .{ + table_encrypt[0][@truncate(u8, s2)], + table_encrypt[1][@truncate(u8, s3 >> 8)], + table_encrypt[2][@truncate(u8, s0 >> 16)], + table_encrypt[3][@truncate(u8, s1 >> 24)], + }; + var t2 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = .{ + table_encrypt[0][@truncate(u8, s3)], + table_encrypt[1][@truncate(u8, s0 >> 8)], + table_encrypt[2][@truncate(u8, s1 >> 16)], + table_encrypt[3][@truncate(u8, s2 >> 24)], + }; + var t3 = x[0] ^ x[1] ^ x[2] ^ x[3]; + + t0 ^= round_key.repr[0]; + t1 ^= round_key.repr[1]; + t2 ^= round_key.repr[2]; + t3 ^= round_key.repr[3]; return Block{ .repr = BlockVec{ t0, t1, t2, t3 } }; } /// Encrypt a block with the last round key. pub inline fn encryptLast(block: Block, round_key: Block) Block { - const t0 = block.repr[0]; - const t1 = block.repr[1]; - const t2 = block.repr[2]; - const t3 = block.repr[3]; + const s0 = block.repr[0]; + const s1 = block.repr[1]; + const s2 = block.repr[2]; + const s3 = block.repr[3]; // Last round uses s-box directly and XORs to produce output. - var s0 = @as(u32, sbox_encrypt[t0 >> 24]) << 24 | @as(u32, sbox_encrypt[t1 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t2 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t3 & 0xff]); - var s1 = @as(u32, sbox_encrypt[t1 >> 24]) << 24 | @as(u32, sbox_encrypt[t2 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t3 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t0 & 0xff]); - var s2 = @as(u32, sbox_encrypt[t2 >> 24]) << 24 | @as(u32, sbox_encrypt[t3 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t0 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t1 & 0xff]); - var s3 = @as(u32, sbox_encrypt[t3 >> 24]) << 24 | @as(u32, sbox_encrypt[t0 >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[t1 >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[t2 & 0xff]); - s0 ^= round_key.repr[0]; - s1 ^= round_key.repr[1]; - s2 ^= round_key.repr[2]; - s3 ^= round_key.repr[3]; + var x: [4]u8 = undefined; + x = sbox_lookup(&sbox_encrypt, @truncate(u8, s3 >> 24), @truncate(u8, s2 >> 16), @truncate(u8, s1 >> 8), @truncate(u8, s0)); + var t0 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); + x = sbox_lookup(&sbox_encrypt, @truncate(u8, s0 >> 24), @truncate(u8, s3 >> 16), @truncate(u8, s2 >> 8), @truncate(u8, s1)); + var t1 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); + x = sbox_lookup(&sbox_encrypt, @truncate(u8, s1 >> 24), @truncate(u8, s0 >> 16), @truncate(u8, s3 >> 8), @truncate(u8, s2)); + var t2 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); + x = sbox_lookup(&sbox_encrypt, @truncate(u8, s2 >> 24), @truncate(u8, s1 >> 16), @truncate(u8, s0 >> 8), @truncate(u8, s3)); + var t3 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); - return Block{ .repr = BlockVec{ s0, s1, s2, s3 } }; + t0 ^= round_key.repr[0]; + t1 ^= round_key.repr[1]; + t2 ^= round_key.repr[2]; + t3 ^= round_key.repr[3]; + + return Block{ .repr = BlockVec{ t0, t1, t2, t3 } }; } /// Decrypt a block with a round key. @@ -85,32 +146,93 @@ pub const Block = struct { const s2 = block.repr[2]; const s3 = block.repr[3]; - const t0 = round_key.repr[0] ^ table_decrypt[0][@truncate(u8, s0 >> 24)] ^ table_decrypt[1][@truncate(u8, s3 >> 16)] ^ table_decrypt[2][@truncate(u8, s2 >> 8)] ^ table_decrypt[3][@truncate(u8, s1)]; - const t1 = round_key.repr[1] ^ table_decrypt[0][@truncate(u8, s1 >> 24)] ^ table_decrypt[1][@truncate(u8, s0 >> 16)] ^ table_decrypt[2][@truncate(u8, s3 >> 8)] ^ table_decrypt[3][@truncate(u8, s2)]; - const t2 = round_key.repr[2] ^ table_decrypt[0][@truncate(u8, s2 >> 24)] ^ table_decrypt[1][@truncate(u8, s1 >> 16)] ^ table_decrypt[2][@truncate(u8, s0 >> 8)] ^ table_decrypt[3][@truncate(u8, s3)]; - const t3 = round_key.repr[3] ^ table_decrypt[0][@truncate(u8, s3 >> 24)] ^ table_decrypt[1][@truncate(u8, s2 >> 16)] ^ table_decrypt[2][@truncate(u8, s1 >> 8)] ^ table_decrypt[3][@truncate(u8, s0)]; + var x: [4]u32 = undefined; + x = table_lookup(&table_decrypt, @truncate(u8, s0), @truncate(u8, s3 >> 8), @truncate(u8, s2 >> 16), @truncate(u8, s1 >> 24)); + var t0 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = table_lookup(&table_decrypt, @truncate(u8, s1), @truncate(u8, s0 >> 8), @truncate(u8, s3 >> 16), @truncate(u8, s2 >> 24)); + var t1 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = table_lookup(&table_decrypt, @truncate(u8, s2), @truncate(u8, s1 >> 8), @truncate(u8, s0 >> 16), @truncate(u8, s3 >> 24)); + var t2 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = table_lookup(&table_decrypt, @truncate(u8, s3), @truncate(u8, s2 >> 8), @truncate(u8, s1 >> 16), @truncate(u8, s0 >> 24)); + var t3 = x[0] ^ x[1] ^ x[2] ^ x[3]; + + t0 ^= round_key.repr[0]; + t1 ^= round_key.repr[1]; + t2 ^= round_key.repr[2]; + t3 ^= round_key.repr[3]; + + return Block{ .repr = BlockVec{ t0, t1, t2, t3 } }; + } + + /// Decrypt a block with a round key *WITHOUT ANY PROTECTION AGAINST SIDE CHANNELS* + pub inline fn decryptUnprotected(block: Block, round_key: Block) Block { + const s0 = block.repr[0]; + const s1 = block.repr[1]; + const s2 = block.repr[2]; + const s3 = block.repr[3]; + + var x: [4]u32 = undefined; + x = .{ + table_decrypt[0][@truncate(u8, s0)], + table_decrypt[1][@truncate(u8, s3 >> 8)], + table_decrypt[2][@truncate(u8, s2 >> 16)], + table_decrypt[3][@truncate(u8, s1 >> 24)], + }; + var t0 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = .{ + table_decrypt[0][@truncate(u8, s1)], + table_decrypt[1][@truncate(u8, s0 >> 8)], + table_decrypt[2][@truncate(u8, s3 >> 16)], + table_decrypt[3][@truncate(u8, s2 >> 24)], + }; + var t1 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = .{ + table_decrypt[0][@truncate(u8, s2)], + table_decrypt[1][@truncate(u8, s1 >> 8)], + table_decrypt[2][@truncate(u8, s0 >> 16)], + table_decrypt[3][@truncate(u8, s3 >> 24)], + }; + var t2 = x[0] ^ x[1] ^ x[2] ^ x[3]; + x = .{ + table_decrypt[0][@truncate(u8, s3)], + table_decrypt[1][@truncate(u8, s2 >> 8)], + table_decrypt[2][@truncate(u8, s1 >> 16)], + table_decrypt[3][@truncate(u8, s0 >> 24)], + }; + var t3 = x[0] ^ x[1] ^ x[2] ^ x[3]; + + t0 ^= round_key.repr[0]; + t1 ^= round_key.repr[1]; + t2 ^= round_key.repr[2]; + t3 ^= round_key.repr[3]; return Block{ .repr = BlockVec{ t0, t1, t2, t3 } }; } /// Decrypt a block with the last round key. pub inline fn decryptLast(block: Block, round_key: Block) Block { - const t0 = block.repr[0]; - const t1 = block.repr[1]; - const t2 = block.repr[2]; - const t3 = block.repr[3]; + const s0 = block.repr[0]; + const s1 = block.repr[1]; + const s2 = block.repr[2]; + const s3 = block.repr[3]; // Last round uses s-box directly and XORs to produce output. - var s0 = @as(u32, sbox_decrypt[t0 >> 24]) << 24 | @as(u32, sbox_decrypt[t3 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t2 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t1 & 0xff]); - var s1 = @as(u32, sbox_decrypt[t1 >> 24]) << 24 | @as(u32, sbox_decrypt[t0 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t3 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t2 & 0xff]); - var s2 = @as(u32, sbox_decrypt[t2 >> 24]) << 24 | @as(u32, sbox_decrypt[t1 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t0 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t3 & 0xff]); - var s3 = @as(u32, sbox_decrypt[t3 >> 24]) << 24 | @as(u32, sbox_decrypt[t2 >> 16 & 0xff]) << 16 | @as(u32, sbox_decrypt[t1 >> 8 & 0xff]) << 8 | @as(u32, sbox_decrypt[t0 & 0xff]); - s0 ^= round_key.repr[0]; - s1 ^= round_key.repr[1]; - s2 ^= round_key.repr[2]; - s3 ^= round_key.repr[3]; + var x: [4]u8 = undefined; + x = sbox_lookup(&sbox_decrypt, @truncate(u8, s1 >> 24), @truncate(u8, s2 >> 16), @truncate(u8, s3 >> 8), @truncate(u8, s0)); + var t0 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); + x = sbox_lookup(&sbox_decrypt, @truncate(u8, s2 >> 24), @truncate(u8, s3 >> 16), @truncate(u8, s0 >> 8), @truncate(u8, s1)); + var t1 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); + x = sbox_lookup(&sbox_decrypt, @truncate(u8, s3 >> 24), @truncate(u8, s0 >> 16), @truncate(u8, s1 >> 8), @truncate(u8, s2)); + var t2 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); + x = sbox_lookup(&sbox_decrypt, @truncate(u8, s0 >> 24), @truncate(u8, s1 >> 16), @truncate(u8, s2 >> 8), @truncate(u8, s3)); + var t3 = @as(u32, x[0]) << 24 | @as(u32, x[1]) << 16 | @as(u32, x[2]) << 8 | @as(u32, x[3]); - return Block{ .repr = BlockVec{ s0, s1, s2, s3 } }; + t0 ^= round_key.repr[0]; + t1 ^= round_key.repr[1]; + t2 ^= round_key.repr[2]; + t3 ^= round_key.repr[3]; + + return Block{ .repr = BlockVec{ t0, t1, t2, t3 } }; } /// Apply the bitwise XOR operation to the content of two blocks. @@ -226,7 +348,8 @@ fn KeySchedule(comptime Aes: type) type { const subw = struct { // Apply sbox_encrypt to each byte in w. fn func(w: u32) u32 { - return @as(u32, sbox_encrypt[w >> 24]) << 24 | @as(u32, sbox_encrypt[w >> 16 & 0xff]) << 16 | @as(u32, sbox_encrypt[w >> 8 & 0xff]) << 8 | @as(u32, sbox_encrypt[w & 0xff]); + const x = sbox_lookup(&sbox_key_schedule, @truncate(u8, w), @truncate(u8, w >> 8), @truncate(u8, w >> 16), @truncate(u8, w >> 24)); + return @as(u32, x[3]) << 24 | @as(u32, x[2]) << 16 | @as(u32, x[1]) << 8 | @as(u32, x[0]); } }.func; @@ -244,6 +367,10 @@ fn KeySchedule(comptime Aes: type) type { } round_keys[i / 4].repr[i % 4] = round_keys[(i - words_in_key) / 4].repr[(i - words_in_key) % 4] ^ t; } + i = 0; + inline while (i < round_keys.len * 4) : (i += 1) { + round_keys[i / 4].repr[i % 4] = @byteSwap(round_keys[i / 4].repr[i % 4]); + } return Self{ .round_keys = round_keys }; } @@ -257,11 +384,13 @@ fn KeySchedule(comptime Aes: type) type { const ei = total_words - i - 4; comptime var j: usize = 0; inline while (j < 4) : (j += 1) { - var x = round_keys[(ei + j) / 4].repr[(ei + j) % 4]; + var rk = round_keys[(ei + j) / 4].repr[(ei + j) % 4]; if (i > 0 and i + 4 < total_words) { - x = table_decrypt[0][sbox_encrypt[x >> 24]] ^ table_decrypt[1][sbox_encrypt[x >> 16 & 0xff]] ^ table_decrypt[2][sbox_encrypt[x >> 8 & 0xff]] ^ table_decrypt[3][sbox_encrypt[x & 0xff]]; + const x = sbox_lookup(&sbox_key_schedule, @truncate(u8, rk >> 24), @truncate(u8, rk >> 16), @truncate(u8, rk >> 8), @truncate(u8, rk)); + const y = table_lookup(&table_decrypt, x[3], x[2], x[1], x[0]); + rk = y[0] ^ y[1] ^ y[2] ^ y[3]; } - inv_round_keys[(i + j) / 4].repr[(i + j) % 4] = x; + inv_round_keys[(i + j) / 4].repr[(i + j) % 4] = rk; } } return Self{ .round_keys = inv_round_keys }; @@ -293,7 +422,17 @@ pub fn AesEncryptCtx(comptime Aes: type) type { const round_keys = ctx.key_schedule.round_keys; var t = Block.fromBytes(src).xorBlocks(round_keys[0]); comptime var i = 1; - inline while (i < rounds) : (i += 1) { + if (side_channels_mitigations == .full) { + inline while (i < rounds) : (i += 1) { + t = t.encrypt(round_keys[i]); + } + } else { + inline while (i < 5) : (i += 1) { + t = t.encrypt(round_keys[i]); + } + inline while (i < rounds - 1) : (i += 1) { + t = t.encryptUnprotected(round_keys[i]); + } t = t.encrypt(round_keys[i]); } t = t.encryptLast(round_keys[rounds]); @@ -305,7 +444,17 @@ pub fn AesEncryptCtx(comptime Aes: type) type { const round_keys = ctx.key_schedule.round_keys; var t = Block.fromBytes(&counter).xorBlocks(round_keys[0]); comptime var i = 1; - inline while (i < rounds) : (i += 1) { + if (side_channels_mitigations == .full) { + inline while (i < rounds) : (i += 1) { + t = t.encrypt(round_keys[i]); + } + } else { + inline while (i < 5) : (i += 1) { + t = t.encrypt(round_keys[i]); + } + inline while (i < rounds - 1) : (i += 1) { + t = t.encryptUnprotected(round_keys[i]); + } t = t.encrypt(round_keys[i]); } t = t.encryptLast(round_keys[rounds]); @@ -359,7 +508,17 @@ pub fn AesDecryptCtx(comptime Aes: type) type { const inv_round_keys = ctx.key_schedule.round_keys; var t = Block.fromBytes(src).xorBlocks(inv_round_keys[0]); comptime var i = 1; - inline while (i < rounds) : (i += 1) { + if (side_channels_mitigations == .full) { + inline while (i < rounds) : (i += 1) { + t = t.decrypt(inv_round_keys[i]); + } + } else { + inline while (i < 5) : (i += 1) { + t = t.decrypt(inv_round_keys[i]); + } + inline while (i < rounds - 1) : (i += 1) { + t = t.decryptUnprotected(inv_round_keys[i]); + } t = t.decrypt(inv_round_keys[i]); } t = t.decryptLast(inv_round_keys[rounds]); @@ -428,10 +587,11 @@ const powx = init: { break :init array; }; -const sbox_encrypt align(64) = generateSbox(false); -const sbox_decrypt align(64) = generateSbox(true); -const table_encrypt align(64) = generateTable(false); -const table_decrypt align(64) = generateTable(true); +const sbox_encrypt align(64) = generateSbox(false); // S-box for encryption +const sbox_key_schedule align(64) = generateSbox(false); // S-box only for key schedule, so that it uses distinct L1 cache entries than the S-box used for encryption +const sbox_decrypt align(64) = generateSbox(true); // S-box for decryption +const table_encrypt align(64) = generateTable(false); // 4-byte LUTs for encryption +const table_decrypt align(64) = generateTable(true); // 4-byte LUTs for decryption // Generate S-box substitution values. fn generateSbox(invert: bool) [256]u8 { @@ -472,14 +632,14 @@ fn generateTable(invert: bool) [4][256]u32 { var table: [4][256]u32 = undefined; for (generateSbox(invert), 0..) |value, index| { - table[0][index] = mul(value, if (invert) 0xb else 0x3); - table[0][index] |= math.shl(u32, mul(value, if (invert) 0xd else 0x1), 8); - table[0][index] |= math.shl(u32, mul(value, if (invert) 0x9 else 0x1), 16); - table[0][index] |= math.shl(u32, mul(value, if (invert) 0xe else 0x2), 24); + table[0][index] = math.shl(u32, mul(value, if (invert) 0xb else 0x3), 24); + table[0][index] |= math.shl(u32, mul(value, if (invert) 0xd else 0x1), 16); + table[0][index] |= math.shl(u32, mul(value, if (invert) 0x9 else 0x1), 8); + table[0][index] |= mul(value, if (invert) 0xe else 0x2); - table[1][index] = math.rotr(u32, table[0][index], 8); - table[2][index] = math.rotr(u32, table[0][index], 16); - table[3][index] = math.rotr(u32, table[0][index], 24); + table[1][index] = math.rotl(u32, table[0][index], 8); + table[2][index] = math.rotl(u32, table[0][index], 16); + table[3][index] = math.rotl(u32, table[0][index], 24); } return table; @@ -506,3 +666,82 @@ fn mul(a: u8, b: u8) u8 { return @truncate(u8, s); } + +const cache_line_bytes = 64; + +inline fn sbox_lookup(sbox: *align(64) const [256]u8, idx0: u8, idx1: u8, idx2: u8, idx3: u8) [4]u8 { + if (side_channels_mitigations == .none) { + return [4]u8{ + sbox[idx0], + sbox[idx1], + sbox[idx2], + sbox[idx3], + }; + } else { + const stride = switch (side_channels_mitigations) { + .none => unreachable, + .basic => sbox.len / 4, + .medium => sbox.len / (sbox.len / cache_line_bytes) * 2, + .full => sbox.len / (sbox.len / cache_line_bytes), + }; + const of0 = idx0 % stride; + const of1 = idx1 % stride; + const of2 = idx2 % stride; + const of3 = idx3 % stride; + var t: [4][sbox.len / stride]u8 align(64) = undefined; + var i: usize = 0; + while (i < t[0].len) : (i += 1) { + const tx = sbox[i * stride ..]; + t[0][i] = tx[of0]; + t[1][i] = tx[of1]; + t[2][i] = tx[of2]; + t[3][i] = tx[of3]; + } + std.mem.doNotOptimizeAway(t); + return [4]u8{ + t[0][idx0 / stride], + t[1][idx1 / stride], + t[2][idx2 / stride], + t[3][idx3 / stride], + }; + } +} + +inline fn table_lookup(table: *align(64) const [4][256]u32, idx0: u8, idx1: u8, idx2: u8, idx3: u8) [4]u32 { + if (side_channels_mitigations == .none) { + return [4]u32{ + table[0][idx0], + table[1][idx1], + table[2][idx2], + table[3][idx3], + }; + } else { + const table_bytes = @sizeOf(@TypeOf(table[0])); + const stride = switch (side_channels_mitigations) { + .none => unreachable, + .basic => table[0].len / 4, + .medium => table[0].len / (table_bytes / cache_line_bytes) * 2, + .full => table[0].len / (table_bytes / cache_line_bytes), + }; + const of0 = idx0 % stride; + const of1 = idx1 % stride; + const of2 = idx2 % stride; + const of3 = idx3 % stride; + var t: [4][table[0].len / stride]u32 align(64) = undefined; + var i: usize = 0; + while (i < t[0].len) : (i += 1) { + const tx = table[0][i * stride ..]; + t[0][i] = tx[of0]; + t[1][i] = tx[of1]; + t[2][i] = tx[of2]; + t[3][i] = tx[of3]; + } + std.mem.doNotOptimizeAway(t); + return [4]u32{ + t[0][idx0 / stride], + math.rotl(u32, t[1][idx1 / stride], 8), + math.rotl(u32, t[2][idx2 / stride], 16), + math.rotl(u32, t[3][idx3 / stride], 24), + }; + } +} From 5a12d00708df019fa510076f8af40d6efcb7c608 Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:40:23 +0100 Subject: [PATCH 118/294] Move std.crypto.config options to std.options (#14906) Options have been moved to a single namespace. --- lib/std/crypto.zig | 42 +++++++++++++++++-------------------- lib/std/crypto/aes/soft.zig | 2 +- lib/std/std.zig | 5 +++++ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index b469620002..d70958f6dd 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -185,31 +185,27 @@ pub const errors = @import("crypto/errors.zig"); pub const tls = @import("crypto/tls.zig"); pub const Certificate = @import("crypto/Certificate.zig"); -/// Global configuration of cryptographic implementations in the standard library. -pub const config = struct { - /// Side-channels mitigations. - pub const SideChannelsMitigations = enum { - /// No additional side-channel mitigations are applied. - /// This is the fastest mode. - none, - /// The `basic` mode protects against most practical attacks, provided that the - /// application or implements proper defenses against brute-force attacks. - /// It offers a good balance between performance and security. - basic, - /// The `medium` mode offers increased resilience against side-channel attacks, - /// making most attacks unpractical even on shared/low latency environements. - /// This is the default mode. - medium, - /// The `full` mode offers the highest level of protection against side-channel attacks. - /// Note that this doesn't cover all possible attacks (especially power analysis or - /// thread-local attacks such as cachebleed), and that the performance impact is significant. - full, - }; - - /// This is a global configuration that applies to all cryptographic implementations. - pub const side_channels_mitigations: SideChannelsMitigations = if (@hasDecl(root, "side_channels_mitigations")) root.side_channels_mitigations else .medium; +/// Side-channels mitigations. +pub const SideChannelsMitigations = enum { + /// No additional side-channel mitigations are applied. + /// This is the fastest mode. + none, + /// The `basic` mode protects against most practical attacks, provided that the + /// application or implements proper defenses against brute-force attacks. + /// It offers a good balance between performance and security. + basic, + /// The `medium` mode offers increased resilience against side-channel attacks, + /// making most attacks unpractical even on shared/low latency environements. + /// This is the default mode. + medium, + /// The `full` mode offers the highest level of protection against side-channel attacks. + /// Note that this doesn't cover all possible attacks (especially power analysis or + /// thread-local attacks such as cachebleed), and that the performance impact is significant. + full, }; +pub const default_side_channels_mitigations = .medium; + test { _ = aead.aegis.Aegis128L; _ = aead.aegis.Aegis256; diff --git a/lib/std/crypto/aes/soft.zig b/lib/std/crypto/aes/soft.zig index 4a300961c6..7a8e7ff0ec 100644 --- a/lib/std/crypto/aes/soft.zig +++ b/lib/std/crypto/aes/soft.zig @@ -4,7 +4,7 @@ const mem = std.mem; const BlockVec = [4]u32; -const side_channels_mitigations = std.crypto.config.side_channels_mitigations; +const side_channels_mitigations = std.options.side_channels_mitigations; /// A single AES block. pub const Block = struct { diff --git a/lib/std/std.zig b/lib/std/std.zig index e888ade659..92ebdf595b 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -190,6 +190,11 @@ pub const options = struct { options_override.http_connection_pool_size else http.Client.default_connection_pool_size; + + pub const side_channels_mitigations: crypto.SideChannelsMitigations = if (@hasDecl(options_override, "side_channels_mitigations")) + options_override.side_channels_mitigations + else + crypto.default_side_channels_mitigations; }; // This forces the start.zig file to be imported, and the comptime logic inside that From 1e6d7f77639d52b61b2852cf0de19e2b5a50f31f Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 3 Mar 2023 21:13:31 +0000 Subject: [PATCH 119/294] Sema: allow comptime mutation of multiple array elements Previously, if you had a pointer to multiple array elements and tried to write to it at comptime, it was incorrectly treated as a pointer to one specific array value, leading to an assertion down the line. If we try to mutate a value at an elem_ptr larger than the element type, we need to perform a modification to multiple array elements. This solution isn't ideal, since it will result in storePtrVal serializing the whole array, modifying the relevant parts, and storing it back. Ideally, it would only take the required elements. However, this change would have been more complex, and this is a fairly rare operation (nobody ever ran into the bug before after all), so it doesn't matter all that much. --- src/Sema.zig | 17 +++++++++++++++++ test/behavior/array.zig | 27 +++++++++++++++++---------- test/behavior/comptime_memory.zig | 8 ++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e6652a5d66..8b6c269246 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -26648,6 +26648,23 @@ fn beginComptimePtrMutation( }); } const elem_ty = parent.ty.childType(); + + // We might have a pointer to multiple elements of the array (e.g. a pointer + // to a sub-array). In this case, we just have to reinterpret the relevant + // bytes of the whole array rather than any single element. + const elem_abi_size_u64 = try sema.typeAbiSize(elem_ptr.elem_ty); + if (elem_abi_size_u64 < try sema.typeAbiSize(ptr_elem_ty)) { + const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64); + return .{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .reinterpret = .{ + .val_ptr = val_ptr, + .byte_offset = elem_abi_size * elem_ptr.index, + } }, + .ty = parent.ty, + }; + } + switch (val_ptr.tag()) { .undef => { // An array has been initialized to undefined at comptime and now we diff --git a/test/behavior/array.zig b/test/behavior/array.zig index a5ecd6f115..3d711357f3 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -48,16 +48,23 @@ fn getArrayLen(a: []const u32) usize { test "array concat with undefined" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - { - var array = "hello".* ++ @as([5]u8, undefined); - array[5..10].* = "world".*; - try std.testing.expect(std.mem.eql(u8, &array, "helloworld")); - } - { - var array = @as([5]u8, undefined) ++ "world".*; - array[0..5].* = "hello".*; - try std.testing.expect(std.mem.eql(u8, &array, "helloworld")); - } + const S = struct { + fn doTheTest() !void { + { + var array = "hello".* ++ @as([5]u8, undefined); + array[5..10].* = "world".*; + try std.testing.expect(std.mem.eql(u8, &array, "helloworld")); + } + { + var array = @as([5]u8, undefined) ++ "world".*; + array[0..5].* = "hello".*; + try std.testing.expect(std.mem.eql(u8, &array, "helloworld")); + } + } + }; + + try S.doTheTest(); + comptime try S.doTheTest(); } test "array concat with tuple" { diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index a4f9f2f7a9..71d177395b 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -412,3 +412,11 @@ test "bitcast packed union to integer" { try testing.expectEqual(@as(u2, 2), cast_b); } } + +test "mutate entire slice at comptime" { + comptime { + var buf: [3]u8 = undefined; + const x: [2]u8 = .{ 1, 2 }; // Avoid RLS + buf[1..3].* = x; + } +} From 9ecdcb8e300a4f6aac050e3118ceea0b7d35f899 Mon Sep 17 00:00:00 2001 From: Kotaro Inoue Date: Tue, 14 Mar 2023 20:07:25 +0900 Subject: [PATCH 120/294] Fix to use '/' for a empty path (#14884) Signed-off-by: Kotaro Inoue --- lib/std/http/Client.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index baf0239388..76073c0ce3 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -333,7 +333,11 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Req try writer.writeAll(@tagName(headers.method)); try writer.writeByte(' '); - try writer.writeAll(escaped_path); + if (escaped_path.len == 0) { + try writer.writeByte('/'); + } else { + try writer.writeAll(escaped_path); + } if (escaped_query) |q| { try writer.writeByte('?'); try writer.writeAll(q); From d6e48abde87400a8a4851c7ab8c918005d81d058 Mon Sep 17 00:00:00 2001 From: DerryAlex <105348204+DerryAlex@users.noreply.github.com> Date: Tue, 14 Mar 2023 19:08:56 +0800 Subject: [PATCH 121/294] Implement readFromMemory/writeToMemory for ptrLikeOptional --- src/value.zig | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/value.zig b/src/value.zig index b6d27d620d..e5283d1270 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1365,6 +1365,17 @@ pub const Value = extern union { if (val.isDeclRef()) return error.ReinterpretDeclRef; return val.writeToMemory(Type.usize, mod, buffer); }, + .Optional => { + assert(ty.isPtrLikeOptional()); + var buf: Type.Payload.ElemType = undefined; + const child = ty.optionalChild(&buf); + const opt_val = val.optionalValue(); + if (opt_val) |some| { + return some.writeToMemory(child, mod, buffer); + } else { + return writeToMemory(Value.zero, Type.usize, mod, buffer); + } + }, else => @panic("TODO implement writeToMemory for more types"), } } @@ -1471,6 +1482,17 @@ pub const Value = extern union { if (val.isDeclRef()) return error.ReinterpretDeclRef; return val.writeToPackedMemory(Type.usize, mod, buffer, bit_offset); }, + .Optional => { + assert(ty.isPtrLikeOptional()); + var buf: Type.Payload.ElemType = undefined; + const child = ty.optionalChild(&buf); + const opt_val = val.optionalValue(); + if (opt_val) |some| { + return some.writeToPackedMemory(child, mod, buffer, bit_offset); + } else { + return writeToPackedMemory(Value.zero, Type.usize, mod, buffer, bit_offset); + } + }, else => @panic("TODO implement writeToPackedMemory for more types"), } } @@ -1579,6 +1601,12 @@ pub const Value = extern union { assert(!ty.isSlice()); // No well defined layout. return readFromMemory(Type.usize, mod, buffer, arena); }, + .Optional => { + assert(ty.isPtrLikeOptional()); + var buf: Type.Payload.ElemType = undefined; + const child = ty.optionalChild(&buf); + return readFromMemory(child, mod, buffer, arena); + }, else => @panic("TODO implement readFromMemory for more types"), } } @@ -1670,6 +1698,12 @@ pub const Value = extern union { assert(!ty.isSlice()); // No well defined layout. return readFromPackedMemory(Type.usize, mod, buffer, bit_offset, arena); }, + .Optional => { + assert(ty.isPtrLikeOptional()); + var buf: Type.Payload.ElemType = undefined; + const child = ty.optionalChild(&buf); + return readFromPackedMemory(child, mod, buffer, bit_offset, arena); + }, else => @panic("TODO implement readFromPackedMemory for more types"), } } From e17998b39655e8af5d2a1134fee7ca4850ad4389 Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:40:02 +0100 Subject: [PATCH 122/294] Argon2: properly handle outputs > 64 bytes in blake2Long() (#14914) Fixes #14912 --- lib/std/crypto/argon2.zig | 53 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/std/crypto/argon2.zig b/lib/std/crypto/argon2.zig index a95e75e538..0112e81c6a 100644 --- a/lib/std/crypto/argon2.zig +++ b/lib/std/crypto/argon2.zig @@ -138,40 +138,39 @@ fn initHash( } fn blake2bLong(out: []u8, in: []const u8) void { - var b2 = Blake2b512.init(.{ .expected_out_bits = math.min(512, out.len * 8) }); + const H = Blake2b512; + var outlen_bytes: [4]u8 = undefined; + mem.writeIntLittle(u32, &outlen_bytes, @intCast(u32, out.len)); - var buffer: [Blake2b512.digest_length]u8 = undefined; - mem.writeIntLittle(u32, buffer[0..4], @intCast(u32, out.len)); - b2.update(buffer[0..4]); - b2.update(in); - b2.final(&buffer); + var out_buf: [H.digest_length]u8 = undefined; - if (out.len <= Blake2b512.digest_length) { - mem.copy(u8, out, buffer[0..out.len]); + if (out.len <= H.digest_length) { + var h = H.init(.{ .expected_out_bits = out.len * 8 }); + h.update(&outlen_bytes); + h.update(in); + h.final(&out_buf); + mem.copy(u8, out, out_buf[0..out.len]); return; } - b2 = Blake2b512.init(.{}); - mem.copy(u8, out, buffer[0..32]); - var out_slice = out[32..]; - while (out_slice.len > Blake2b512.digest_length) : ({ - out_slice = out_slice[32..]; - b2 = Blake2b512.init(.{}); - }) { - b2.update(&buffer); - b2.final(&buffer); - mem.copy(u8, out_slice, buffer[0..32]); - } + var h = H.init(.{}); + h.update(&outlen_bytes); + h.update(in); + h.final(&out_buf); + var out_slice = out; + mem.copy(u8, out_slice, out_buf[0 .. H.digest_length / 2]); + out_slice = out_slice[H.digest_length / 2 ..]; - var r = Blake2b512.digest_length; - if (out.len % Blake2b512.digest_length > 0) { - r = ((out.len + 31) / 32) - 2; - b2 = Blake2b512.init(.{ .expected_out_bits = r * 8 }); + var in_buf: [H.digest_length]u8 = undefined; + while (out_slice.len > H.digest_length) { + mem.copy(u8, &in_buf, &out_buf); + H.hash(&in_buf, &out_buf, .{}); + mem.copy(u8, out_slice, out_buf[0 .. H.digest_length / 2]); + out_slice = out_slice[H.digest_length / 2 ..]; } - - b2.update(&buffer); - b2.final(&buffer); - mem.copy(u8, out_slice, buffer[0..r]); + mem.copy(u8, &in_buf, &out_buf); + H.hash(&in_buf, &out_buf, .{ .expected_out_bits = out_slice.len * 8 }); + mem.copy(u8, out_slice, out_buf[0..out_slice.len]); } fn initBlocks( From 4414f9c46e778a58c6d08df3c6ab604449abf38d Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Wed, 15 Mar 2023 04:50:45 +0100 Subject: [PATCH 123/294] Add Kyber post-quantum key encapsulation mechanism (#14902) Implementation of the IND-CCA2 post-quantum secure key encapsulation mechanism (KEM) CRYSTALS-Kyber, as submitted to the third round of the NIST Post-Quantum Cryptography (v3.02/"draft00"), and selected for standardisation. Co-authored-by: Frank Denis <124872+jedisct1@users.noreply.github.com> --- lib/std/crypto.zig | 7 + lib/std/crypto/benchmark.zig | 87 ++ lib/std/crypto/kyber_d00.zig | 1780 ++++++++++++++++++++++++++++++++++ 3 files changed, 1874 insertions(+) create mode 100644 lib/std/crypto/kyber_d00.zig diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index d70958f6dd..9b995480aa 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -68,6 +68,11 @@ pub const dh = struct { pub const X25519 = @import("crypto/25519/x25519.zig").X25519; }; +/// Key Encapsulation Mechanisms. +pub const kem = struct { + pub const kyber_d00 = @import("crypto/kyber_d00.zig"); +}; + /// Elliptic-curve arithmetic. pub const ecc = struct { pub const Curve25519 = @import("crypto/25519/curve25519.zig").Curve25519; @@ -240,6 +245,8 @@ test { _ = dh.X25519; + _ = kem.kyber_d00; + _ = ecc.Curve25519; _ = ecc.Edwards25519; _ = ecc.P256; diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 47ca24aa66..ff098c7804 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -203,6 +203,72 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime return throughput; } +const kems = [_]Crypto{ + Crypto{ .ty = crypto.kem.kyber_d00.Kyber512, .name = "kyber512d00" }, + Crypto{ .ty = crypto.kem.kyber_d00.Kyber768, .name = "kyber768d00" }, + Crypto{ .ty = crypto.kem.kyber_d00.Kyber1024, .name = "kyber1024d00" }, +}; + +pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 { + const key_pair = try Kem.KeyPair.create(null); + + var timer = try Timer.start(); + const start = timer.lap(); + { + var i: usize = 0; + while (i < kems_count) : (i += 1) { + const e = key_pair.public_key.encaps(null); + mem.doNotOptimizeAway(&e); + } + } + const end = timer.read(); + + const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; + const throughput = @floatToInt(u64, kems_count / elapsed_s); + + return throughput; +} + +pub fn benchmarkKemDecaps(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 { + const key_pair = try Kem.KeyPair.create(null); + + const e = key_pair.public_key.encaps(null); + + var timer = try Timer.start(); + const start = timer.lap(); + { + var i: usize = 0; + while (i < kems_count) : (i += 1) { + const ss2 = try key_pair.secret_key.decaps(&e.ciphertext); + mem.doNotOptimizeAway(&ss2); + } + } + const end = timer.read(); + + const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; + const throughput = @floatToInt(u64, kems_count / elapsed_s); + + return throughput; +} + +pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 { + var timer = try Timer.start(); + const start = timer.lap(); + { + var i: usize = 0; + while (i < kems_count) : (i += 1) { + const key_pair = try Kem.KeyPair.create(null); + mem.doNotOptimizeAway(&key_pair); + } + } + const end = timer.read(); + + const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s; + const throughput = @floatToInt(u64, kems_count / elapsed_s); + + return throughput; +} + const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.chacha_poly.ChaCha20Poly1305, .name = "chacha20Poly1305" }, Crypto{ .ty = crypto.aead.chacha_poly.XChaCha20Poly1305, .name = "xchacha20Poly1305" }, @@ -485,4 +551,25 @@ pub fn main() !void { try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput }); } } + + inline for (kems) |E| { + if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) { + const throughput = try benchmarkKem(E.ty, mode(1000)); + try stdout.print("{s:>17}: {:10} encaps/s\n", .{ E.name, throughput }); + } + } + + inline for (kems) |E| { + if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) { + const throughput = try benchmarkKemDecaps(E.ty, mode(25000)); + try stdout.print("{s:>17}: {:10} decaps/s\n", .{ E.name, throughput }); + } + } + + inline for (kems) |E| { + if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) { + const throughput = try benchmarkKemKeyGen(E.ty, mode(25000)); + try stdout.print("{s:>17}: {:10} keygen/s\n", .{ E.name, throughput }); + } + } } diff --git a/lib/std/crypto/kyber_d00.zig b/lib/std/crypto/kyber_d00.zig new file mode 100644 index 0000000000..21fdb6ff17 --- /dev/null +++ b/lib/std/crypto/kyber_d00.zig @@ -0,0 +1,1780 @@ +//! Implementation of the IND-CCA2 post-quantum secure key encapsulation +//! mechanism (KEM) CRYSTALS-Kyber, as submitted to the third round of the NIST +//! Post-Quantum Cryptography (v3.02/"draft00"), and selected for standardisation. +//! +//! Kyber will likely change before final standardisation. +//! +//! The namespace suffix (currently `_d00`) refers to the version currently +//! implemented, in accordance with the draft. It may not be updated if new +//! versions of the draft only include editorial changes. +//! +//! The suffix will eventually be removed once Kyber is finalized. +//! +//! Quoting from the CFRG I-D: +//! +//! Kyber is not a Diffie-Hellman (DH) style non-interactive key +//! agreement, but instead, Kyber is a Key Encapsulation Method (KEM). +//! In essence, a KEM is a Public-Key Encryption (PKE) scheme where the +//! plaintext cannot be specified, but is generated as a random key as +//! part of the encryption. A KEM can be transformed into an unrestricted +//! PKE using HPKE (RFC9180). On its own, a KEM can be used as a key +//! agreement method in TLS. +//! +//! Kyber is an IND-CCA2 secure KEM. It is constructed by applying a +//! Fujisaki--Okamato style transformation on InnerPKE, which is the +//! underlying IND-CPA secure Public Key Encryption scheme. We cannot +//! use InnerPKE directly, as its ciphertexts are malleable. +//! +//! ``` +//! F.O. transform +//! InnerPKE ----------------------> Kyber +//! IND-CPA IND-CCA2 +//! ``` +//! +//! Kyber is a lattice-based scheme. More precisely, its security is +//! based on the learning-with-errors-and-rounding problem in module +//! lattices (MLWER). The underlying polynomial ring R (defined in +//! Section 5) is chosen such that multiplication is very fast using the +//! number theoretic transform (NTT, see Section 5.1.3). +//! +//! An InnerPKE private key is a vector _s_ over R of length k which is +//! _small_ in a particular way. Here k is a security parameter akin to +//! the size of a prime modulus. For Kyber512, which targets AES-128's +//! security level, the value of k is 2. +//! +//! The public key consists of two values: +//! +//! * _A_ a uniformly sampled k by k matrix over R _and_ +//! +//! * _t = A s + e_, where e is a suitably small masking vector. +//! +//! Distinguishing between such A s + e and a uniformly sampled t is the +//! module learning-with-errors (MLWE) problem. If that is hard, then it +//! is also hard to recover the private key from the public key as that +//! would allow you to distinguish between those two. +//! +//! To save space in the public key, A is recomputed deterministically +//! from a seed _rho_. +//! +//! A ciphertext for a message m under this public key is a pair (c_1, +//! c_2) computed roughly as follows: +//! +//! c_1 = Compress(A^T r + e_1, d_u) +//! c_2 = Compress(t^T r + e_2 + Decompress(m, 1), d_v) +//! +//! where +//! +//! * e_1, e_2 and r are small blinds; +//! +//! * Compress(-, d) removes some information, leaving d bits per +//! coefficient and Decompress is such that Compress after Decompress +//! does nothing and +//! +//! * d_u, d_v are scheme parameters. +//! +//! Distinguishing such a ciphertext and uniformly sampled (c_1, c_2) is +//! an example of the full MLWER problem, see section 4.4 of [KyberV302]. +//! +//! To decrypt the ciphertext, one computes +//! +//! m = Compress(Decompress(c_2, d_v) - s^T Decompress(c_1, d_u), 1). +//! +//! It it not straight-forward to see that this formula is correct. In +//! fact, there is negligable but non-zero probability that a ciphertext +//! does not decrypt correctly given by the DFP column in Table 4. This +//! failure probability can be computed by a careful automated analysis +//! of the probabilities involved, see kyber_failure.py of [SecEst]. +//! +//! [KyberV302](https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf) +//! [I-D](https://github.com/bwesterb/draft-schwabe-cfrg-kyber) +//! [SecEst](https://github.com/pq-crystals/security-estimates) + +// TODO +// +// - The bottleneck in Kyber are the various hash/xof calls: +// - Optimize Zig's keccak implementation. +// - Use SIMD to compute keccak in parallel. +// - Can we track bounds of coefficients using comptime types without +// duplicating code? +// - Would be neater to have tests closer to the thing under test. +// - When generating a keypair, we have a copy of the inner public key with +// its large matrix A in both the public key and the private key. In Go we +// can just have a pointer in the private key to the public key, but +// how do we do this elegantly in Zig? + +const std = @import("std"); +const builtin = @import("builtin"); + +const testing = std.testing; +const assert = std.debug.assert; +const crypto = std.crypto; +const math = std.math; +const mem = std.mem; +const RndGen = std.rand.DefaultPrng; +const sha3 = crypto.hash.sha3; + +// Q is the parameter q ≡ 3329 = 2¹¹ + 2¹⁰ + 2⁸ + 1. +const Q: i16 = 3329; + +// Montgomery R +const R: i32 = 1 << 16; + +// Parameter n, degree of polynomials. +const N: usize = 256; + +// Size of "small" vectors used in encryption blinds. +const eta2: u8 = 2; + +const Params = struct { + name: []const u8, + + // Width and height of the matrix A. + k: u8, + + // Size of "small" vectors used in private key and encryption blinds. + eta1: u8, + + // How many bits to retain of u, the private-key independent part + // of the ciphertext. + du: u8, + + // How many bits to retain of v, the private-key dependent part + // of the ciphertext. + dv: u8, +}; + +pub const Kyber512 = Kyber(.{ + .name = "Kyber512", + .k = 2, + .eta1 = 3, + .du = 10, + .dv = 4, +}); + +pub const Kyber768 = Kyber(.{ + .name = "Kyber768", + .k = 3, + .eta1 = 2, + .du = 10, + .dv = 4, +}); + +pub const Kyber1024 = Kyber(.{ + .name = "Kyber1024", + .k = 4, + .eta1 = 2, + .du = 11, + .dv = 5, +}); + +const modes = [_]type{ Kyber512, Kyber768, Kyber1024 }; +const h_length: usize = 32; +const inner_seed_length: usize = 32; +const common_encaps_seed_length: usize = 32; +const common_shared_key_size: usize = 32; + +fn Kyber(comptime p: Params) type { + return struct { + // Size of a ciphertext, in bytes. + pub const ciphertext_length = Poly.compressedSize(p.du) * p.k + Poly.compressedSize(p.dv); + + const Self = @This(); + const V = Vec(p.k); + const M = Mat(p.k); + + /// Length (in bytes) of a shared secret. + pub const shared_length = common_shared_key_size; + /// Length (in bytes) of a seed for deterministic encapsulation. + pub const encaps_seed_length = common_encaps_seed_length; + /// Length (in bytes) of a seed for key generation. + pub const seed_length: usize = inner_seed_length + shared_length; + /// Algorithm name. + pub const name = p.name; + + /// A shared secret, and an encapsulated (encrypted) representation of it. + pub const EncapsulatedSecret = struct { + shared_secret: [shared_length]u8, + ciphertext: [ciphertext_length]u8, + }; + + /// A Kyber public key. + pub const PublicKey = struct { + pk: InnerPk, + + // Cached + hpk: [h_length]u8, // H(pk) + + /// Size of a serialized representation of the key, in bytes. + pub const bytes_length = InnerPk.bytes_length; + + /// Generates a shared secret, and encapsulates it for the public key. + /// If `seed` is `null`, a random seed is used. This is recommended. + /// If `seed` is set, encapsulation is deterministic. + pub fn encaps(pk: PublicKey, seed_: ?[encaps_seed_length]u8) EncapsulatedSecret { + const seed = seed_ orelse seed: { + var random_seed: [encaps_seed_length]u8 = undefined; + crypto.random.bytes(&random_seed); + break :seed random_seed; + }; + + var m: [inner_plaintext_length]u8 = undefined; + + // m = H(seed) + var h = sha3.Sha3_256.init(.{}); + h.update(&seed); + h.final(&m); + + // (K', r) = G(m ‖ H(pk)) + var kr: [inner_plaintext_length + h_length]u8 = undefined; + var g = sha3.Sha3_512.init(.{}); + g.update(&m); + g.update(&pk.hpk); + g.final(&kr); + + // c = innerEncrypy(pk, m, r) + const ct = pk.pk.encrypt(&m, kr[32..64]); + + // Compute H(c) and put in second slot of kr, which will be (K', H(c)). + h = sha3.Sha3_256.init(.{}); + h.update(&ct); + h.final(kr[32..64]); + + // K = KDF(K' ‖ H(c)) + var kdf = sha3.Shake256.init(.{}); + kdf.update(&kr); + var ss: [shared_length]u8 = undefined; + kdf.squeeze(&ss); + + return EncapsulatedSecret{ + .shared_secret = ss, + .ciphertext = ct, + }; + } + + /// Serializes the key into a byte array. + pub fn toBytes(pk: PublicKey) [bytes_length]u8 { + return pk.pk.toBytes(); + } + + /// Deserializes the key from a byte array. + pub fn fromBytes(buf: *const [bytes_length]u8) !PublicKey { + var ret: PublicKey = undefined; + ret.pk = InnerPk.fromBytes(buf[0..InnerPk.bytes_length]); + + var h = sha3.Sha3_256.init(.{}); + h.update(buf); + h.final(&ret.hpk); + return ret; + } + }; + + /// A Kyber secret key. + pub const SecretKey = struct { + sk: InnerSk, + pk: InnerPk, + hpk: [h_length]u8, // H(pk) + z: [shared_length]u8, + + /// Size of a serialized representation of the key, in bytes. + pub const bytes_length: usize = + InnerSk.bytes_length + InnerPk.bytes_length + h_length + shared_length; + + /// Decapsulates the shared secret within ct using the private key. + pub fn decaps(sk: SecretKey, ct: *const [ciphertext_length]u8) ![shared_length]u8 { + // m' = innerDec(ct) + const m2 = sk.sk.decrypt(ct); + + // (K'', r') = G(m' ‖ H(pk)) + var kr2: [64]u8 = undefined; + var g = sha3.Sha3_512.init(.{}); + g.update(&m2); + g.update(&sk.hpk); + g.final(&kr2); + + // ct' = innerEnc(pk, m', r') + const ct2 = sk.pk.encrypt(&m2, kr2[32..64]); + + // Compute H(ct) and put in the second slot of kr2 which will be (K'', H(ct)). + var h = sha3.Sha3_256.init(.{}); + h.update(ct); + h.final(kr2[32..64]); + + // Replace K'' by z in the first slot of kr2 if ct ≠ ct'. + cmov(32, kr2[0..32], sk.z, ctneq(ciphertext_length, ct.*, ct2)); + + // K = KDF(K''/z, H(c)) + var kdf = sha3.Shake256.init(.{}); + var ss: [shared_length]u8 = undefined; + kdf.update(&kr2); + kdf.squeeze(&ss); + return ss; + } + + /// Serializes the key into a byte array. + pub fn toBytes(sk: SecretKey) [bytes_length]u8 { + return sk.sk.toBytes() ++ sk.pk.toBytes() ++ sk.hpk ++ sk.z; + } + + /// Deserializes the key from a byte array. + pub fn fromBytes(buf: *const [bytes_length]u8) !SecretKey { + var ret: SecretKey = undefined; + comptime var s: usize = 0; + ret.sk = InnerSk.fromBytes(buf[s .. s + InnerSk.bytes_length]); + s += InnerSk.bytes_length; + ret.pk = InnerPk.fromBytes(buf[s .. s + InnerPk.bytes_length]); + s += InnerPk.bytes_length; + mem.copy(u8, &ret.hpk, buf[s .. s + h_length]); + s += h_length; + mem.copy(u8, &ret.z, buf[s .. s + shared_length]); + return ret; + } + }; + + /// A Kyber key pair. + pub const KeyPair = struct { + secret_key: SecretKey, + public_key: PublicKey, + + /// Create a new key pair. + /// If seed is null, a random seed will be generated. + /// If a seed is provided, the key pair will be determinsitic. + pub fn create(seed_: ?[seed_length]u8) !KeyPair { + const seed = seed_ orelse sk: { + var random_seed: [seed_length]u8 = undefined; + crypto.random.bytes(&random_seed); + break :sk random_seed; + }; + var ret: KeyPair = undefined; + mem.copy(u8, &ret.secret_key.z, seed[inner_seed_length..seed_length]); + + // Generate inner key + innerKeyFromSeed( + seed[0..inner_seed_length].*, + &ret.public_key.pk, + &ret.secret_key.sk, + ); + ret.secret_key.pk = ret.public_key.pk; + + // Copy over z from seed. + mem.copy(u8, &ret.secret_key.z, seed[inner_seed_length..seed_length]); + + // Compute H(pk) + var h = sha3.Sha3_256.init(.{}); + h.update(&ret.public_key.pk.toBytes()); + h.final(&ret.secret_key.hpk); + ret.public_key.hpk = ret.secret_key.hpk; + + return ret; + } + }; + + // Size of plaintexts of the in + const inner_plaintext_length: usize = Poly.compressedSize(1); + + const InnerPk = struct { + rho: [32]u8, // ρ, the seed for the matrix A + th: V, // NTT(t), normalized + + // Cached values + aT: M, + + const bytes_length = V.bytes_length + 32; + + fn encrypt( + pk: InnerPk, + pt: *const [inner_plaintext_length]u8, + seed: *const [32]u8, + ) [ciphertext_length]u8 { + // Sample r, e₁ and e₂ appropriately + const rh = V.noise(p.eta1, 0, seed).ntt().barrettReduce(); + const e1 = V.noise(eta2, p.k, seed); + const e2 = Poly.noise(eta2, 2 * p.k, seed); + + // Next we compute u = Aᵀ r + e₁. First Aᵀ. + var u: V = undefined; + for (0..p.k) |i| { + // Note that coefficients of r are bounded by q and those of Aᵀ + // are bounded by 4.5q and so their product is bounded by 2¹⁵q + // as required for multiplication. + u.ps[i] = pk.aT.vs[i].dotHat(rh); + } + + // Aᵀ and r were not in Montgomery form, so the Montgomery + // multiplications in the inner product added a factor R⁻¹ which + // the InvNTT cancels out. + u = u.barrettReduce().invNTT().add(e1).normalize(); + + // Next, compute v = + e₂ + Decompress_q(m, 1) + const v = pk.th.dotHat(rh).barrettReduce().invNTT() + .add(Poly.decompress(1, pt)).add(e2).normalize(); + + return u.compress(p.du) ++ v.compress(p.dv); + } + + fn toBytes(pk: InnerPk) [bytes_length]u8 { + return pk.th.toBytes() ++ pk.rho; + } + + fn fromBytes(buf: *const [bytes_length]u8) InnerPk { + var ret: InnerPk = undefined; + ret.th = V.fromBytes(buf[0..V.bytes_length]).normalize(); + mem.copy(u8, &ret.rho, buf[V.bytes_length..bytes_length]); + ret.aT = M.uniform(ret.rho, true); + return ret; + } + }; + + // Private key of the inner PKE + const InnerSk = struct { + sh: V, // NTT(s), normalized + const bytes_length = V.bytes_length; + + fn decrypt(sk: InnerSk, ct: *const [ciphertext_length]u8) [inner_plaintext_length]u8 { + const u = V.decompress(p.du, ct[0..comptime V.compressedSize(p.du)]); + const v = Poly.decompress( + p.dv, + ct[comptime V.compressedSize(p.du)..ciphertext_length], + ); + + // Compute m = v - + return v.sub(sk.sh.dotHat(u.ntt()).barrettReduce().invNTT()) + .normalize().compress(1); + } + + fn toBytes(sk: InnerSk) [bytes_length]u8 { + return sk.sh.toBytes(); + } + + fn fromBytes(buf: *const [bytes_length]u8) InnerSk { + var ret: InnerSk = undefined; + ret.sh = V.fromBytes(buf).normalize(); + return ret; + } + }; + + // Derives inner PKE keypair from given seed. + fn innerKeyFromSeed(seed: [inner_seed_length]u8, pk: *InnerPk, sk: *InnerSk) void { + var expanded_seed: [64]u8 = undefined; + + var h = sha3.Sha3_512.init(.{}); + h.update(&seed); + h.final(&expanded_seed); + mem.copy(u8, &pk.rho, expanded_seed[0..32]); + const sigma = expanded_seed[32..64]; + pk.aT = M.uniform(pk.rho, false); // Expand ρ to A; we'll transpose later on + + // Sample secret vector s. + sk.sh = V.noise(p.eta1, 0, sigma).ntt().normalize(); + + const eh = Vec(p.k).noise(p.eta1, p.k, sigma).ntt(); // sample blind e. + var th: V = undefined; + + // Next, we compute t = A s + e. + for (0..p.k) |i| { + // Note that coefficients of s are bounded by q and those of A + // are bounded by 4.5q and so their product is bounded by 2¹⁵q + // as required for multiplication. + // A and s were not in Montgomery form, so the Montgomery + // multiplications in the inner product added a factor R⁻¹ which + // we'll cancel out with toMont(). This will also ensure the + // coefficients of th are bounded in absolute value by q. + th.ps[i] = pk.aT.vs[i].dotHat(sk.sh).toMont(); + } + + pk.th = th.add(eh).normalize(); // bounded by 8q + pk.aT = pk.aT.transpose(); + } + }; +} + +// R mod q +const r_mod_q: i32 = @rem(@as(i32, R), Q); + +// R² mod q +const r2_mod_q: i32 = @rem(r_mod_q * r_mod_q, Q); + +// ζ is the degree 256 primitive root of unity used for the NTT. +const zeta: i16 = 17; + +// (128)⁻¹ R². Used in inverse NTT. +const r2_over_128: i32 = @mod(invertMod(128, Q) * r2_mod_q, Q); + +// zetas lists precomputed powers of the primitive root of unity in +// Montgomery representation used for the NTT: +// +// zetas[i] = ζᵇʳᵛ⁽ⁱ⁾ R mod q +// +// where ζ = 17, brv(i) is the bitreversal of a 7-bit number and R=2¹⁶ mod q. +const zetas = computeZetas(); + +// invNTTReductions keeps track of which coefficients to apply Barrett +// reduction to in Poly.invNTT(). +// +// Generated lazily: once a butterfly is computed which is about to +// overflow the i16, the largest coefficient is reduced. If that is +// not enough, the other coefficient is reduced as well. +// +// This is actually optimal, as proven in https://eprint.iacr.org/2020/1377.pdf +// TODO generate comptime? +const inv_ntt_reductions = [_]i16{ + -1, // after layer 1 + -1, // after layer 2 + 16, + 17, + 48, + 49, + 80, + 81, + 112, + 113, + 144, + 145, + 176, + 177, + 208, + 209, + 240, 241, -1, // after layer 3 + 0, 1, 32, + 33, 34, 35, + 64, 65, 96, + 97, 98, 99, + 128, 129, + 160, 161, 162, 163, 192, 193, 224, 225, 226, 227, -1, // after layer 4 + 2, 3, 66, 67, 68, 69, 70, 71, 130, 131, 194, + 195, 196, 197, + 198, 199, -1, // after layer 5 + 4, 5, 6, + 7, 132, 133, + 134, 135, 136, + 137, 138, 139, + 140, 141, + 142, 143, -1, // after layer 6 + -1, // after layer 7 +}; + +test "invNTTReductions bounds" { + // Checks whether the reductions proposed by invNTTReductions + // don't overflow during invNTT(). + var xs = [_]i32{1} ** 256; // start at |x| ≤ q + + var r: usize = 0; + var layer: math.Log2Int(usize) = 1; + while (layer < 8) : (layer += 1) { + const w = @as(usize, 1) << layer; + var i: usize = 0; + + while (i + w < 256) { + xs[i] = xs[i] + xs[i + w]; + try testing.expect(xs[i] <= 9); // we can't exceed 9q + xs[i + w] = 1; + i += 1; + if (@mod(i, w) == 0) { + i += w; + } + } + + while (true) { + const j = inv_ntt_reductions[r]; + r += 1; + if (j < 0) { + break; + } + xs[@intCast(usize, j)] = 1; + } + } +} + +// Extended euclidean algorithm. +// +// For a, b finds x, y such that x a + y b = gcd(a, b). Used to compute +// modular inverse. +fn eea(a: anytype, b: @TypeOf(a)) EeaResult(@TypeOf(a)) { + if (a == 0) { + return .{ .gcd = b, .x = 0, .y = 1 }; + } + const r = eea(@rem(b, a), a); + return .{ .gcd = r.gcd, .x = r.y - @divTrunc(b, a) * r.x, .y = r.x }; +} + +fn EeaResult(comptime T: type) type { + return struct { gcd: T, x: T, y: T }; +} + +// Returns least common multiple of a and b. +fn lcm(a: anytype, b: @TypeOf(a)) @TypeOf(a) { + const r = eea(a, b); + return a * b / r.gcd; +} + +// Invert modulo p. +fn invertMod(a: anytype, p: @TypeOf(a)) @TypeOf(a) { + const r = eea(a, p); + assert(r.gcd == 1); + return r.x; +} + +// Reduce mod q for testing. +fn modQ32(x: i32) i16 { + var y = @intCast(i16, @rem(x, @as(i32, Q))); + if (y < 0) { + y += Q; + } + return y; +} + +// Given -2¹⁵ q ≤ x < 2¹⁵ q, returns -q < y < q with x 2⁻¹⁶ = y (mod q). +fn montReduce(x: i32) i16 { + const qInv = comptime invertMod(@as(i32, Q), R); + // This is Montgomery reduction with R=2¹⁶. + // + // Note gcd(2¹⁶, q) = 1 as q is prime. Write q' := 62209 = q⁻¹ mod R. + // First we compute + // + // m := ((x mod R) q') mod R + // = x q' mod R + // = int16(x q') + // = int16(int32(x) * int32(q')) + // + // Note that x q' might be as big as 2³² and could overflow the int32 + // multiplication in the last line. However for any int32s a and b, + // we have int32(int64(a)*int64(b)) = int32(a*b) and so the result is ok. + const m = @truncate(i16, @truncate(i32, x *% qInv)); + + // Note that x - m q is divisable by R; indeed modulo R we have + // + // x - m q ≡ x - x q' q ≡ x - x q⁻¹ q ≡ x - x = 0. + // + // We return y := (x - m q) / R. Note that y is indeed correct as + // modulo q we have + // + // y ≡ x R⁻¹ - m q R⁻¹ = x R⁻¹ + // + // and as both 2¹⁵ q ≤ m q, x < 2¹⁵ q, we have + // 2¹⁶ q ≤ x - m q < 2¹⁶ and so q ≤ (x - m q) / R < q as desired. + const yR = x - @as(i32, m) * @as(i32, Q); + return @bitCast(i16, @truncate(u16, @bitCast(u32, yR) >> 16)); +} + +test "Test montReduce" { + var rnd = RndGen.init(0); + for (0..1000) |_| { + const bound = comptime @as(i32, Q) * (1 << 15); + const x = rnd.random().intRangeLessThan(i32, -bound, bound); + const y = montReduce(x); + try testing.expect(-Q < y and y < Q); + try testing.expectEqual(modQ32(x), modQ32(@as(i32, y) * R)); + } +} + +// Given any x, return x R mod q where R=2¹⁶. +fn feToMont(x: i16) i16 { + // Note |1353 x| ≤ 1353 2¹⁵ ≤ 13318 q ≤ 2¹⁵ q and so we're within + // the bounds of montReduce. + return montReduce(@as(i32, x) * r2_mod_q); +} + +test "Test feToMont" { + var x: i32 = -(1 << 15); + while (x < 1 << 15) : (x += 1) { + const y = feToMont(@intCast(i16, x)); + try testing.expectEqual(modQ32(@as(i32, y)), modQ32(x * r_mod_q)); + } +} + +// Given any x, compute 0 ≤ y ≤ q with x = y (mod q). +// +// Beware: we might have feBarrettReduce(x) = q ≠ 0 for some x. In fact, +// this happens if and only if x = -nq for some positive integer n. +fn feBarrettReduce(x: i16) i16 { + // This is standard Barrett reduction. + // + // For any x we have x mod q = x - ⌊x/q⌋ q. We will use 20159/2²⁶ as + // an approximation of 1/q. Note that 0 ≤ 20159/2²⁶ - 1/q ≤ 0.135/2²⁶ + // and so | x 20156/2²⁶ - x/q | ≤ 2⁻¹⁰ for |x| ≤ 2¹⁶. For all x + // not a multiple of q, the number x/q is further than 1/q from any integer + // and so ⌊x 20156/2²⁶⌋ = ⌊x/q⌋. If x is a multiple of q and x is positive, + // then x 20156/2²⁶ is larger than x/q so ⌊x 20156/2²⁶⌋ = ⌊x/q⌋ as well. + // Finally, if x is negative multiple of q, then ⌊x 20156/2²⁶⌋ = ⌊x/q⌋-1. + // Thus + // [ q if x=-nq for pos. integer n + // x - ⌊x 20156/2²⁶⌋ q = [ + // [ x mod q otherwise + // + // To actually compute this, note that + // + // ⌊x 20156/2²⁶⌋ = (20159 x) >> 26. + return x -% @intCast(i16, (@as(i32, x) * 20159) >> 26) *% Q; +} + +test "Test Barrett reduction" { + var x: i32 = -(1 << 15); + while (x < 1 << 15) : (x += 1) { + var y1 = feBarrettReduce(@intCast(i16, x)); + const y2 = @mod(@intCast(i16, x), Q); + if (x < 0 and @rem(-x, Q) == 0) { + y1 -= Q; + } + try testing.expectEqual(y1, y2); + } +} + +// Returns x if x < q and x - q otherwise. Assumes x ≥ -29439. +fn csubq(x: i16) i16 { + var r = x; + r -= Q; + r += (r >> 15) & Q; + return r; +} + +test "Test csubq" { + var x: i32 = -29439; + while (x < 1 << 15) : (x += 1) { + const y1 = csubq(@intCast(i16, x)); + var y2 = @intCast(i16, x); + if (@intCast(i16, x) >= Q) { + y2 -= Q; + } + try testing.expectEqual(y1, y2); + } +} + +// Compute a^s mod p. +fn mpow(a: anytype, s: @TypeOf(a), p: @TypeOf(a)) @TypeOf(a) { + var ret: @TypeOf(a) = 1; + var s2 = s; + var a2 = a; + + while (true) { + if (s2 & 1 == 1) { + ret = @mod(ret * a2, p); + } + s2 >>= 1; + if (s2 == 0) { + break; + } + a2 = @mod(a2 * a2, p); + } + return ret; +} + +// Computes zetas table used by ntt and invNTT. +fn computeZetas() [128]i16 { + @setEvalBranchQuota(10000); + var ret: [128]i16 = undefined; + for (&ret, 0..) |*r, i| { + const t = @intCast(i16, mpow(@as(i32, zeta), @bitReverse(@intCast(u7, i)), Q)); + r.* = csubq(feBarrettReduce(feToMont(t))); + } + return ret; +} + +// An element of our base ring R which are polynomials over ℤ_q +// modulo the equation Xᴺ = -1, where q=3329 and N=256. +// +// This type is also used to store NTT-transformed polynomials, +// see Poly.NTT(). +// +// Coefficients aren't always reduced. See Normalize(). +const Poly = struct { + cs: [N]i16, + + const bytes_length = N / 2 * 3; + const zero: Poly = .{ .cs = .{0} ** N }; + + fn add(a: Poly, b: Poly) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = a.cs[i] + b.cs[i]; + } + return ret; + } + + fn sub(a: Poly, b: Poly) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = a.cs[i] - b.cs[i]; + } + return ret; + } + + // For testing, generates a random polynomial with for each + // coefficient |x| ≤ q. + fn randAbsLeqQ(rnd: anytype) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = rnd.random().intRangeAtMost(i16, -Q, Q); + } + return ret; + } + + // For testing, generates a random normalized polynomial. + fn randNormalized(rnd: anytype) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = rnd.random().intRangeLessThan(i16, 0, Q); + } + return ret; + } + + // Executes a forward "NTT" on p. + // + // Assumes the coefficients are in absolute value ≤q. The resulting + // coefficients are in absolute value ≤7q. If the input is in Montgomery + // form, then the result is in Montgomery form and so (by linearity of the NTT) + // if the input is in regular form, then the result is also in regular form. + fn ntt(a: Poly) Poly { + // Note that ℤ_q does not have a primitive 512ᵗʰ root of unity (as 512 + // does not divide into q-1) and so we cannot do a regular NTT. ℤ_q + // does have a primitive 256ᵗʰ root of unity, the smallest of which + // is ζ := 17. + // + // Recall that our base ring R := ℤ_q[x] / (x²⁵⁶ + 1). The polynomial + // x²⁵⁶+1 will not split completely (as its roots would be 512ᵗʰ roots + // of unity.) However, it does split almost (using ζ¹²⁸ = -1): + // + // x²⁵⁶ + 1 = (x²)¹²⁸ - ζ¹²⁸ + // = ((x²)⁶⁴ - ζ⁶⁴)((x²)⁶⁴ + ζ⁶⁴) + // = ((x²)³² - ζ³²)((x²)³² + ζ³²)((x²)³² - ζ⁹⁶)((x²)³² + ζ⁹⁶) + // ⋮ + // = (x² - ζ)(x² + ζ)(x² - ζ⁶⁵)(x² + ζ⁶⁵) … (x² + ζ¹²⁷) + // + // Note that the powers of ζ that appear (from the second line down) are + // in binary + // + // 0100000 1100000 + // 0010000 1010000 0110000 1110000 + // 0001000 1001000 0101000 1101000 0011000 1011000 0111000 1111000 + // … + // + // That is: brv(2), brv(3), brv(4), …, where brv(x) denotes the 7-bit + // bitreversal of x. These powers of ζ are given by the Zetas array. + // + // The polynomials x² ± ζⁱ are irreducible and coprime, hence by + // the Chinese Remainder Theorem we know + // + // ℤ_q[x]/(x²⁵⁶+1) → ℤ_q[x]/(x²-ζ) x … x ℤ_q[x]/(x²+ζ¹²⁷) + // + // given by a ↦ ( a mod x²-ζ, …, a mod x²+ζ¹²⁷ ) + // is an isomorphism, which is the "NTT". It can be efficiently computed by + // + // + // a ↦ ( a mod (x²)⁶⁴ - ζ⁶⁴, a mod (x²)⁶⁴ + ζ⁶⁴ ) + // ↦ ( a mod (x²)³² - ζ³², a mod (x²)³² + ζ³², + // a mod (x²)⁹⁶ - ζ⁹⁶, a mod (x²)⁹⁶ + ζ⁹⁶ ) + // + // et cetera + // If N was 8 then this can be pictured in the following diagram: + // + // https://cnx.org/resources/17ee4dfe517a6adda05377b25a00bf6e6c93c334/File0026.png + // + // Each cross is a Cooley-Tukey butterfly: it's the map + // + // (a, b) ↦ (a + ζb, a - ζb) + // + // for the appropriate power ζ for that column and row group. + var p = a; + var k: usize = 0; // index into zetas + + var l = N >> 1; + while (l > 1) : (l >>= 1) { + // On the nᵗʰ iteration of the l-loop, the absolute value of the + // coefficients are bounded by nq. + + // offset effectively loops over the row groups in this column; it is + // the first row in the row group. + var offset: usize = 0; + while (offset < N - l) : (offset += 2 * l) { + k += 1; + const z = @as(i32, zetas[k]); + + // j loops over each butterfly in the row group. + for (offset..offset + l) |j| { + const t = montReduce(z * @as(i32, p.cs[j + l])); + p.cs[j + l] = p.cs[j] - t; + p.cs[j] += t; + } + } + } + + return p; + } + + // Executes an inverse "NTT" on p and multiply by the Montgomery factor R. + // + // Assumes the coefficients are in absolute value ≤q. The resulting + // coefficients are in absolute value ≤q. If the input is in Montgomery + // form, then the result is in Montgomery form and so (by linearity) + // if the input is in regular form, then the result is also in regular form. + fn invNTT(a: Poly) Poly { + var k: usize = 127; // index into zetas + var r: usize = 0; // index into invNTTReductions + var p = a; + + // We basically do the oppposite of NTT, but postpone dividing by 2 in the + // inverse of the Cooley-Tukey butterfly and accumulate that into a big + // division by 2⁷ at the end. See the comments in the ntt() function. + + var l: usize = 2; + while (l < N) : (l <<= 1) { + var offset: usize = 0; + while (offset < N - l) : (offset += 2 * l) { + // As we're inverting, we need powers of ζ⁻¹ (instead of ζ). + // To be precise, we need ζᵇʳᵛ⁽ᵏ⁾⁻¹²⁸. However, as ζ⁻¹²⁸ = -1, + // we can use the existing zetas table instead of + // keeping a separate invZetas table as in Dilithium. + + const minZeta = @as(i32, zetas[k]); + k -= 1; + + for (offset..offset + l) |j| { + // Gentleman-Sande butterfly: (a, b) ↦ (a + b, ζ(a-b)) + const t = p.cs[j + l] - p.cs[j]; + p.cs[j] += p.cs[j + l]; + p.cs[j + l] = montReduce(minZeta * @as(i32, t)); + + // Note that if we had |a| < αq and |b| < βq before the + // butterfly, then now we have |a| < (α+β)q and |b| < q. + } + } + + // We let the invNTTReductions instruct us which coefficients to + // Barrett reduce. + while (true) { + const i = inv_ntt_reductions[r]; + r += 1; + if (i < 0) { + break; + } + p.cs[@intCast(usize, i)] = feBarrettReduce(p.cs[@intCast(usize, i)]); + } + } + + for (0..N) |j| { + // Note 1441 = (128)⁻¹ R². The coefficients are bounded by 9q, so + // as 1441 * 9 ≈ 2¹⁴ < 2¹⁵, we're within the required bounds + // for montReduce(). + p.cs[j] = montReduce(r2_over_128 * @as(i32, p.cs[j])); + } + + return p; + } + + // Normalizes coefficients. + // + // Ensures each coefficient is in {0, …, q-1}. + fn normalize(a: Poly) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = csubq(feBarrettReduce(a.cs[i])); + } + return ret; + } + + // Put p in Montgomery form. + fn toMont(a: Poly) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = feToMont(a.cs[i]); + } + return ret; + } + + // Barret reduce coefficients. + // + // Beware, this does not fully normalize coefficients. + fn barrettReduce(a: Poly) Poly { + var ret: Poly = undefined; + for (0..N) |i| { + ret.cs[i] = feBarrettReduce(a.cs[i]); + } + return ret; + } + + fn compressedSize(comptime d: u8) usize { + return @divTrunc(N * d, 8); + } + + // Returns packed Compress_q(p, d). + // + // Assumes p is normalized. + fn compress(p: Poly, comptime d: u8) [compressedSize(d)]u8 { + @setEvalBranchQuota(10000); + const q_over_2: u32 = comptime @divTrunc(Q, 2); // (q-1)/2 + const two_d_min_1: u32 = comptime (1 << d) - 1; // 2ᵈ-1 + var in_off: usize = 0; + var out_off: usize = 0; + + const batch_size: usize = comptime lcm(@as(i16, d), 8); + const in_batch_size: usize = comptime batch_size / d; + const out_batch_size: usize = comptime batch_size / 8; + + const out_length: usize = comptime @divTrunc(N * d, 8); + comptime assert(out_length * 8 == d * N); + var out = [_]u8{0} ** out_length; + + while (in_off < N) { + // First we compress into in. + var in: [in_batch_size]u16 = undefined; + inline for (0..in_batch_size) |i| { + // Compress_q(x, d) = ⌈(2ᵈ/q)x⌋ mod⁺ 2ᵈ + // = ⌊(2ᵈ/q)x+½⌋ mod⁺ 2ᵈ + // = ⌊((x << d) + q/2) / q⌋ mod⁺ 2ᵈ + // = DIV((x << d) + q/2, q) & ((1< 0) { + const out_shift = comptime 8 - todo; + out[out_off + j] |= @truncate(u8, (in[i] >> in_shift) << out_shift); + + const done = comptime @min(@min(d, todo), d - in_shift); + todo -= done; + in_shift += done; + + if (in_shift == d) { + in_shift = 0; + i += 1; + } + } + } + + in_off += in_batch_size; + out_off += out_batch_size; + } + + return out; + } + + // Set p to Decompress_q(m, d). + fn decompress(comptime d: u8, in: *const [compressedSize(d)]u8) Poly { + @setEvalBranchQuota(10000); + const inLen = comptime @divTrunc(N * d, 8); + comptime assert(inLen * 8 == d * N); + var ret: Poly = undefined; + var in_off: usize = 0; + var out_off: usize = 0; + + const batch_size: usize = comptime lcm(@as(i16, d), 8); + const in_batch_size: usize = comptime batch_size / 8; + const out_batch_size: usize = comptime batch_size / d; + + while (out_off < N) { + comptime var in_shift: usize = 0; + comptime var j: usize = 0; + comptime var i: usize = 0; + inline while (i < out_batch_size) : (i += 1) { + // First, unpack next coefficient. + comptime var todo = d; + var out: u16 = 0; + + inline while (todo > 0) { + const out_shift = comptime d - todo; + const m = comptime (1 << d) - 1; + out |= (@as(u16, in[in_off + j] >> in_shift) << out_shift) & m; + + const done = comptime @min(@min(8, todo), 8 - in_shift); + todo -= done; + in_shift += done; + + if (in_shift == 8) { + in_shift = 0; + j += 1; + } + } + + // Decompress_q(x, d) = ⌈(q/2ᵈ)x⌋ + // = ⌊(q/2ᵈ)x+½⌋ + // = ⌊(qx + 2ᵈ⁻¹)/2ᵈ⌋ + // = (qx + (1<<(d-1))) >> d + const qx = @as(u32, out) * @as(u32, Q); + ret.cs[out_off + i] = @intCast(i16, (qx + (1 << (d - 1))) >> d); + } + + in_off += in_batch_size; + out_off += out_batch_size; + } + + return ret; + } + + // Returns the "pointwise" multiplication a o b. + // + // That is: invNTT(a o b) = invNTT(a) * invNTT(b). Assumes a and b are in + // Montgomery form. Products between coefficients of a and b must be strictly + // bounded in absolute value by 2¹⁵q. a o b will be in Montgomery form and + // bounded in absolute value by 2q. + fn mulHat(a: Poly, b: Poly) Poly { + // Recall from the discussion in ntt(), that a transformed polynomial is + // an element of ℤ_q[x]/(x²-ζ) x … x ℤ_q[x]/(x²+ζ¹²⁷); + // that is: 128 degree-one polynomials instead of simply 256 elements + // from ℤ_q as in the regular NTT. So instead of pointwise multiplication, + // we multiply the 128 pairs of degree-one polynomials modulo the + // right equation: + // + // (a₁ + a₂x)(b₁ + b₂x) = a₁b₁ + a₂b₂ζ' + (a₁b₂ + a₂b₁)x, + // + // where ζ' is the appropriate power of ζ. + + var p: Poly = undefined; + var k: usize = 64; + var i: usize = 0; + while (i < N) : (i += 4) { + const z = @as(i32, zetas[k]); + k += 1; + + const a1b1 = montReduce(@as(i32, a.cs[i + 1]) * @as(i32, b.cs[i + 1])); + const a0b0 = montReduce(@as(i32, a.cs[i]) * @as(i32, b.cs[i])); + const a1b0 = montReduce(@as(i32, a.cs[i + 1]) * @as(i32, b.cs[i])); + const a0b1 = montReduce(@as(i32, a.cs[i]) * @as(i32, b.cs[i + 1])); + + p.cs[i] = montReduce(a1b1 * z) + a0b0; + p.cs[i + 1] = a0b1 + a1b0; + + const a3b3 = montReduce(@as(i32, a.cs[i + 3]) * @as(i32, b.cs[i + 3])); + const a2b2 = montReduce(@as(i32, a.cs[i + 2]) * @as(i32, b.cs[i + 2])); + const a3b2 = montReduce(@as(i32, a.cs[i + 3]) * @as(i32, b.cs[i + 2])); + const a2b3 = montReduce(@as(i32, a.cs[i + 2]) * @as(i32, b.cs[i + 3])); + + p.cs[i + 2] = a2b2 - montReduce(a3b3 * z); + p.cs[i + 3] = a2b3 + a3b2; + } + + return p; + } + + // Sample p from a centered binomial distribution with n=2η and p=½ - viz: + // coefficients are in {-η, …, η} with probabilities + // + // {ncr(0, 2η)/2^2η, ncr(1, 2η)/2^2η, …, ncr(2η,2η)/2^2η} + fn noise(comptime eta: u8, nonce: u8, seed: *const [32]u8) Poly { + var h = sha3.Shake256.init(.{}); + const suffix: [1]u8 = .{nonce}; + h.update(seed); + h.update(&suffix); + + // The distribution at hand is exactly the same as that + // of (a₁ + a₂ + … + a_η) - (b₁ + … + b_η) where a_i,b_i~U(1). + // Thus we need 2η bits per coefficient. + const buf_len = comptime 2 * eta * N / 8; + var buf: [buf_len]u8 = undefined; + h.squeeze(&buf); + + // buf is interpreted as a₁…a_ηb₁…b_ηa₁…a_ηb₁…b_η…. We process + // multiple coefficients in one batch. + + const T = switch (builtin.target.cpu.arch) { + .x86_64, .x86 => u32, // Generates better code on Intel CPUs + else => u64, // u128 might be faster on some other CPUs. + }; + + comptime var batch_count: usize = undefined; + comptime var batch_bytes: usize = undefined; + comptime var mask: T = 0; + comptime { + batch_count = @bitSizeOf(T) / @as(usize, 2 * eta); + while (@rem(N, batch_count) != 0 and batch_count > 0) : (batch_count -= 1) {} + assert(batch_count > 0); + assert(@rem(2 * eta * batch_count, 8) == 0); + batch_bytes = 2 * eta * batch_count / 8; + + for (0..2 * eta * batch_count) |_| { + mask <<= eta; + mask |= 1; + } + } + + var ret: Poly = undefined; + for (0..comptime N / batch_count) |i| { + // Read coefficients into t. In the case of η=3, + // we have t = a₁ + 2a₂ + 4a₃ + 8b₁ + 16b₂ + … + var t: T = 0; + inline for (0..batch_bytes) |j| { + t |= @as(T, buf[batch_bytes * i + j]) << (8 * j); + } + + // Accumelate `a's and `b's together by masking them out, shifting + // and adding. For η=3, we have d = a₁ + a₂ + a₃ + 8(b₁ + b₂ + b₃) + … + var d: T = 0; + inline for (0..eta) |j| { + d += (t >> j) & mask; + } + + // Extract each a and b separately and set coefficient in polynomial. + inline for (0..batch_count) |j| { + const mask2 = comptime (1 << eta) - 1; + const a = @intCast(i16, (d >> (comptime (2 * j * eta))) & mask2); + const b = @intCast(i16, (d >> (comptime ((2 * j + 1) * eta))) & mask2); + ret.cs[batch_count * i + j] = a - b; + } + } + + return ret; + } + + // Sample p uniformly from the given seed and x and y coordinates. + fn uniform(seed: [32]u8, x: u8, y: u8) Poly { + var h = sha3.Shake128.init(.{}); + const suffix: [2]u8 = .{ x, y }; + h.update(&seed); + h.update(&suffix); + + const buf_len = sha3.Shake128.block_length; // rate SHAKE-128 + var buf: [buf_len]u8 = undefined; + + var ret: Poly = undefined; + var i: usize = 0; // index into ret.cs + outer: while (true) { + h.squeeze(&buf); + + var j: usize = 0; // index into buf + while (j < buf_len) : (j += 3) { + const b0 = @as(u16, buf[j]); + const b1 = @as(u16, buf[j + 1]); + const b2 = @as(u16, buf[j + 2]); + + const ts: [2]u16 = .{ + b0 | ((b1 & 0xf) << 8), + (b1 >> 4) | (b2 << 4), + }; + + inline for (ts) |t| { + if (t < Q) { + ret.cs[i] = @intCast(i16, t); + i += 1; + + if (i == N) { + break :outer; + } + } + } + } + } + + return ret; + } + + // Packs p. + // + // Assumes p is normalized (and not just Barrett reduced). + fn toBytes(p: Poly) [bytes_length]u8 { + var ret: [bytes_length]u8 = undefined; + for (0..comptime N / 2) |i| { + const t0 = @intCast(u16, p.cs[2 * i]); + const t1 = @intCast(u16, p.cs[2 * i + 1]); + ret[3 * i] = @truncate(u8, t0); + ret[3 * i + 1] = @truncate(u8, (t0 >> 8) | (t1 << 4)); + ret[3 * i + 2] = @truncate(u8, t1 >> 4); + } + return ret; + } + + // Unpacks a Poly from buf. + // + // p will not be normalized; instead 0 ≤ p[i] < 4096. + fn fromBytes(buf: *const [bytes_length]u8) Poly { + var ret: Poly = undefined; + for (0..comptime N / 2) |i| { + const b0 = @as(i16, buf[3 * i]); + const b1 = @as(i16, buf[3 * i + 1]); + const b2 = @as(i16, buf[3 * i + 2]); + ret.cs[2 * i] = b0 | ((b1 & 0xf) << 8); + ret.cs[2 * i + 1] = (b1 >> 4) | b2 << 4; + } + return ret; + } +}; + +// A vector of K polynomials. +fn Vec(comptime K: u8) type { + return struct { + ps: [K]Poly, + + const Self = @This(); + const bytes_length = K * Poly.bytes_length; + + fn compressedSize(comptime d: u8) usize { + return Poly.compressedSize(d) * K; + } + + fn ntt(a: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = a.ps[i].ntt(); + } + return ret; + } + + fn invNTT(a: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = a.ps[i].invNTT(); + } + return ret; + } + + fn normalize(a: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = a.ps[i].normalize(); + } + return ret; + } + + fn barrettReduce(a: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = a.ps[i].barrettReduce(); + } + return ret; + } + + fn add(a: Self, b: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = a.ps[i].add(b.ps[i]); + } + return ret; + } + + fn sub(a: Self, b: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = a.ps[i].sub(b.ps[i]); + } + return ret; + } + + // Samples v[i] from centered binomial distribution with the given η, + // seed and nonce+i. + fn noise(comptime eta: u8, nonce: u8, seed: *const [32]u8) Self { + var ret: Self = undefined; + for (0..K) |i| { + ret.ps[i] = Poly.noise(eta, nonce + @intCast(u8, i), seed); + } + return ret; + } + + // Sets p to the inner product of a and b using "pointwise" multiplication. + // + // See MulHat() and NTT() for a description of the multiplication. + // Assumes a and b are in Montgomery form. p will be in Montgomery form, + // and its coefficients will be bounded in absolute value by 2kq. + // If a and b are not in Montgomery form, then the action is the same + // as "pointwise" multiplication followed by multiplying by R⁻¹, the inverse + // of the Montgomery factor. + fn dotHat(a: Self, b: Self) Poly { + var ret: Poly = Poly.zero; + for (0..K) |i| { + ret = ret.add(a.ps[i].mulHat(b.ps[i])); + } + return ret; + } + + fn compress(v: Self, comptime d: u8) [compressedSize(d)]u8 { + const cs = comptime Poly.compressedSize(d); + var ret: [compressedSize(d)]u8 = undefined; + inline for (0..K) |i| { + mem.copy(u8, ret[i * cs .. (i + 1) * cs], &v.ps[i].compress(d)); + } + return ret; + } + + fn decompress(comptime d: u8, buf: *const [compressedSize(d)]u8) Self { + const cs = comptime Poly.compressedSize(d); + var ret: Self = undefined; + inline for (0..K) |i| { + ret.ps[i] = Poly.decompress(d, buf[i * cs .. (i + 1) * cs]); + } + return ret; + } + + /// Serializes the key into a byte array. + fn toBytes(v: Self) [bytes_length]u8 { + var ret: [bytes_length]u8 = undefined; + inline for (0..K) |i| { + mem.copy( + u8, + ret[i * Poly.bytes_length .. (i + 1) * Poly.bytes_length], + &v.ps[i].toBytes(), + ); + } + return ret; + } + + /// Deserializes the key from a byte array. + fn fromBytes(buf: *const [bytes_length]u8) Self { + var ret: Self = undefined; + inline for (0..K) |i| { + ret.ps[i] = Poly.fromBytes( + buf[i * Poly.bytes_length .. (i + 1) * Poly.bytes_length], + ); + } + return ret; + } + }; +} + +// A matrix of K vectors +fn Mat(comptime K: u8) type { + return struct { + const Self = @This(); + vs: [K]Vec(K), + + fn uniform(seed: [32]u8, comptime transposed: bool) Self { + var ret: Self = undefined; + var i: u8 = 0; + while (i < K) : (i += 1) { + var j: u8 = 0; + while (j < K) : (j += 1) { + ret.vs[i].ps[j] = Poly.uniform( + seed, + if (transposed) i else j, + if (transposed) j else i, + ); + } + } + return ret; + } + + // Returns transpose of A + fn transpose(m: Self) Self { + var ret: Self = undefined; + for (0..K) |i| { + for (0..K) |j| { + ret.vs[i].ps[j] = m.vs[j].ps[i]; + } + } + return ret; + } + }; +} + +// Returns `true` if a ≠ b. +fn ctneq(comptime len: usize, a: [len]u8, b: [len]u8) u1 { + return 1 - @boolToInt(crypto.utils.timingSafeEql([len]u8, a, b)); +} + +// Copy src into dst given b = 1. +fn cmov(comptime len: usize, dst: *[len]u8, src: [len]u8, b: u1) void { + const mask = @as(u8, 0) -% b; + for (0..len) |i| { + dst[i] ^= mask & (dst[i] ^ src[i]); + } +} + +test "MulHat" { + var rnd = RndGen.init(0); + + for (0..100) |_| { + const a = Poly.randAbsLeqQ(&rnd); + const b = Poly.randAbsLeqQ(&rnd); + + const p2 = a.ntt().mulHat(b.ntt()).barrettReduce().invNTT().normalize(); + var p: Poly = undefined; + + mem.set(i16, &p.cs, 0); + + for (0..N) |i| { + for (0..N) |j| { + var v = montReduce(@as(i32, a.cs[i]) * @as(i32, b.cs[j])); + var k = i + j; + if (k >= N) { + // Recall Xᴺ = -1. + k -= N; + v = -v; + } + p.cs[k] = feBarrettReduce(v + p.cs[k]); + } + } + + p = p.toMont().normalize(); + + try testing.expectEqual(p, p2); + } +} + +test "NTT" { + var rnd = RndGen.init(0); + + for (0..1000) |_| { + var p = Poly.randAbsLeqQ(&rnd); + const q = p.toMont().normalize(); + p = p.ntt(); + + for (0..N) |i| { + try testing.expect(p.cs[i] <= 7 * Q and -7 * Q <= p.cs[i]); + } + + p = p.normalize().invNTT(); + for (0..N) |i| { + try testing.expect(p.cs[i] <= Q and -Q <= p.cs[i]); + } + + p = p.normalize(); + + try testing.expectEqual(p, q); + } +} + +test "Compression" { + var rnd = RndGen.init(0); + inline for (.{ 1, 4, 5, 10, 11 }) |d| { + for (0..1000) |_| { + const p = Poly.randNormalized(&rnd); + const pp = p.compress(d); + const pq = Poly.decompress(d, &pp).compress(d); + try testing.expectEqual(pp, pq); + } + } +} + +test "noise" { + var seed: [32]u8 = undefined; + for (&seed, 0..) |*s, i| { + s.* = @intCast(u8, i); + } + try testing.expectEqual(Poly.noise(3, 37, &seed).cs, .{ + 0, 0, 1, -1, 0, 2, 0, -1, -1, 3, 0, 1, -2, -2, 0, 1, -2, + 1, 0, -2, 3, 0, 0, 0, 1, 3, 1, 1, 2, 1, -1, -1, -1, 0, + 1, 0, 1, 0, 2, 0, 1, -2, 0, -1, -1, -2, 1, -1, -1, 2, -1, + 1, 1, 2, -3, -1, -1, 0, 0, 0, 0, 1, -1, -2, -2, 0, -2, 0, + 0, 0, 1, 0, -1, -1, 1, -2, 2, 0, 0, 2, -2, 0, 1, 0, 1, + 1, 1, 0, 1, -2, -1, -2, -1, 1, 0, 0, 0, 0, 0, 1, 0, -1, + -1, 0, -1, 1, 0, 1, 0, -1, -1, 0, -2, 2, 0, -2, 1, -1, 0, + 1, -1, -1, 2, 1, 0, 0, -2, -1, 2, 0, 0, 0, -1, -1, 3, 1, + 0, 1, 0, 1, 0, 2, 1, 0, 0, 1, 0, 1, 0, 0, -1, -1, -1, + 0, 1, 3, 1, 0, 1, 0, 1, -1, -1, -1, -1, 0, 0, -2, -1, -1, + 2, 0, 1, 0, 1, 0, 2, -2, 0, 1, 1, -3, -1, -2, -1, 0, 1, + 0, 1, -2, 2, 2, 1, 1, 0, -1, 0, -1, -1, 1, 0, -1, 2, 1, + -1, 1, 2, -2, 1, 2, 0, 1, 2, 1, 0, 0, 2, 1, 2, 1, 0, + 2, 1, 0, 0, -1, -1, 1, -1, 0, 1, -1, 2, 2, 0, 0, -1, 1, + 1, 1, 1, 0, 0, -2, 0, -1, 1, 2, 0, 0, 1, 1, -1, 1, 0, + 1, + }); + try testing.expectEqual(Poly.noise(2, 37, &seed).cs, .{ + 1, 0, 1, -1, -1, -2, -1, -1, 2, 0, -1, 0, 0, -1, + 1, 1, -1, 1, 0, 2, -2, 0, 1, 2, 0, 0, -1, 1, + 0, -1, 1, -1, 1, 2, 1, 1, 0, -1, 1, -1, -2, -1, + 1, -1, -1, -1, 2, -1, -1, 0, 0, 1, 1, -1, 1, 1, + 1, 1, -1, -2, 0, 1, 0, 0, 2, 1, -1, 2, 0, 0, + 1, 1, 0, -1, 0, 0, -1, -1, 2, 0, 1, -1, 2, -1, + -1, -1, -1, 0, -2, 0, 2, 1, 0, 0, 0, -1, 0, 0, + 0, -1, -1, 0, -1, -1, 0, -1, 0, 0, -2, 1, 1, 0, + 1, 0, 1, 0, 1, 1, -1, 2, 0, 1, -1, 1, 2, 0, + 0, 0, 0, -1, -1, -1, 0, 1, 0, -1, 2, 0, 0, 1, + 1, 1, 0, 1, -1, 1, 2, 1, 0, 2, -1, 1, -1, -2, + -1, -2, -1, 1, 0, -2, -2, -1, 1, 0, 0, 0, 0, 1, + 0, 0, 0, 2, 2, 0, 1, 0, -1, -1, 0, 2, 0, 0, + -2, 1, 0, 2, 1, -1, -2, 0, 0, -1, 1, 1, 0, 0, + 2, 0, 1, 1, -2, 1, -2, 1, 1, 0, 2, 0, -1, 0, + -1, 0, 1, 2, 0, 1, 0, -2, 1, -2, -2, 1, -1, 0, + -1, 1, 1, 0, 0, 0, 1, 0, -1, 1, 1, 0, 0, 0, + 0, 1, 0, 1, -1, 0, 1, -1, -1, 2, 0, 0, 1, -1, + 0, 1, -1, 0, + }); +} + +test "uniform sampling" { + var seed: [32]u8 = undefined; + for (&seed, 0..) |*s, i| { + s.* = @intCast(u8, i); + } + try testing.expectEqual(Poly.uniform(seed, 1, 0).cs, .{ + 797, 993, 161, 6, 2608, 2385, 2096, 2661, 1676, 247, 2440, + 342, 634, 194, 1570, 2848, 986, 684, 3148, 3208, 2018, 351, + 2288, 612, 1394, 170, 1521, 3119, 58, 596, 2093, 1549, 409, + 2156, 1934, 1730, 1324, 388, 446, 418, 1719, 2202, 1812, 98, + 1019, 2369, 214, 2699, 28, 1523, 2824, 273, 402, 2899, 246, + 210, 1288, 863, 2708, 177, 3076, 349, 44, 949, 854, 1371, + 957, 292, 2502, 1617, 1501, 254, 7, 1761, 2581, 2206, 2655, + 1211, 629, 1274, 2358, 816, 2766, 2115, 2985, 1006, 2433, 856, + 2596, 3192, 1, 1378, 2345, 707, 1891, 1669, 536, 1221, 710, + 2511, 120, 1176, 322, 1897, 2309, 595, 2950, 1171, 801, 1848, + 695, 2912, 1396, 1931, 1775, 2904, 893, 2507, 1810, 2873, 253, + 1529, 1047, 2615, 1687, 831, 1414, 965, 3169, 1887, 753, 3246, + 1937, 115, 2953, 586, 545, 1621, 1667, 3187, 1654, 1988, 1857, + 512, 1239, 1219, 898, 3106, 391, 1331, 2228, 3169, 586, 2412, + 845, 768, 156, 662, 478, 1693, 2632, 573, 2434, 1671, 173, + 969, 364, 1663, 2701, 2169, 813, 1000, 1471, 720, 2431, 2530, + 3161, 733, 1691, 527, 2634, 335, 26, 2377, 1707, 767, 3020, + 950, 502, 426, 1138, 3208, 2607, 2389, 44, 1358, 1392, 2334, + 875, 2097, 173, 1697, 2578, 942, 1817, 974, 1165, 2853, 1958, + 2973, 3282, 271, 1236, 1677, 2230, 673, 1554, 96, 242, 1729, + 2518, 1884, 2272, 71, 1382, 924, 1807, 1610, 456, 1148, 2479, + 2152, 238, 2208, 2329, 713, 1175, 1196, 757, 1078, 3190, 3169, + 708, 3117, 154, 1751, 3225, 1364, 154, 23, 2842, 1105, 1419, + 79, 5, 2013, + }); +} + +test "Polynomial packing" { + var rnd = RndGen.init(0); + + for (0..1000) |_| { + const p = Poly.randNormalized(&rnd); + try testing.expectEqual(Poly.fromBytes(&p.toBytes()), p); + } +} + +test "Test inner PKE" { + var seed: [32]u8 = undefined; + var pt: [32]u8 = undefined; + for (&seed, &pt, 0..) |*s, *p, i| { + s.* = @intCast(u8, i); + p.* = @intCast(u8, i + 32); + } + inline for (modes) |mode| { + for (0..100) |i| { + var pk: mode.InnerPk = undefined; + var sk: mode.InnerSk = undefined; + seed[0] = @intCast(u8, i); + mode.innerKeyFromSeed(seed, &pk, &sk); + for (0..10) |j| { + seed[1] = @intCast(u8, j); + try testing.expectEqual(sk.decrypt(&pk.encrypt(&pt, &seed)), pt); + } + } + } +} + +test "Test happy flow" { + var seed: [64]u8 = undefined; + for (&seed, 0..) |*s, i| { + s.* = @intCast(u8, i); + } + inline for (modes) |mode| { + for (0..100) |i| { + seed[0] = @intCast(u8, i); + const kp = try mode.KeyPair.create(seed); + const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes()); + try testing.expectEqual(sk, kp.secret_key); + const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes()); + try testing.expectEqual(pk, kp.public_key); + for (0..10) |j| { + seed[1] = @intCast(u8, j); + const e = pk.encaps(seed[0..32].*); + try testing.expectEqual(e.shared_secret, try sk.decaps(&e.ciphertext)); + } + } + } +} + +// Code to test NIST Known Answer Tests (KAT), see PQCgenKAT.c. + +const sha2 = crypto.hash.sha2; + +test "NIST KAT test" { + inline for (.{ + .{ Kyber512, "e9c2bd37133fcb40772f81559f14b1f58dccd1c816701be9ba6214d43baf4547" }, + .{ Kyber1024, "89248f2f33f7f4f7051729111f3049c409a933ec904aedadf035f30fa5646cd5" }, + .{ Kyber768, "a1e122cad3c24bc51622e4c242d8b8acbcd3f618fee4220400605ca8f9ea02c2" }, + }) |modeHash| { + const mode = modeHash[0]; + var seed: [48]u8 = undefined; + for (&seed, 0..) |*s, i| { + s.* = @intCast(u8, i); + } + var f = sha2.Sha256.init(.{}); + const fw = f.writer(); + var g = NistDRBG.init(seed); + try std.fmt.format(fw, "# {s}\n\n", .{mode.name}); + for (0..100) |i| { + g.fill(&seed); + try std.fmt.format(fw, "count = {}\n", .{i}); + try std.fmt.format(fw, "seed = {s}\n", .{std.fmt.fmtSliceHexUpper(&seed)}); + var g2 = NistDRBG.init(seed); + + // This is not equivalent to g2.fill(kseed[:]). As the reference + // implementation calls randombytes twice generating the keypair, + // we have to do that as well. + var kseed: [64]u8 = undefined; + var eseed: [32]u8 = undefined; + g2.fill(kseed[0..32]); + g2.fill(kseed[32..64]); + g2.fill(&eseed); + const kp = try mode.KeyPair.create(kseed); + const e = kp.public_key.encaps(eseed); + const ss2 = try kp.secret_key.decaps(&e.ciphertext); + try testing.expectEqual(ss2, e.shared_secret); + try std.fmt.format(fw, "pk = {s}\n", .{std.fmt.fmtSliceHexUpper(&kp.public_key.toBytes())}); + try std.fmt.format(fw, "sk = {s}\n", .{std.fmt.fmtSliceHexUpper(&kp.secret_key.toBytes())}); + try std.fmt.format(fw, "ct = {s}\n", .{std.fmt.fmtSliceHexUpper(&e.ciphertext)}); + try std.fmt.format(fw, "ss = {s}\n\n", .{std.fmt.fmtSliceHexUpper(&e.shared_secret)}); + } + + var out: [32]u8 = undefined; + f.final(&out); + var outHex: [64]u8 = undefined; + _ = try std.fmt.bufPrint(&outHex, "{s}", .{std.fmt.fmtSliceHexLower(&out)}); + try testing.expectEqual(outHex, modeHash[1].*); + } +} + +const NistDRBG = struct { + key: [32]u8, + v: [16]u8, + + fn incV(g: *NistDRBG) void { + var j: usize = 15; + while (j >= 0) : (j -= 1) { + if (g.v[j] == 255) { + g.v[j] = 0; + } else { + g.v[j] += 1; + break; + } + } + } + + // AES256_CTR_DRBG_Update(pd, &g.key, &g.v). + fn update(g: *NistDRBG, pd: ?[48]u8) void { + var buf: [48]u8 = undefined; + const ctx = crypto.core.aes.Aes256.initEnc(g.key); + var i: usize = 0; + while (i < 3) : (i += 1) { + g.incV(); + var block: [16]u8 = undefined; + ctx.encrypt(&block, &g.v); + mem.copy(u8, buf[i * 16 .. (i + 1) * 16], &block); + } + if (pd) |p| { + for (&buf, p) |*b, x| { + b.* ^= x; + } + } + mem.copy(u8, &g.key, buf[0..32]); + mem.copy(u8, &g.v, buf[32..48]); + } + + // randombytes. + fn fill(g: *NistDRBG, out: []u8) void { + var block: [16]u8 = undefined; + var dst = out; + + const ctx = crypto.core.aes.Aes256.initEnc(g.key); + while (dst.len > 0) { + g.incV(); + ctx.encrypt(&block, &g.v); + if (dst.len < 16) { + mem.copy(u8, dst, block[0..dst.len]); + break; + } + mem.copy(u8, dst, &block); + dst = dst[16..dst.len]; + } + g.update(null); + } + + fn init(seed: [48]u8) NistDRBG { + var ret: NistDRBG = .{ .key = .{0} ** 32, .v = .{0} ** 16 }; + ret.update(seed); + return ret; + } +}; From 8be607348061674f7be766d6ee86eef3a2d28727 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Mon, 13 Mar 2023 18:41:14 -0400 Subject: [PATCH 124/294] tools: fix typo in lldb command --- tools/lldb_pretty_printers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lldb_pretty_printers.py b/tools/lldb_pretty_printers.py index a1db03b33a..b72b6f9760 100644 --- a/tools/lldb_pretty_printers.py +++ b/tools/lldb_pretty_printers.py @@ -1,6 +1,6 @@ # pretty printing for the zig language, zig standard library, and zig stage 2 compiler. # put commands in ~/.lldbinit to run them automatically when starting lldb -# `command script /path/to/stage2_lldb_pretty_printers.py` to import this file +# `command script import /path/to/zig/tools/lldb_pretty_printers.py` to import this file # `type category enable zig` to enable pretty printing for the zig language # `type category enable zig.std` to enable pretty printing for the zig standard library # `type category enable zig.stage2` to enable pretty printing for the zig stage 2 compiler From 9a4e9215fcd91cc75e569195834ba1428a736fa0 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Mon, 13 Mar 2023 19:46:47 -0400 Subject: [PATCH 125/294] x86_64: fix error code paths to not have extra pops --- src/arch/x86_64/CodeGen.zig | 70 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 3b7dc0db57..a547b1222d 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4410,16 +4410,16 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { // Capture the state of register and stack allocation state so that we can revert to it. const saved_state = try self.captureState(); - try self.branch_stack.append(.{}); - errdefer { - _ = self.branch_stack.pop(); - } + { + try self.branch_stack.append(.{}); + errdefer _ = self.branch_stack.pop(); - try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len); - for (liveness_condbr.then_deaths) |operand| { - self.processDeath(operand); + try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len); + for (liveness_condbr.then_deaths) |operand| { + self.processDeath(operand); + } + try self.genBody(then_body); } - try self.genBody(then_body); // Revert to the previous register and stack allocation state. @@ -4430,16 +4430,16 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { try self.performReloc(reloc); - try self.branch_stack.append(.{}); - errdefer { - _ = self.branch_stack.pop(); - } + { + try self.branch_stack.append(.{}); + errdefer _ = self.branch_stack.pop(); - try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len); - for (liveness_condbr.else_deaths) |operand| { - self.processDeath(operand); + try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len); + for (liveness_condbr.else_deaths) |operand| { + self.processDeath(operand); + } + try self.genBody(else_body); } - try self.genBody(else_body); var else_branch = self.branch_stack.pop(); defer else_branch.deinit(self.gpa); @@ -4850,17 +4850,17 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { // Capture the state of register and stack allocation state so that we can revert to it. const saved_state = try self.captureState(); - try self.branch_stack.append(.{}); - errdefer { - _ = self.branch_stack.pop(); - } + { + try self.branch_stack.append(.{}); + errdefer _ = self.branch_stack.pop(); - try self.ensureProcessDeathCapacity(liveness.deaths[case_i].len); - for (liveness.deaths[case_i]) |operand| { - self.processDeath(operand); - } + try self.ensureProcessDeathCapacity(liveness.deaths[case_i].len); + for (liveness.deaths[case_i]) |operand| { + self.processDeath(operand); + } - try self.genBody(case_body); + try self.genBody(case_body); + } branch_stack.appendAssumeCapacity(self.branch_stack.pop()); @@ -4878,18 +4878,18 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { // Capture the state of register and stack allocation state so that we can revert to it. const saved_state = try self.captureState(); - try self.branch_stack.append(.{}); - errdefer { - _ = self.branch_stack.pop(); - } + { + try self.branch_stack.append(.{}); + errdefer _ = self.branch_stack.pop(); - const else_deaths = liveness.deaths.len - 1; - try self.ensureProcessDeathCapacity(liveness.deaths[else_deaths].len); - for (liveness.deaths[else_deaths]) |operand| { - self.processDeath(operand); - } + const else_deaths = liveness.deaths.len - 1; + try self.ensureProcessDeathCapacity(liveness.deaths[else_deaths].len); + for (liveness.deaths[else_deaths]) |operand| { + self.processDeath(operand); + } - try self.genBody(else_body); + try self.genBody(else_body); + } branch_stack.appendAssumeCapacity(self.branch_stack.pop()); From 5ab426a3020614dcc8640a4019c8d93c6c917ea0 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Mon, 13 Mar 2023 20:25:13 -0400 Subject: [PATCH 126/294] x86_64: fix store of undefined --- src/arch/x86_64/CodeGen.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index a547b1222d..12c5071462 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2861,7 +2861,13 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .unreach => unreachable, .eflags => unreachable, .undef => { - try self.genSetReg(value_ty, reg, value); + switch (abi_size) { + 1 => try self.store(ptr, .{ .immediate = 0xaa }, ptr_ty, value_ty), + 2 => try self.store(ptr, .{ .immediate = 0xaaaa }, ptr_ty, value_ty), + 4 => try self.store(ptr, .{ .immediate = 0xaaaaaaaa }, ptr_ty, value_ty), + 8 => try self.store(ptr, .{ .immediate = 0xaaaaaaaaaaaaaaaa }, ptr_ty, value_ty), + else => try self.genInlineMemset(ptr, .{ .immediate = 0xaa }, .{ .immediate = abi_size }, .{}), + } }, .immediate => |imm| { switch (abi_size) { From c51930b060cf66b21c78b97e53fd71b153a5e60c Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Mon, 13 Mar 2023 21:12:57 -0400 Subject: [PATCH 127/294] behavior: enable passing behavior tests on stage2_x86_64 --- test/behavior/align.zig | 1 - test/behavior/array.zig | 1 - test/behavior/bit_shifting.zig | 1 - test/behavior/bitreverse.zig | 1 - test/behavior/bugs/1076.zig | 1 - test/behavior/bugs/11046.zig | 1 - test/behavior/bugs/11787.zig | 1 - test/behavior/bugs/12000.zig | 1 - test/behavior/bugs/12119.zig | 1 - test/behavior/bugs/12169.zig | 1 - test/behavior/bugs/12890.zig | 1 - test/behavior/bugs/13113.zig | 1 - test/behavior/bugs/1421.zig | 1 - test/behavior/bugs/1607.zig | 1 - test/behavior/bugs/2622.zig | 1 - test/behavior/bugs/2727.zig | 1 - test/behavior/bugs/3742.zig | 1 - test/behavior/bugs/394.zig | 1 - test/behavior/bugs/421.zig | 1 - test/behavior/bugs/5398.zig | 1 - test/behavior/bugs/5487.zig | 1 - test/behavior/bugs/656.zig | 1 - test/behavior/bugs/6947.zig | 1 - test/behavior/byteswap.zig | 1 - test/behavior/byval_arg_var.zig | 1 - test/behavior/call.zig | 2 -- test/behavior/cast.zig | 9 --------- test/behavior/defer.zig | 1 - test/behavior/enum.zig | 2 -- test/behavior/error.zig | 10 ---------- test/behavior/eval.zig | 2 -- test/behavior/floatop.zig | 7 ------- test/behavior/fn.zig | 2 -- test/behavior/for.zig | 8 -------- test/behavior/generics.zig | 3 --- test/behavior/if.zig | 1 - test/behavior/member_func.zig | 2 -- test/behavior/null.zig | 3 --- test/behavior/optional.zig | 1 - test/behavior/pointers.zig | 2 -- test/behavior/ptrcast.zig | 1 - .../ref_var_in_if_after_if_2nd_switch_prong.zig | 1 - test/behavior/reflection.zig | 1 - test/behavior/sizeof_and_typeof.zig | 3 --- test/behavior/slice.zig | 9 --------- test/behavior/struct.zig | 5 ----- test/behavior/struct_contains_null_ptr_itself.zig | 1 - test/behavior/switch.zig | 2 -- test/behavior/threadlocal.zig | 2 -- test/behavior/translate_c_macros.zig | 1 - test/behavior/tuple.zig | 1 - test/behavior/type.zig | 4 ---- test/behavior/type_info.zig | 2 -- test/behavior/union.zig | 9 --------- test/behavior/vector.zig | 6 ------ test/behavior/void.zig | 1 - test/behavior/while.zig | 8 -------- test/behavior/widening.zig | 1 - 58 files changed, 138 deletions(-) diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 901ea3697a..9d626dad66 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -568,7 +568,6 @@ fn overaligned_fn() align(0x1000) i32 { } test "comptime alloc alignment" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO diff --git a/test/behavior/array.zig b/test/behavior/array.zig index 3d711357f3..6c68d50fda 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -591,7 +591,6 @@ test "type coercion of anon struct literal to array" { test "type coercion of pointer to anon struct literal to pointer to array" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { diff --git a/test/behavior/bit_shifting.zig b/test/behavior/bit_shifting.zig index 97186eb54a..8ad71400fe 100644 --- a/test/behavior/bit_shifting.zig +++ b/test/behavior/bit_shifting.zig @@ -63,7 +63,6 @@ fn ShardedTable(comptime Key: type, comptime mask_bit_count: comptime_int, compt test "sharded table" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO // realistic 16-way sharding diff --git a/test/behavior/bitreverse.zig b/test/behavior/bitreverse.zig index 80167b9a17..9a24090c0e 100644 --- a/test/behavior/bitreverse.zig +++ b/test/behavior/bitreverse.zig @@ -150,7 +150,6 @@ fn vector0() !void { test "bitReverse vectors u0" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; comptime try vector0(); try vector0(); diff --git a/test/behavior/bugs/1076.zig b/test/behavior/bugs/1076.zig index 6fe4fbd38f..ba2b61e3db 100644 --- a/test/behavior/bugs/1076.zig +++ b/test/behavior/bugs/1076.zig @@ -4,7 +4,6 @@ const mem = std.mem; const expect = std.testing.expect; test "comptime code should not modify constant data" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/11046.zig b/test/behavior/bugs/11046.zig index ba6c9d1a83..a13e02e45c 100644 --- a/test/behavior/bugs/11046.zig +++ b/test/behavior/bugs/11046.zig @@ -10,7 +10,6 @@ fn bar() !void { } test "fixed" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/11787.zig b/test/behavior/bugs/11787.zig index 8678f0789a..6d17730a47 100644 --- a/test/behavior/bugs/11787.zig +++ b/test/behavior/bugs/11787.zig @@ -4,7 +4,6 @@ const testing = std.testing; test "slicing zero length array field of struct" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/12000.zig b/test/behavior/bugs/12000.zig index c29fb84270..a823ce6a0a 100644 --- a/test/behavior/bugs/12000.zig +++ b/test/behavior/bugs/12000.zig @@ -7,7 +7,6 @@ const T = struct { test { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/12119.zig b/test/behavior/bugs/12119.zig index bb12e3565a..8c734ad6d6 100644 --- a/test/behavior/bugs/12119.zig +++ b/test/behavior/bugs/12119.zig @@ -6,7 +6,6 @@ const u32x8 = @Vector(8, u32); test { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/12169.zig b/test/behavior/bugs/12169.zig index 5dd3fdefa9..b3db56e20b 100644 --- a/test/behavior/bugs/12169.zig +++ b/test/behavior/bugs/12169.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); test { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/12890.zig b/test/behavior/bugs/12890.zig index e6095ac33d..1316c2745e 100644 --- a/test/behavior/bugs/12890.zig +++ b/test/behavior/bugs/12890.zig @@ -10,7 +10,6 @@ fn a(b: []u3, c: u3) void { } test { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO var arr: [8]u3 = undefined; diff --git a/test/behavior/bugs/13113.zig b/test/behavior/bugs/13113.zig index cfbf7b6650..f9e0c8e7bb 100644 --- a/test/behavior/bugs/13113.zig +++ b/test/behavior/bugs/13113.zig @@ -8,7 +8,6 @@ const Foo = extern struct { test { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/1421.zig b/test/behavior/bugs/1421.zig index fbd05fb73c..1c85c3089e 100644 --- a/test/behavior/bugs/1421.zig +++ b/test/behavior/bugs/1421.zig @@ -9,7 +9,6 @@ const S = struct { }; test "functions with return type required to be comptime are generic" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const ti = S.method(); try expect(@as(std.builtin.TypeId, ti) == std.builtin.TypeId.Struct); } diff --git a/test/behavior/bugs/1607.zig b/test/behavior/bugs/1607.zig index d9e97e37b7..a60a406b75 100644 --- a/test/behavior/bugs/1607.zig +++ b/test/behavior/bugs/1607.zig @@ -13,7 +13,6 @@ fn checkAddress(s: []const u8) !void { test "slices pointing at the same address as global array." { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try checkAddress(&a); comptime try checkAddress(&a); diff --git a/test/behavior/bugs/2622.zig b/test/behavior/bugs/2622.zig index 8a0d1a06ba..89130a3974 100644 --- a/test/behavior/bugs/2622.zig +++ b/test/behavior/bugs/2622.zig @@ -4,7 +4,6 @@ var buf: []u8 = undefined; test "reslice of undefined global var slice" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/2727.zig b/test/behavior/bugs/2727.zig index 0478d41b63..9e0def70d4 100644 --- a/test/behavior/bugs/2727.zig +++ b/test/behavior/bugs/2727.zig @@ -6,7 +6,6 @@ fn t() bool { test { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/3742.zig b/test/behavior/bugs/3742.zig index a984f0d8e4..1ee88b8b64 100644 --- a/test/behavior/bugs/3742.zig +++ b/test/behavior/bugs/3742.zig @@ -37,7 +37,6 @@ pub const ArgSerializer = struct { test "fixed" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64 and builtin.os.tag == .windows) return error.SkipZigTest; diff --git a/test/behavior/bugs/394.zig b/test/behavior/bugs/394.zig index 02e90258bf..37864edbc5 100644 --- a/test/behavior/bugs/394.zig +++ b/test/behavior/bugs/394.zig @@ -11,7 +11,6 @@ const expect = @import("std").testing.expect; const builtin = @import("builtin"); test "fixed" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const x = S{ .x = 3, .y = E{ .B = 1 }, diff --git a/test/behavior/bugs/421.zig b/test/behavior/bugs/421.zig index 500493e7d1..69ecbd2331 100644 --- a/test/behavior/bugs/421.zig +++ b/test/behavior/bugs/421.zig @@ -2,7 +2,6 @@ const builtin = @import("builtin"); const expect = @import("std").testing.expect; test "bitCast to array" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/bugs/5398.zig b/test/behavior/bugs/5398.zig index 78d31914d0..6f75bd9436 100644 --- a/test/behavior/bugs/5398.zig +++ b/test/behavior/bugs/5398.zig @@ -21,7 +21,6 @@ var renderable: Renderable = undefined; test "assignment of field with padding" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO renderable = Renderable{ diff --git a/test/behavior/bugs/5487.zig b/test/behavior/bugs/5487.zig index d901a692cd..3ea8cad220 100644 --- a/test/behavior/bugs/5487.zig +++ b/test/behavior/bugs/5487.zig @@ -12,7 +12,6 @@ pub fn writer() io.Writer(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_ty test "crash" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO _ = io.multiWriter(.{writer()}); } diff --git a/test/behavior/bugs/656.zig b/test/behavior/bugs/656.zig index fa9e3ecc1e..216c9d8e1c 100644 --- a/test/behavior/bugs/656.zig +++ b/test/behavior/bugs/656.zig @@ -13,7 +13,6 @@ const Value = struct { test "optional if after an if in a switch prong of a switch with 2 prongs in an else" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try foo(false, true); } diff --git a/test/behavior/bugs/6947.zig b/test/behavior/bugs/6947.zig index 2e891ac5b3..c2b538c3fa 100644 --- a/test/behavior/bugs/6947.zig +++ b/test/behavior/bugs/6947.zig @@ -6,7 +6,6 @@ fn destroy(ptr: *void) void { test { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO diff --git a/test/behavior/byteswap.zig b/test/behavior/byteswap.zig index d173c13275..8bd6fec6e3 100644 --- a/test/behavior/byteswap.zig +++ b/test/behavior/byteswap.zig @@ -116,7 +116,6 @@ fn vector0() !void { test "@byteSwap vectors u0" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; comptime try vector0(); try vector0(); diff --git a/test/behavior/byval_arg_var.zig b/test/behavior/byval_arg_var.zig index 476d0d2e4e..01b5f90ef7 100644 --- a/test/behavior/byval_arg_var.zig +++ b/test/behavior/byval_arg_var.zig @@ -4,7 +4,6 @@ const builtin = @import("builtin"); var result: []const u8 = "wrong"; test "pass string literal byvalue to a generic var param" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO start(); diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 9622aa3144..b51a459932 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -21,7 +21,6 @@ test "super basic invocations" { test "basic invocations" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -56,7 +55,6 @@ test "basic invocations" { test "tuple parameters" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const add = struct { diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index f179cbe525..275533d6ec 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -176,7 +176,6 @@ fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void { test "implicitly cast indirect pointer to maybe-indirect pointer" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -318,7 +317,6 @@ test "peer result null and comptime_int" { test "*const ?[*]const T to [*c]const [*c]const T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64 or builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO var array = [_]u8{ 'o', 'k' }; @@ -588,7 +586,6 @@ fn testCastPtrOfArrayToSliceAndPtr() !void { test "cast *[1][*]const u8 to [*]const ?[*]const u8" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const window_name = [1][*]const u8{"window name"}; @@ -927,7 +924,6 @@ test "peer cast *[N:x]T to *[N]T" { test "peer cast [*:x]T to [*]T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -948,7 +944,6 @@ test "peer cast [*:x]T to [*]T" { test "peer cast [:x]T to [*:x]T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -989,7 +984,6 @@ test "peer type resolution implicit cast to return type" { test "peer type resolution implicit cast to variable type" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -1112,8 +1106,6 @@ fn incrementVoidPtrArray(array: ?*anyopaque, len: usize) void { } test "compile time int to ptr of function" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - try foobar(FUNCTION_CONSTANT); } @@ -1505,7 +1497,6 @@ test "implicit cast from [:0]T to [*c]T" { test "bitcast packed struct with u0" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO const S = packed struct(u2) { a: u0, b: u2 }; const s = @bitCast(S, @as(u2, 2)); diff --git a/test/behavior/defer.zig b/test/behavior/defer.zig index 32f63b1a46..26c2adc271 100644 --- a/test/behavior/defer.zig +++ b/test/behavior/defer.zig @@ -50,7 +50,6 @@ fn testNestedFnErrDefer() anyerror!void { } test "return variable while defer expression in scope to modify it" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 095f3b740b..9076f9f9ac 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -937,7 +937,6 @@ test "enum literal casting to error union with payload enum" { } test "constant enum initialization with differing sizes" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1137,7 +1136,6 @@ test "tag name functions are unique" { } test "size of enum with only one tag which has explicit integer tag type" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/error.zig b/test/behavior/error.zig index f30290eb91..9d4b154311 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -144,15 +144,11 @@ test "implicit cast to optional to error union to return result loc" { } test "fn returning empty error set can be passed as fn returning any error" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - entry(); comptime entry(); } test "fn returning empty error set can be passed as fn returning any error - pointer" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - entryPtr(); comptime entryPtr(); } @@ -219,7 +215,6 @@ fn testErrorSetType() !void { } test "explicit error set cast" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testExplicitErrorSetCast(Set1.A); @@ -238,7 +233,6 @@ fn testExplicitErrorSetCast(set1: Set1) !void { } test "comptime test error for empty error set" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testComptimeTestErrorEmptySet(1234); @@ -255,8 +249,6 @@ fn testComptimeTestErrorEmptySet(x: EmptyErrorSet!i32) !void { } test "comptime err to int of error set with only 1 possible value" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - testErrToIntWithOnePossibleValue(error.A, @errorToInt(error.A)); comptime testErrToIntWithOnePossibleValue(error.A, @errorToInt(error.A)); } @@ -426,7 +418,6 @@ test "nested error union function call in optional unwrap" { } test "return function call to error set from error union function" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -486,7 +477,6 @@ test "nested catch" { } test "function pointer with return type that is error union with payload which is pointer of parent struct" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 8364196f94..52b30f9aed 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -138,7 +138,6 @@ test "pointer to type" { } test "a type constructed in a global expression" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1515,7 +1514,6 @@ test "x or true is comptime-known true" { } test "non-optional and optional array elements concatenated" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index f05901f7d9..a93949cd88 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -21,7 +21,6 @@ fn epsForType(comptime T: type) T { test "floating point comparisons" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO try testFloatComparisons(); @@ -91,7 +90,6 @@ fn testDifferentSizedFloatComparisons() !void { test "negative f128 floatToInt at compile-time" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -331,7 +329,6 @@ fn testExpWithVectors() !void { test "@exp2" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -500,7 +497,6 @@ fn testLog10WithVectors() !void { test "@fabs" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -627,7 +623,6 @@ test "a third @fabs test, surely there should not be three fabs tests" { test "@floor" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -720,7 +715,6 @@ fn testFloorLegacy(comptime T: type, x: T) !void { test "@ceil" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -813,7 +807,6 @@ fn testCeilLegacy(comptime T: type, x: T) !void { test "@trunc" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig index 9c37b9a8d9..5113e21452 100644 --- a/test/behavior/fn.zig +++ b/test/behavior/fn.zig @@ -275,7 +275,6 @@ test "implicit cast fn call result to optional in field result" { } test "void parameters" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; @@ -300,7 +299,6 @@ fn acceptsString(foo: []u8) void { } test "function pointers" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/for.zig b/test/behavior/for.zig index 67a20e4840..98ffff85a3 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -110,7 +110,6 @@ test "basic for loop" { } test "for with null and T peer types and inferred result location type" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -152,7 +151,6 @@ test "2 break statements and an else" { } test "for loop with pointer elem var" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -198,7 +196,6 @@ test "for copies its payload" { } test "for on slice with allowzero ptr" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -253,7 +250,6 @@ test "for loop with else branch" { test "count over fixed range" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO var sum: usize = 0; for (0..6) |i| { @@ -266,7 +262,6 @@ test "count over fixed range" { test "two counters" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO var sum: usize = 0; for (0..10, 10..20) |i, j| { @@ -280,7 +275,6 @@ test "two counters" { test "1-based counter and ptr to array" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO var ok: usize = 0; @@ -401,7 +395,6 @@ test "raw pointer and counter" { test "inline for with slice as the comptime-known" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const comptime_slice = "hello"; @@ -432,7 +425,6 @@ test "inline for with slice as the comptime-known" { test "inline for with counter as the comptime-known" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO var runtime_slice = "hello"; diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index 205823430c..0e002b2016 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -56,7 +56,6 @@ fn sameButWithFloats(a: f64, b: f64) f64 { } test "fn with comptime args" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -67,7 +66,6 @@ test "fn with comptime args" { } test "anytype params" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -93,7 +91,6 @@ fn max_f64(a: f64, b: f64) f64 { } test "type constructed by comptime function call" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/if.zig b/test/behavior/if.zig index ac11a6585d..6632cdd5c2 100644 --- a/test/behavior/if.zig +++ b/test/behavior/if.zig @@ -78,7 +78,6 @@ test "const result loc, runtime if cond, else unreachable" { } test "if copies its payload" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/member_func.zig b/test/behavior/member_func.zig index a6229846d6..e7b19d5c01 100644 --- a/test/behavior/member_func.zig +++ b/test/behavior/member_func.zig @@ -27,7 +27,6 @@ const HasFuncs = struct { }; test "standard field calls" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -71,7 +70,6 @@ test "standard field calls" { } test "@field field calls" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/null.zig b/test/behavior/null.zig index 223be69084..c78a995833 100644 --- a/test/behavior/null.zig +++ b/test/behavior/null.zig @@ -50,7 +50,6 @@ test "rhs maybe unwrap return" { } test "maybe return" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -71,7 +70,6 @@ fn foo(x: ?i32) ?bool { } test "test null runtime" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -184,7 +182,6 @@ const SillyStruct = struct { const here_is_a_null_literal = SillyStruct{ .context = null }; test "unwrap optional which is field of global var" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 3a5b7b008b..bbcc5b3ce6 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -274,7 +274,6 @@ test "0-bit child type coerced to optional return ptr result location" { } test "0-bit child type coerced to optional" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index ec4ff332cf..e5ccfec543 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -190,7 +190,6 @@ test "compare equality of optional and non-optional pointer" { test "allowzero pointer and slice" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -510,7 +509,6 @@ test "ptrToInt on a generic function" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO const S = struct { fn generic(i: anytype) @TypeOf(i) { diff --git a/test/behavior/ptrcast.zig b/test/behavior/ptrcast.zig index 9336d58641..599d13be1d 100644 --- a/test/behavior/ptrcast.zig +++ b/test/behavior/ptrcast.zig @@ -53,7 +53,6 @@ fn testReinterpretStructWrappedBytesAsInteger() !void { } test "reinterpret bytes of an array into an extern struct" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig b/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig index cd1f67dd11..bb6d5b1359 100644 --- a/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig +++ b/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig @@ -5,7 +5,6 @@ const mem = std.mem; var ok: bool = false; test "reference a variable in an if after an if in the 2nd switch prong" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/reflection.zig b/test/behavior/reflection.zig index 4c3f8ccad5..aea84bc45a 100644 --- a/test/behavior/reflection.zig +++ b/test/behavior/reflection.zig @@ -27,7 +27,6 @@ fn dummy(a: bool, b: i32, c: f32) i32 { test "reflection: @field" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO var f = Foo{ diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig index cfe948ac02..940ceda107 100644 --- a/test/behavior/sizeof_and_typeof.zig +++ b/test/behavior/sizeof_and_typeof.zig @@ -18,7 +18,6 @@ test "@sizeOf on compile-time types" { } test "@TypeOf() with multiple arguments" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; { var var_1: u32 = undefined; var var_2: u8 = undefined; @@ -138,7 +137,6 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { } test "@TypeOf() has no runtime side effects" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const S = struct { fn foo(comptime T: type, ptr: *T) T { ptr.* += 1; @@ -153,7 +151,6 @@ test "@TypeOf() has no runtime side effects" { test "branching logic inside @TypeOf" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; const S = struct { var data: i32 = 0; fn foo() anyerror!i32 { diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 6239de2d76..d749697ec5 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -119,7 +119,6 @@ test "slice of type" { } test "generic malloc free" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const a = memAlloc(u8, 10) catch unreachable; @@ -171,7 +170,6 @@ test "comptime pointer cast array and then slice" { test "slicing zero length array" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const s1 = ""[0..]; @@ -185,8 +183,6 @@ test "slicing zero length array" { const x = @intToPtr([*]i32, 0x1000)[0..0x500]; const y = x[0x100..]; test "compile time slice of pointer to hard coded address" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; - try expect(@ptrToInt(x) == 0x1000); try expect(x.len == 0x500); @@ -342,7 +338,6 @@ test "@ptrCast slice to pointer" { } test "slice syntax resulting in pointer-to-array" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -477,7 +472,6 @@ test "slice syntax resulting in pointer-to-array" { } test "slice pointer-to-array null terminated" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -497,7 +491,6 @@ test "slice pointer-to-array null terminated" { } test "slice pointer-to-array zero length" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO comptime { @@ -530,7 +523,6 @@ test "slice pointer-to-array zero length" { } test "type coercion of pointer to anon struct literal to pointer to slice" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -730,7 +722,6 @@ test "slice with dereferenced value" { test "empty slice ptr is non null" { if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64 and (builtin.os.tag == .macos or builtin.os.tag == .windows)) return error.SkipZigTest; // TODO const empty_slice: []u8 = &[_]u8{}; const p: [*]u8 = empty_slice.ptr + 0; diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 348e269682..2a1acebc0f 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -342,7 +342,6 @@ fn testPassSliceOfEmptyStructToFn(slice: []const EmptyStruct2) usize { test "self-referencing struct via array member" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const T = struct { @@ -536,7 +535,6 @@ test "implicit cast packed struct field to const ptr" { } test "zero-bit field in packed struct" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = packed struct { @@ -719,7 +717,6 @@ test "pointer to packed struct member in a stack variable" { } test "packed struct with u0 field access" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = packed struct { @@ -1018,7 +1015,6 @@ test "type coercion of anon struct literal to struct" { } test "type coercion of pointer to anon struct literal to pointer to struct" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1088,7 +1084,6 @@ test "packed struct with undefined initializers" { } test "for loop over pointers to struct, getting field from struct pointer" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/struct_contains_null_ptr_itself.zig b/test/behavior/struct_contains_null_ptr_itself.zig index d60e04a91a..7f0182af22 100644 --- a/test/behavior/struct_contains_null_ptr_itself.zig +++ b/test/behavior/struct_contains_null_ptr_itself.zig @@ -4,7 +4,6 @@ const builtin = @import("builtin"); test "struct contains null pointer which contains original struct" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO var x: ?*NodeLineComment = null; diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index b8c367eb44..9129b73f16 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -403,7 +403,6 @@ fn return_a_number() anyerror!i32 { } test "switch on integer with else capturing expr" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -577,7 +576,6 @@ test "switch prongs with cases with identical payload types" { } test "switch on pointer type" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/threadlocal.zig b/test/behavior/threadlocal.zig index ebeb1177c2..1f1bc6bea4 100644 --- a/test/behavior/threadlocal.zig +++ b/test/behavior/threadlocal.zig @@ -4,7 +4,6 @@ const expect = std.testing.expect; test "thread local variable" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_llvm) switch (builtin.cpu.arch) { @@ -40,7 +39,6 @@ threadlocal var buffer: [11]u8 = undefined; test "reference a global threadlocal variable" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_llvm) switch (builtin.cpu.arch) { diff --git a/test/behavior/translate_c_macros.zig b/test/behavior/translate_c_macros.zig index 8143b1bddd..6d8d4eca6d 100644 --- a/test/behavior/translate_c_macros.zig +++ b/test/behavior/translate_c_macros.zig @@ -79,7 +79,6 @@ test "casting to union with a macro" { test "casting or calling a value with a paren-surrounded macro" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig index f7860be34e..79db21424e 100644 --- a/test/behavior/tuple.zig +++ b/test/behavior/tuple.zig @@ -126,7 +126,6 @@ test "tuple initializer for var" { } test "array-like initializer for tuple types" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/type.zig b/test/behavior/type.zig index 325bf0a8ed..7f44f350d1 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -449,8 +449,6 @@ test "Type.Union" { } test "Type.Union from Type.Enum" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - const Tag = @Type(.{ .Enum = .{ .tag_type = u0, @@ -475,8 +473,6 @@ test "Type.Union from Type.Enum" { } test "Type.Union from regular enum" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - const E = enum { working_as_expected }; const T = @Type(.{ .Union = .{ diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig index 6f64c92006..495c1f3195 100644 --- a/test/behavior/type_info.zig +++ b/test/behavior/type_info.zig @@ -509,7 +509,6 @@ test "type info for async frames" { } test "Declarations are returned in declaration order" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { @@ -532,7 +531,6 @@ test "Struct.is_tuple for anon list literal" { } test "Struct.is_tuple for anon struct literal" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const info = @typeInfo(@TypeOf(.{ .a = 0 })); diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 87691cf3cb..03c47b09d6 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -11,7 +11,6 @@ const FooWithFloats = union { }; test "basic unions with floats" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -379,7 +378,6 @@ test "union with only 1 field which is void should be zero bits" { } test "tagged union initialization with runtime void" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -400,7 +398,6 @@ fn testTaggedUnionInit(x: anytype) bool { pub const UnionEnumNoPayloads = union(enum) { A, B }; test "tagged union with no payloads" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -466,7 +463,6 @@ pub const FooUnion = union(enum) { var glbl_array: [2]FooUnion = undefined; test "initialize global array of union" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -494,7 +490,6 @@ test "update the tag value for zero-sized unions" { test "union initializer generates padding only if needed" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -561,7 +556,6 @@ const FooNoVoid = union(enum) { const Baz = enum { A, B, C, D }; test "tagged union type" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const foo1 = TaggedFoo{ .One = 13 }; @@ -598,7 +592,6 @@ fn returnAnInt(x: i32) TaggedFoo { } test "tagged union with all void fields but a meaningful tag" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -777,7 +770,6 @@ fn Setter(comptime attr: Attribute) type { } test "return union init with void payload" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO @@ -913,7 +905,6 @@ test "extern union doesn't trigger field check at comptime" { test "anonymous union literal syntax" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const S = struct { diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 1d9d517a96..562e9aba20 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -382,7 +382,6 @@ test "store vector elements via runtime index" { } test "initialize vector which is a struct field" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -794,7 +793,6 @@ test "vector reduce operation" { test "vector @reduce comptime" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1069,7 +1067,6 @@ test "alignment of vectors" { test "loading the second vector from a slice of vectors" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1150,7 +1147,6 @@ test "byte vector initialized in inline function" { test "zero divisor" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1171,7 +1167,6 @@ test "zero divisor" { test "zero multiplicand" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -1234,7 +1229,6 @@ test "load packed vector element" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO var x: @Vector(2, u15) = .{ 1, 4 }; try expect((&x[0]).* == 1); diff --git a/test/behavior/void.zig b/test/behavior/void.zig index 9b6c05d07d..8c6269123d 100644 --- a/test/behavior/void.zig +++ b/test/behavior/void.zig @@ -33,7 +33,6 @@ fn times(n: usize) []const void { } test "void optional" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/while.zig b/test/behavior/while.zig index 6a97f96763..956aa30f7b 100644 --- a/test/behavior/while.zig +++ b/test/behavior/while.zig @@ -104,7 +104,6 @@ fn testBreakOuter() void { } test "while copies its payload" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -143,7 +142,6 @@ fn runContinueAndBreakTest() !void { } test "while with optional as condition" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -156,7 +154,6 @@ test "while with optional as condition" { } test "while with optional as condition with else" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -174,7 +171,6 @@ test "while with optional as condition with else" { } test "while with error union condition" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO numbers_left = 10; @@ -205,7 +201,6 @@ test "while on bool with else result follow break prong" { } test "while on optional with else result follow else prong" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -217,7 +212,6 @@ test "while on optional with else result follow else prong" { } test "while on optional with else result follow break prong" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -290,7 +284,6 @@ test "while bool 2 break statements and an else" { } test "while optional 2 break statements and an else" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -310,7 +303,6 @@ test "while optional 2 break statements and an else" { } test "while error 2 break statements and an else" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/widening.zig b/test/behavior/widening.zig index ddd438d5d3..0992943fc3 100644 --- a/test/behavior/widening.zig +++ b/test/behavior/widening.zig @@ -28,7 +28,6 @@ test "integer widening u0 to u8" { } test "implicit unsigned integer to signed integer" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO From bb6b9c19e06320d8617f8858dde1ae6a7cf7fc5e Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Mar 2023 19:57:42 -0400 Subject: [PATCH 128/294] x86_64: fix lowering of non-pointer optional is null --- src/arch/x86_64/CodeGen.zig | 43 ++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 12c5071462..588782d838 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -208,7 +208,7 @@ const Branch = struct { }; const StackAllocation = struct { - inst: Air.Inst.Index, + inst: ?Air.Inst.Index, /// TODO do we need size? should be determined by inst.ty.abiSize(self.target.*) size: u32, }; @@ -1109,7 +1109,7 @@ fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void { try table.ensureUnusedCapacity(self.gpa, additional_count); } -fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: u32) !u32 { +fn allocMem(self: *Self, inst: ?Air.Inst.Index, abi_size: u32, abi_align: u32) !u32 { if (abi_align > self.stack_align) self.stack_align = abi_align; // TODO find a free slot instead of always appending @@ -1142,7 +1142,14 @@ fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 { } fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue { - const elem_ty = self.air.typeOfIndex(inst); + return self.allocRegOrMemAdvanced(self.air.typeOfIndex(inst), inst, reg_ok); +} + +fn allocTempRegOrMem(self: *Self, elem_ty: Type, reg_ok: bool) !MCValue { + return self.allocRegOrMemAdvanced(elem_ty, null, reg_ok); +} + +fn allocRegOrMemAdvanced(self: *Self, elem_ty: Type, inst: ?Air.Inst.Index, reg_ok: bool) !MCValue { const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) orelse { const mod = self.bin_file.options.module.?; return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(mod)}); @@ -4571,18 +4578,16 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void { }; defer if (operand_ptr_lock) |lock| self.register_manager.unlockReg(lock); - const operand: MCValue = blk: { - if (self.reuseOperand(inst, un_op, 0, operand_ptr)) { - // The MCValue that holds the pointer can be re-used as the value. - break :blk operand_ptr; - } else { - break :blk try self.allocRegOrMem(inst, true); - } - }; const ptr_ty = self.air.typeOf(un_op); + const elem_ty = ptr_ty.childType(); + const operand = if (elem_ty.isPtrLikeOptional() and self.reuseOperand(inst, un_op, 0, operand_ptr)) + // The MCValue that holds the pointer can be re-used as the value. + operand_ptr + else + try self.allocTempRegOrMem(elem_ty, true); try self.load(operand, operand_ptr, ptr_ty); - const result = try self.isNull(inst, ptr_ty.elemType(), operand); + const result = try self.isNull(inst, elem_ty, operand); return self.finishAir(inst, result, .{ un_op, .none, .none }); } @@ -4611,15 +4616,13 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void { }; defer if (operand_ptr_lock) |lock| self.register_manager.unlockReg(lock); - const operand: MCValue = blk: { - if (self.reuseOperand(inst, un_op, 0, operand_ptr)) { - // The MCValue that holds the pointer can be re-used as the value. - break :blk operand_ptr; - } else { - break :blk try self.allocRegOrMem(inst, true); - } - }; const ptr_ty = self.air.typeOf(un_op); + const elem_ty = ptr_ty.childType(); + const operand = if (elem_ty.isPtrLikeOptional() and self.reuseOperand(inst, un_op, 0, operand_ptr)) + // The MCValue that holds the pointer can be re-used as the value. + operand_ptr + else + try self.allocTempRegOrMem(elem_ty, true); try self.load(operand, operand_ptr, ptr_ty); const result = try self.isNonNull(inst, ptr_ty.elemType(), operand); From d14a9e82feca6467176d188d2eccf6ff51606952 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Mar 2023 22:13:52 -0400 Subject: [PATCH 129/294] x86_64: use new for loop syntax --- src/arch/x86_64/CodeGen.zig | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 588782d838..37f20261d5 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -184,8 +184,7 @@ const Branch = struct { _ = options; comptime assert(unused_format_string.len == 0); try writer.writeAll("Branch {\n"); - for (ctx.insts, 0..) |inst, i| { - const mcv = ctx.mcvs[i]; + for (ctx.insts, ctx.mcvs) |inst, mcv| { try writer.print(" %{d} => {}\n", .{ inst, mcv }); } try writer.writeAll("}"); @@ -3982,10 +3981,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier }; defer if (ret_reg_lock) |lock| self.register_manager.unlockReg(lock); - for (args, 0..) |arg, arg_i| { - const mc_arg = info.args[arg_i]; + for (args, info.args) |arg, info_arg| { + const mc_arg = info_arg; const arg_ty = self.air.typeOf(arg); - const arg_mcv = try self.resolveInst(args[arg_i]); + const arg_mcv = try self.resolveInst(arg); // Here we do not use setRegOrMem even though the logic is similar, because // the function call will move the stack pointer, so the offsets are different. switch (mc_arg) { @@ -4851,9 +4850,9 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { var relocs = try self.gpa.alloc(u32, items.len); defer self.gpa.free(relocs); - for (items, 0..) |item, item_i| { + for (items, relocs) |item, *reloc| { const item_mcv = try self.resolveInst(item); - relocs[item_i] = try self.genCondSwitchMir(condition_ty, condition, item_mcv); + reloc.* = try self.genCondSwitchMir(condition_ty, condition, item_mcv); } // Capture the state of register and stack allocation state so that we can revert to it. @@ -4935,11 +4934,7 @@ fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Bran try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, target_branch.inst_table.count()); const target_slice = target_branch.inst_table.entries.slice(); - const target_keys = target_slice.items(.key); - const target_values = target_slice.items(.value); - - for (target_keys, 0..) |target_key, target_idx| { - const target_value = target_values[target_idx]; + for (target_slice.items(.key), target_slice.items(.value)) |target_key, target_value| { const canon_mcv = if (canon_branch.inst_table.fetchSwapRemove(target_key)) |canon_entry| blk: { // The instruction's MCValue is overridden in both branches. parent_branch.inst_table.putAssumeCapacity(target_key, canon_entry.value); @@ -4969,10 +4964,7 @@ fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Bran } try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, canon_branch.inst_table.count()); const canon_slice = canon_branch.inst_table.entries.slice(); - const canon_keys = canon_slice.items(.key); - const canon_values = canon_slice.items(.value); - for (canon_keys, 0..) |canon_key, canon_idx| { - const canon_value = canon_values[canon_idx]; + for (canon_slice.items(.key), canon_slice.items(.value)) |canon_key, canon_value| { // We already deleted the items from this table that matched the target_branch. // So these are all instructions that are only overridden in the canon branch. parent_branch.inst_table.putAssumeCapacity(canon_key, canon_value); @@ -6446,7 +6438,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { else => 0, }; - for (param_types, 0..) |ty, i| { + for (param_types, result.args, 0..) |ty, *arg, i| { assert(ty.hasRuntimeBits()); const classes: []const abi.Class = switch (self.target.os.tag) { @@ -6459,7 +6451,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { switch (classes[0]) { .integer => blk: { if (i >= abi.getCAbiIntParamRegs(self.target.*).len) break :blk; // fallthrough - result.args[i] = .{ .register = abi.getCAbiIntParamRegs(self.target.*)[i] }; + arg.* = .{ .register = abi.getCAbiIntParamRegs(self.target.*)[i] }; continue; }, .memory => {}, // fallthrough @@ -6471,7 +6463,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { const param_size = @intCast(u32, ty.abiSize(self.target.*)); const param_align = @intCast(u32, ty.abiAlignment(self.target.*)); const offset = mem.alignForwardGeneric(u32, next_stack_offset + param_size, param_align); - result.args[i] = .{ .stack_offset = @intCast(i32, offset) }; + arg.* = .{ .stack_offset = @intCast(i32, offset) }; next_stack_offset = offset; } @@ -6522,15 +6514,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { else => 0, }; - for (param_types, 0..) |ty, i| { + for (param_types, result.args) |ty, *arg| { if (!ty.hasRuntimeBits()) { - result.args[i] = .{ .none = {} }; + arg.* = .{ .none = {} }; continue; } const param_size = @intCast(u32, ty.abiSize(self.target.*)); const param_align = @intCast(u32, ty.abiAlignment(self.target.*)); const offset = mem.alignForwardGeneric(u32, next_stack_offset + param_size, param_align); - result.args[i] = .{ .stack_offset = @intCast(i32, offset) }; + arg.* = .{ .stack_offset = @intCast(i32, offset) }; next_stack_offset = offset; } From 238615984064f52b6ff31b5750824a9c764fdb15 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Mar 2023 22:17:19 -0400 Subject: [PATCH 130/294] x86_64: use short union init --- src/arch/x86_64/CodeGen.zig | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 37f20261d5..1be93accc7 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -1037,7 +1037,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { fn processDeath(self: *Self, inst: Air.Inst.Index) void { const air_tags = self.air.instructions.items(.tag); if (air_tags[inst] == .constant) return; // Constants are immortal. - log.debug("%{d} => {}", .{ inst, MCValue{ .dead = {} } }); + log.debug("%{d} => {}", .{ inst, MCValue.dead }); // When editing this function, note that the logic must synchronize with `reuseOperand`. const prev_value = self.getResolvedInstValue(inst); const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; @@ -4729,7 +4729,7 @@ fn airBlock(self: *Self, inst: Air.Inst.Index) !void { // break instruction will choose a MCValue for the block result and overwrite // this field. Following break instructions will use that MCValue to put their // block results. - .mcv = MCValue{ .none = {} }, + .mcv = .none, }); defer self.blocks.getPtr(inst).?.relocs.deinit(self.gpa); @@ -5145,9 +5145,9 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg_name = output[2 .. output.len - 1]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - break :result MCValue{ .register = reg }; + break :result .{ .register = reg }; } else { - break :result MCValue{ .none = {} }; + break :result .none; } }; @@ -6289,7 +6289,7 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { if (ref_int < Air.Inst.Ref.typed_value_map.len) { const tv = Air.Inst.Ref.typed_value_map[ref_int]; if (!tv.ty.hasRuntimeBitsIgnoreComptime() and !tv.ty.isError()) { - return MCValue{ .none = {} }; + return .none; } return self.genTypedValue(tv); } @@ -6297,7 +6297,7 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // If the type has no codegen bits, no need to store it. const inst_ty = self.air.typeOf(inst); if (!inst_ty.hasRuntimeBitsIgnoreComptime() and !inst_ty.isError()) - return MCValue{ .none = {} }; + return .none; const inst_index = @intCast(Air.Inst.Index, ref_int - Air.Inst.Ref.typed_value_map.len); switch (self.air.instructions.items(.tag)[inst_index]) { @@ -6406,7 +6406,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { switch (cc) { .Naked => { assert(result.args.len == 0); - result.return_value = .{ .unreach = {} }; + result.return_value = .unreach; result.stack_byte_count = 0; result.stack_align = 1; return result; @@ -6414,10 +6414,10 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { .C => { // Return values if (ret_ty.zigTypeTag() == .NoReturn) { - result.return_value = .{ .unreach = {} }; + result.return_value = .unreach; } else if (!ret_ty.hasRuntimeBitsIgnoreComptime() and !ret_ty.isError()) { // TODO: is this even possible for C calling convention? - result.return_value = .{ .none = {} }; + result.return_value = .none; } else { const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); if (ret_ty_size == 0) { @@ -6489,9 +6489,9 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { .Unspecified => { // Return values if (ret_ty.zigTypeTag() == .NoReturn) { - result.return_value = .{ .unreach = {} }; + result.return_value = .unreach; } else if (!ret_ty.hasRuntimeBitsIgnoreComptime() and !ret_ty.isError()) { - result.return_value = .{ .none = {} }; + result.return_value = .none; } else { const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); if (ret_ty_size == 0) { @@ -6516,7 +6516,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { for (param_types, result.args) |ty, *arg| { if (!ty.hasRuntimeBits()) { - arg.* = .{ .none = {} }; + arg.* = .none; continue; } const param_size = @intCast(u32, ty.abiSize(self.target.*)); From 05b12e677939effa9da147008071908a2a93fe35 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Mar 2023 22:32:00 -0400 Subject: [PATCH 131/294] x86_64: handle duplicate prong deaths --- src/arch/x86_64/CodeGen.zig | 13 ++++++------- test/behavior/inline_switch.zig | 1 - test/behavior/union.zig | 2 -- test/behavior/union_with_members.zig | 1 - 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 1be93accc7..bf1d9f0eb0 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -1037,9 +1037,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { fn processDeath(self: *Self, inst: Air.Inst.Index) void { const air_tags = self.air.instructions.items(.tag); if (air_tags[inst] == .constant) return; // Constants are immortal. + const prev_value = self.getResolvedInstValue(inst) orelse return; log.debug("%{d} => {}", .{ inst, MCValue.dead }); // When editing this function, note that the logic must synchronize with `reuseOperand`. - const prev_value = self.getResolvedInstValue(inst); const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; branch.inst_table.putAssumeCapacity(inst, .dead); switch (prev_value) { @@ -1225,7 +1225,7 @@ fn revertState(self: *Self, state: State) void { pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void { const stack_mcv = try self.allocRegOrMem(inst, false); log.debug("spilling %{d} to stack mcv {any}", .{ inst, stack_mcv }); - const reg_mcv = self.getResolvedInstValue(inst); + const reg_mcv = self.getResolvedInstValue(inst).?; switch (reg_mcv) { .register => |other| { assert(reg.to64() == other.to64()); @@ -1242,7 +1242,7 @@ pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void pub fn spillEflagsIfOccupied(self: *Self) !void { if (self.eflags_inst) |inst_to_save| { - const mcv = self.getResolvedInstValue(inst_to_save); + const mcv = self.getResolvedInstValue(inst_to_save).?; const new_mcv = switch (mcv) { .register_overflow => try self.allocRegOrMem(inst_to_save, false), .eflags => try self.allocRegOrMem(inst_to_save, true), @@ -6315,18 +6315,17 @@ fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { return gop.value_ptr.*; }, .const_ty => unreachable, - else => return self.getResolvedInstValue(inst_index), + else => return self.getResolvedInstValue(inst_index).?, } } -fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue { +fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) ?MCValue { // Treat each stack item as a "layer" on top of the previous one. var i: usize = self.branch_stack.items.len; while (true) { i -= 1; if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| { - assert(mcv != .dead); - return mcv; + return if (mcv != .dead) mcv else null; } } } diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig index 90e8b36284..dcd603c94f 100644 --- a/test/behavior/inline_switch.zig +++ b/test/behavior/inline_switch.zig @@ -46,7 +46,6 @@ const U = union(E) { a: void, b: u2, c: u3, d: u4 }; test "inline switch unions" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO var x: U = .a; diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 03c47b09d6..9b49f8bf47 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -214,7 +214,6 @@ const Payload = union(Letter) { }; test "union with specified enum tag" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -224,7 +223,6 @@ test "union with specified enum tag" { } test "packed union generates correctly aligned type" { - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/union_with_members.zig b/test/behavior/union_with_members.zig index 0cb06a81ab..8e9c2db475 100644 --- a/test/behavior/union_with_members.zig +++ b/test/behavior/union_with_members.zig @@ -18,7 +18,6 @@ const ET = union(enum) { test "enum with members" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO From ba9d93dc9fd341f4dc08082f894fcbc1060cdcad Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Mar 2023 23:49:24 -0400 Subject: [PATCH 132/294] x86_64: implement more binary immediate combinations --- src/arch/x86_64/CodeGen.zig | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index bf1d9f0eb0..3e89779ca7 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3637,12 +3637,35 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu ), }, .immediate => |imm| { - // TODO - try self.asmRegisterImmediate( - mir_tag, - registerAlias(dst_reg, abi_size), - Immediate.u(@intCast(u32, imm)), - ); + switch (abi_size) { + 0 => unreachable, + 1...4 => { + try self.asmRegisterImmediate( + mir_tag, + registerAlias(dst_reg, abi_size), + Immediate.u(@intCast(u32, imm)), + ); + }, + 5...8 => { + if (math.cast(i32, @bitCast(i64, imm))) |small| { + try self.asmRegisterImmediate( + mir_tag, + registerAlias(dst_reg, abi_size), + Immediate.s(small), + ); + } else { + const tmp_reg = try self.register_manager.allocReg(null, gp); + const tmp_alias = registerAlias(tmp_reg, abi_size); + try self.asmRegisterImmediate(.mov, tmp_alias, Immediate.u(imm)); + try self.asmRegisterRegister( + mir_tag, + registerAlias(dst_reg, abi_size), + tmp_alias, + ); + } + }, + else => return self.fail("TODO getBinOpMir implement large immediate ABI", .{}), + } }, .memory, .linker_load, From d70955b0df92fdd0fdf3680f7f297a4cc676ee5a Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Mar 2023 23:49:45 -0400 Subject: [PATCH 133/294] x86_64: turn packed struct crashes into compile errors --- src/arch/x86_64/CodeGen.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 3e89779ca7..5dfce901f7 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3053,6 +3053,9 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde const mcv = try self.resolveInst(operand); const ptr_ty = self.air.typeOf(operand); const struct_ty = ptr_ty.childType(); + if (struct_ty.zigTypeTag() == .Struct and struct_ty.containerLayout() == .Packed) { + return self.fail("TODO structFieldPtr implement packed structs", .{}); + } const struct_field_offset = @intCast(u32, struct_ty.structFieldOffset(index, self.target.*)); const dst_mcv: MCValue = result: { @@ -3116,6 +3119,9 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { const mcv = try self.resolveInst(operand); const struct_ty = self.air.typeOf(operand); + if (struct_ty.zigTypeTag() == .Struct and struct_ty.containerLayout() == .Packed) { + return self.fail("TODO airStructFieldVal implement packed structs", .{}); + } const struct_field_offset = struct_ty.structFieldOffset(index, self.target.*); const struct_field_ty = struct_ty.structFieldType(index); @@ -6242,6 +6248,9 @@ fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void { if (self.liveness.isUnused(inst)) break :res MCValue.dead; switch (result_ty.zigTypeTag()) { .Struct => { + if (result_ty.containerLayout() == .Packed) { + return self.fail("TODO airAggregateInit implement packed structs", .{}); + } const stack_offset = @intCast(i32, try self.allocMem(inst, abi_size, abi_align)); for (elements, 0..) |elem, elem_i| { if (result_ty.structFieldValueComptime(elem_i) != null) continue; // comptime elem From c6a895f6671d30e2db77ae4f4e0c2d9e40624787 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Feb 2023 13:10:20 -0700 Subject: [PATCH 134/294] extract some logic from std.Build to build_runner.zig --- lib/build_runner.zig | 87 ++++++++++++++++++++++++++++++++------------ lib/std/Build.zig | 73 ++++++------------------------------- 2 files changed, 75 insertions(+), 85 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 64421d1031..f9a2bbd40a 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -92,23 +92,23 @@ pub fn main() !void { // before arg parsing, check for the NO_COLOR environment variable // if it exists, default the color setting to .off // explicit --color arguments will still override this setting. - builder.color = if (std.process.hasEnvVarConstant("NO_COLOR")) .off else .auto; + builder.color = if (process.hasEnvVarConstant("NO_COLOR")) .off else .auto; while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-D")) { const option_contents = arg[2..]; if (option_contents.len == 0) { std.debug.print("Expected option name after '-D'\n\n", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); } if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| { const option_name = option_contents[0..name_end]; const option_value = option_contents[name_end + 1 ..]; if (try builder.addUserInputOption(option_name, option_value)) - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); } else { if (try builder.addUserInputFlag(option_contents)) - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); } } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "--verbose")) { @@ -118,61 +118,61 @@ pub fn main() !void { } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) { install_prefix = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after {s}\n\n", .{arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) { return steps(builder, false, stdout_stream); } else if (mem.eql(u8, arg, "--prefix-lib-dir")) { dir_list.lib_dir = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after {s}\n\n", .{arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--prefix-exe-dir")) { dir_list.exe_dir = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after {s}\n\n", .{arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--prefix-include-dir")) { dir_list.include_dir = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after {s}\n\n", .{arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--sysroot")) { const sysroot = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after --sysroot\n\n", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; builder.sysroot = sysroot; } else if (mem.eql(u8, arg, "--search-prefix")) { const search_prefix = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after --search-prefix\n\n", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; builder.addSearchPrefix(search_prefix); } else if (mem.eql(u8, arg, "--libc")) { const libc_file = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after --libc\n\n", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; builder.libc_file = libc_file; } else if (mem.eql(u8, arg, "--color")) { const next_arg = nextArg(args, &arg_idx) orelse { std.debug.print("expected [auto|on|off] after --color", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; builder.color = std.meta.stringToEnum(@TypeOf(builder.color), next_arg) orelse { std.debug.print("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--zig-lib-dir")) { builder.zig_lib_dir = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after --zig-lib-dir\n\n", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--debug-log")) { const next_arg = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after {s}\n\n", .{arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; try debug_log_scopes.append(next_arg); } else if (mem.eql(u8, arg, "--debug-compile-errors")) { @@ -180,7 +180,7 @@ pub fn main() !void { } else if (mem.eql(u8, arg, "--glibc-runtimes")) { builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after --glibc-runtimes\n\n", .{}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); }; } else if (mem.eql(u8, arg, "--verbose-link")) { builder.verbose_link = true; @@ -231,7 +231,7 @@ pub fn main() !void { break; } else { std.debug.print("Unrecognized argument: {s}\n\n", .{arg}); - return usageAndErr(builder, false, stderr_stream); + usageAndErr(builder, false, stderr_stream); } } else { try targets.append(arg); @@ -243,13 +243,10 @@ pub fn main() !void { try builder.runBuild(root); if (builder.validateUserInputDidItFail()) - return usageAndErr(builder, true, stderr_stream); + usageAndErr(builder, true, stderr_stream); - builder.make(targets.items) catch |err| { + make(builder, targets.items) catch |err| { switch (err) { - error.InvalidStepName => { - return usageAndErr(builder, true, stderr_stream); - }, error.UncleanExit => process.exit(1), // This error is intended to indicate that the step has already // logged an error message and so printing the error return trace @@ -261,6 +258,48 @@ pub fn main() !void { }; } +fn make(b: *std.Build, step_names: []const []const u8) !void { + var wanted_steps = ArrayList(*std.Build.Step).init(b.allocator); + defer wanted_steps.deinit(); + + if (step_names.len == 0) { + try wanted_steps.append(b.default_step); + } else { + for (step_names) |step_name| { + const s = b.top_level_steps.get(step_name) orelse { + std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name}); + process.exit(1); + }; + try wanted_steps.append(&s.step); + } + } + + for (wanted_steps.items) |s| { + try makeOneStep(b, s); + } +} + +fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { + if (s.loop_flag) { + std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); + return error.DependencyLoopDetected; + } + s.loop_flag = true; + + for (s.dependencies.items) |dep| { + makeOneStep(b, dep) catch |err| { + if (err == error.DependencyLoopDetected) { + std.debug.print(" {s}\n", .{s.name}); + } + return err; + }; + } + + s.loop_flag = false; + + try s.make(); +} + fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void { // run the build script to collect the options if (!already_ran_build) { @@ -269,7 +308,7 @@ fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi } const allocator = builder.allocator; - for (builder.top_level_steps.items) |top_level_step| { + for (builder.top_level_steps.values()) |top_level_step| { const name = if (&top_level_step.step == builder.default_step) try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name}) else @@ -374,7 +413,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi ); } -fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) void { +fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) noreturn { usage(builder, already_ran_build, out_stream) catch {}; process.exit(1); } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 120196f972..168180e383 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -67,7 +67,7 @@ invalid_user_input: bool, zig_exe: []const u8, default_step: *Step, env_map: *EnvMap, -top_level_steps: ArrayList(*TopLevelStep), +top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep), install_prefix: []const u8, dest_dir: ?[]const u8, lib_dir: []const u8, @@ -217,7 +217,7 @@ pub fn create( .user_input_options = UserInputOptionsMap.init(allocator), .available_options_map = AvailableOptionsMap.init(allocator), .available_options_list = ArrayList(AvailableOption).init(allocator), - .top_level_steps = ArrayList(*TopLevelStep).init(allocator), + .top_level_steps = .{}, .default_step = undefined, .env_map = env_map, .search_prefixes = ArrayList([]const u8).init(allocator), @@ -241,8 +241,8 @@ pub fn create( .host = host, .modules = std.StringArrayHashMap(*Module).init(allocator), }; - try self.top_level_steps.append(&self.install_tls); - try self.top_level_steps.append(&self.uninstall_tls); + try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls); + try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls); self.default_step = &self.install_tls.step; return self; } @@ -288,7 +288,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc .zig_exe = parent.zig_exe, .default_step = undefined, .env_map = parent.env_map, - .top_level_steps = ArrayList(*TopLevelStep).init(allocator), + .top_level_steps = .{}, .install_prefix = undefined, .dest_dir = parent.dest_dir, .lib_dir = parent.lib_dir, @@ -316,8 +316,8 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), .modules = std.StringArrayHashMap(*Module).init(allocator), }; - try child.top_level_steps.append(&child.install_tls); - try child.top_level_steps.append(&child.uninstall_tls); + try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls); + try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls); child.default_step = &child.install_tls.step; return child; } @@ -389,10 +389,10 @@ fn applyArgs(b: *Build, args: anytype) !void { b.resolveInstallPrefix(install_prefix, .{}); } -pub fn destroy(self: *Build) void { - self.env_map.deinit(); - self.top_level_steps.deinit(); - self.allocator.destroy(self); +pub fn destroy(b: *Build) void { + b.env_map.deinit(); + b.top_level_steps.deinit(b.allocator); + b.allocator.destroy(b); } /// This function is intended to be called by lib/build_runner.zig, not a build.zig file. @@ -698,24 +698,6 @@ pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCS return TranslateCStep.create(self, options); } -pub fn make(self: *Build, step_names: []const []const u8) !void { - var wanted_steps = ArrayList(*Step).init(self.allocator); - defer wanted_steps.deinit(); - - if (step_names.len == 0) { - try wanted_steps.append(self.default_step); - } else { - for (step_names) |step_name| { - const s = try self.getTopLevelStepByName(step_name); - try wanted_steps.append(s); - } - } - - for (wanted_steps.items) |s| { - try self.makeOneStep(s); - } -} - pub fn getInstallStep(self: *Build) *Step { return &self.install_tls.step; } @@ -739,37 +721,6 @@ fn makeUninstall(uninstall_step: *Step) anyerror!void { // TODO remove empty directories } -fn makeOneStep(self: *Build, s: *Step) anyerror!void { - if (s.loop_flag) { - log.err("Dependency loop detected:\n {s}", .{s.name}); - return error.DependencyLoopDetected; - } - s.loop_flag = true; - - for (s.dependencies.items) |dep| { - self.makeOneStep(dep) catch |err| { - if (err == error.DependencyLoopDetected) { - log.err(" {s}", .{s.name}); - } - return err; - }; - } - - s.loop_flag = false; - - try s.make(); -} - -fn getTopLevelStepByName(self: *Build, name: []const u8) !*Step { - for (self.top_level_steps.items) |top_level_step| { - if (mem.eql(u8, top_level_step.step.name, name)) { - return &top_level_step.step; - } - } - log.err("Cannot run step '{s}' because it does not exist", .{name}); - return error.InvalidStepName; -} - pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T { const name = self.dupe(name_raw); const description = self.dupe(description_raw); @@ -910,7 +861,7 @@ pub fn step(self: *Build, name: []const u8, description: []const u8) *Step { .step = Step.initNoOp(.top_level, name, self.allocator), .description = self.dupe(description), }; - self.top_level_steps.append(step_info) catch @panic("OOM"); + self.top_level_steps.put(self.allocator, step_info.step.name, step_info) catch @panic("OOM"); return &step_info.step; } From 0b744d7d670d00fa865ebd17847cbdc1a909ba70 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Feb 2023 13:28:18 -0700 Subject: [PATCH 135/294] build runner: untangle dependency loop checking from making --- lib/build_runner.zig | 23 ++++++++++++++++++----- lib/std/Build/Step.zig | 5 +++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index f9a2bbd40a..cc5c9325ae 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -274,20 +274,27 @@ fn make(b: *std.Build, step_names: []const []const u8) !void { } } + for (wanted_steps.items) |s| { + checkForDependencyLoop(b, s) catch |err| switch (err) { + error.DependencyLoopDetected => return error.UncleanExit, + else => |e| return e, + }; + } + for (wanted_steps.items) |s| { try makeOneStep(b, s); } } -fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { - if (s.loop_flag) { +fn checkForDependencyLoop(b: *std.Build, s: *std.Build.Step) !void { + if (s.loop_tag == .started) { std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); return error.DependencyLoopDetected; } - s.loop_flag = true; + s.loop_tag = .started; for (s.dependencies.items) |dep| { - makeOneStep(b, dep) catch |err| { + checkForDependencyLoop(b, dep) catch |err| { if (err == error.DependencyLoopDetected) { std.debug.print(" {s}\n", .{s.name}); } @@ -295,7 +302,13 @@ fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { }; } - s.loop_flag = false; + s.loop_tag = .done; +} + +fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { + for (s.dependencies.items) |dep| { + try makeOneStep(b, dep); + } try s.make(); } diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 82c39ac2cc..16b1640e70 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -2,7 +2,8 @@ id: Id, name: []const u8, makeFn: *const fn (self: *Step) anyerror!void, dependencies: std.ArrayList(*Step), -loop_flag: bool, +/// Used only during a pre-check for dependency loops. +loop_tag: enum { unstarted, started, done }, done_flag: bool, pub const Id = enum { @@ -60,7 +61,7 @@ pub fn init( .name = allocator.dupe(u8, name) catch @panic("OOM"), .makeFn = makeFn, .dependencies = std.ArrayList(*Step).init(allocator), - .loop_flag = false, + .loop_tag = .unstarted, .done_flag = false, }; } From 5b90fa05a4e5b155f25319713acfc67ad9516c69 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Feb 2023 13:39:06 -0700 Subject: [PATCH 136/294] extract ThreadPool and WaitGroup from compiler to std lib --- CMakeLists.txt | 4 ++-- lib/std/Thread.zig | 2 ++ src/ThreadPool.zig => lib/std/Thread/Pool.zig | 16 ++++++++-------- {src => lib/std/Thread}/WaitGroup.zig | 0 src/Compilation.zig | 4 ++-- src/Package.zig | 4 ++-- src/link/MachO/CodeSignature.zig | 4 ++-- src/main.zig | 2 +- src/test.zig | 4 ++-- 9 files changed, 21 insertions(+), 19 deletions(-) rename src/ThreadPool.zig => lib/std/Thread/Pool.zig (90%) rename {src => lib/std/Thread}/WaitGroup.zig (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5afea9354e..c77c66add4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,7 +506,9 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig" "${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig" "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig" + "${CMAKE_SOURCE_DIR}/lib/std/Thread/Pool.zig" "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig" + "${CMAKE_SOURCE_DIR}/lib/std/Thread/WaitGroup.zig" "${CMAKE_SOURCE_DIR}/lib/std/time.zig" "${CMAKE_SOURCE_DIR}/lib/std/treap.zig" "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig" @@ -530,9 +532,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/Package.zig" "${CMAKE_SOURCE_DIR}/src/RangeSet.zig" "${CMAKE_SOURCE_DIR}/src/Sema.zig" - "${CMAKE_SOURCE_DIR}/src/ThreadPool.zig" "${CMAKE_SOURCE_DIR}/src/TypedValue.zig" - "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig" "${CMAKE_SOURCE_DIR}/src/Zir.zig" "${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig" "${CMAKE_SOURCE_DIR}/src/arch/aarch64/Emit.zig" diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 27f7fa5030..e3345e4a42 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -16,6 +16,8 @@ pub const Mutex = @import("Thread/Mutex.zig"); pub const Semaphore = @import("Thread/Semaphore.zig"); pub const Condition = @import("Thread/Condition.zig"); pub const RwLock = @import("Thread/RwLock.zig"); +pub const Pool = @import("Thread/Pool.zig"); +pub const WaitGroup = @import("Thread/WaitGroup.zig"); pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc; const is_gnu = target.abi.isGnu(); diff --git a/src/ThreadPool.zig b/lib/std/Thread/Pool.zig similarity index 90% rename from src/ThreadPool.zig rename to lib/std/Thread/Pool.zig index fde5ed27db..930befbac5 100644 --- a/src/ThreadPool.zig +++ b/lib/std/Thread/Pool.zig @@ -1,6 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const ThreadPool = @This(); +const Pool = @This(); const WaitGroup = @import("WaitGroup.zig"); mutex: std.Thread.Mutex = .{}, @@ -17,7 +17,7 @@ const Runnable = struct { const RunProto = *const fn (*Runnable) void; -pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void { +pub fn init(pool: *Pool, allocator: std.mem.Allocator) !void { pool.* = .{ .allocator = allocator, .threads = &[_]std.Thread{}, @@ -41,12 +41,12 @@ pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void { } } -pub fn deinit(pool: *ThreadPool) void { +pub fn deinit(pool: *Pool) void { pool.join(pool.threads.len); // kill and join all threads. pool.* = undefined; } -fn join(pool: *ThreadPool, spawned: usize) void { +fn join(pool: *Pool, spawned: usize) void { if (builtin.single_threaded) { return; } @@ -69,7 +69,7 @@ fn join(pool: *ThreadPool, spawned: usize) void { pool.allocator.free(pool.threads); } -pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void { +pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void { if (builtin.single_threaded) { @call(.auto, func, args); return; @@ -78,7 +78,7 @@ pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void { const Args = @TypeOf(args); const Closure = struct { arguments: Args, - pool: *ThreadPool, + pool: *Pool, run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } }, fn runFn(runnable: *Runnable) void { @@ -112,7 +112,7 @@ pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void { pool.cond.signal(); } -fn worker(pool: *ThreadPool) void { +fn worker(pool: *Pool) void { pool.mutex.lock(); defer pool.mutex.unlock(); @@ -135,7 +135,7 @@ fn worker(pool: *ThreadPool) void { } } -pub fn waitAndWork(pool: *ThreadPool, wait_group: *WaitGroup) void { +pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void { while (!wait_group.isDone()) { if (blk: { pool.mutex.lock(); diff --git a/src/WaitGroup.zig b/lib/std/Thread/WaitGroup.zig similarity index 100% rename from src/WaitGroup.zig rename to lib/std/Thread/WaitGroup.zig diff --git a/src/Compilation.zig b/src/Compilation.zig index de433a6800..63dd229ec5 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -7,6 +7,8 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const log = std.log.scoped(.compilation); const Target = std.Target; +const ThreadPool = std.Thread.Pool; +const WaitGroup = std.Thread.WaitGroup; const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; @@ -30,8 +32,6 @@ const Cache = std.Build.Cache; const translate_c = @import("translate_c.zig"); const clang = @import("clang.zig"); const c_codegen = @import("codegen/c.zig"); -const ThreadPool = @import("ThreadPool.zig"); -const WaitGroup = @import("WaitGroup.zig"); const libtsan = @import("libtsan.zig"); const Zir = @import("Zir.zig"); const Autodoc = @import("Autodoc.zig"); diff --git a/src/Package.zig b/src/Package.zig index c238d3d567..87d52197bd 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -8,11 +8,11 @@ const Allocator = mem.Allocator; const assert = std.debug.assert; const log = std.log.scoped(.package); const main = @import("main.zig"); +const ThreadPool = std.Thread.Pool; +const WaitGroup = std.Thread.WaitGroup; const Compilation = @import("Compilation.zig"); const Module = @import("Module.zig"); -const ThreadPool = @import("ThreadPool.zig"); -const WaitGroup = @import("WaitGroup.zig"); const Cache = std.Build.Cache; const build_options = @import("build_options"); const Manifest = @import("Manifest.zig"); diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 8bc00d9181..6d1cd7b536 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -7,12 +7,12 @@ const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; const testing = std.testing; +const ThreadPool = std.Thread.Pool; +const WaitGroup = std.Thread.WaitGroup; const Allocator = mem.Allocator; const Compilation = @import("../../Compilation.zig"); const Sha256 = std.crypto.hash.sha2.Sha256; -const ThreadPool = @import("../../ThreadPool.zig"); -const WaitGroup = @import("../../WaitGroup.zig"); const hash_size = Sha256.digest_length; diff --git a/src/main.zig b/src/main.zig index 95cfca1463..dd0faa628c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,6 +9,7 @@ const Allocator = mem.Allocator; const ArrayList = std.ArrayList; const Ast = std.zig.Ast; const warn = std.log.warn; +const ThreadPool = std.Thread.Pool; const tracy = @import("tracy.zig"); const Compilation = @import("Compilation.zig"); @@ -22,7 +23,6 @@ const translate_c = @import("translate_c.zig"); const clang = @import("clang.zig"); const Cache = std.Build.Cache; const target_util = @import("target.zig"); -const ThreadPool = @import("ThreadPool.zig"); const crash_report = @import("crash_report.zig"); pub const std_options = struct { diff --git a/src/test.zig b/src/test.zig index 61cdb705e3..ce87742606 100644 --- a/src/test.zig +++ b/src/test.zig @@ -4,14 +4,14 @@ const Allocator = std.mem.Allocator; const CrossTarget = std.zig.CrossTarget; const print = std.debug.print; const assert = std.debug.assert; +const ThreadPool = std.Thread.Pool; +const WaitGroup = std.Thread.WaitGroup; const link = @import("link.zig"); const Compilation = @import("Compilation.zig"); const Package = @import("Package.zig"); const introspect = @import("introspect.zig"); const build_options = @import("build_options"); -const ThreadPool = @import("ThreadPool.zig"); -const WaitGroup = @import("WaitGroup.zig"); const zig_h = link.File.C.zig_h; const enable_qemu: bool = build_options.enable_qemu; From 658de75500871f28015aa2ff14872eed0410dddf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Feb 2023 15:14:16 -0700 Subject: [PATCH 137/294] add std.heap.ThreadSafeAllocator This wraps any allocator and makes it thread-safe by using a mutex. --- lib/std/heap.zig | 1 + lib/std/heap/ThreadSafeAllocator.zig | 45 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 lib/std/heap/ThreadSafeAllocator.zig diff --git a/lib/std/heap.zig b/lib/std/heap.zig index c15e5d0ec2..e2d000f318 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -19,6 +19,7 @@ pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig" pub const WasmAllocator = @import("heap/WasmAllocator.zig"); pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig"); pub const PageAllocator = @import("heap/PageAllocator.zig"); +pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig"); const memory_pool = @import("heap/memory_pool.zig"); pub const MemoryPool = memory_pool.MemoryPool; diff --git a/lib/std/heap/ThreadSafeAllocator.zig b/lib/std/heap/ThreadSafeAllocator.zig new file mode 100644 index 0000000000..fe10eb2fdb --- /dev/null +++ b/lib/std/heap/ThreadSafeAllocator.zig @@ -0,0 +1,45 @@ +//! Wraps a non-thread-safe allocator and makes it thread-safe. + +child_allocator: Allocator, +mutex: std.Thread.Mutex = .{}, + +pub fn allocator(self: *ThreadSafeAllocator) Allocator { + return .{ + .ptr = self, + .vtable = &.{ + .alloc = alloc, + .resize = resize, + .free = free, + }, + }; +} + +fn alloc(ctx: *anyopaque, n: usize, log2_ptr_align: u8, ra: usize) ?[*]u8 { + const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx)); + self.mutex.lock(); + defer self.mutex.unlock(); + + return self.child_allocator.rawAlloc(n, log2_ptr_align, ra); +} + +fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool { + const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx)); + + self.mutex.lock(); + defer self.mutex.unlock(); + + return self.child_allocator.rawResize(buf, log2_buf_align, new_len, ret_addr); +} + +fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void { + const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx)); + + self.mutex.lock(); + defer self.mutex.unlock(); + + return self.child_allocator.rawFree(buf, log2_buf_align, ret_addr); +} + +const std = @import("../std.zig"); +const ThreadSafeAllocator = @This(); +const Allocator = std.mem.Allocator; From cff86cf7a17e038db44fa1f72ee5919eea6a6cae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Feb 2023 15:14:49 -0700 Subject: [PATCH 138/294] build_runner now executes the step graph in parallel --- lib/build_runner.zig | 128 ++++++++++++++++++++++++++++++----------- lib/std/Build/Step.zig | 15 +++-- 2 files changed, 104 insertions(+), 39 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index cc5c9325ae..c8f0b9493c 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -14,10 +14,14 @@ pub fn main() !void { // Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived, // one shot program. We don't need to waste time freeing memory and finding places to squish // bytes into. So we free everything all at once at the very end. - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); + var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer single_threaded_arena.deinit(); + + var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ + .child_allocator = single_threaded_arena.allocator(), + }; + const allocator = thread_safe_arena.allocator(); - const allocator = arena.allocator(); var args = try process.argsAlloc(allocator); defer process.argsFree(allocator, args); @@ -245,71 +249,127 @@ pub fn main() !void { if (builder.validateUserInputDidItFail()) usageAndErr(builder, true, stderr_stream); - make(builder, targets.items) catch |err| { + runStepNames(builder, targets.items) catch |err| { switch (err) { error.UncleanExit => process.exit(1), - // This error is intended to indicate that the step has already - // logged an error message and so printing the error return trace - // here would be unwanted extra information, unless the user opts - // into it with a debug flag. - error.StepFailed => process.exit(1), else => return err, } }; } -fn make(b: *std.Build, step_names: []const []const u8) !void { - var wanted_steps = ArrayList(*std.Build.Step).init(b.allocator); - defer wanted_steps.deinit(); +fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { + var step_stack = ArrayList(*std.Build.Step).init(b.allocator); + defer step_stack.deinit(); if (step_names.len == 0) { - try wanted_steps.append(b.default_step); + try step_stack.append(b.default_step); } else { - for (step_names) |step_name| { + try step_stack.resize(step_names.len); + + for (step_names) |step_name, i| { const s = b.top_level_steps.get(step_name) orelse { std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name}); process.exit(1); }; - try wanted_steps.append(&s.step); + step_stack.items[step_names.len - i - 1] = &s.step; } } - for (wanted_steps.items) |s| { - checkForDependencyLoop(b, s) catch |err| switch (err) { + const starting_steps = step_stack.items; + for (starting_steps) |s| { + checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) { error.DependencyLoopDetected => return error.UncleanExit, else => |e| return e, }; } - for (wanted_steps.items) |s| { - try makeOneStep(b, s); + var thread_pool: std.Thread.Pool = undefined; + try thread_pool.init(b.allocator); + defer thread_pool.deinit(); + + { + var wait_group: std.Thread.WaitGroup = .{}; + defer wait_group.wait(); + var i = step_stack.items.len; + + while (i > 0) { + i -= 1; + const step = step_stack.items[i]; + + wait_group.start(); + thread_pool.spawn(workerMakeOneStep, .{ &wait_group, b, step }) catch + @panic("unhandled error"); + } + } + + var any_failed = false; + + for (step_stack.items) |s| { + switch (s.result) { + .not_done => unreachable, + .success => continue, + .failure => |f| { + any_failed = true; + std.debug.print("{s}: {s}\n", .{ + s.name, @errorName(f.err_code), + }); + }, + } + } + + if (any_failed) { + process.exit(1); } } -fn checkForDependencyLoop(b: *std.Build, s: *std.Build.Step) !void { - if (s.loop_tag == .started) { - std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); - return error.DependencyLoopDetected; - } - s.loop_tag = .started; +fn checkForDependencyLoop( + b: *std.Build, + s: *std.Build.Step, + step_stack: *ArrayList(*std.Build.Step), +) !void { + switch (s.loop_tag) { + .started => { + std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); + return error.DependencyLoopDetected; + }, + .unstarted => { + s.loop_tag = .started; - for (s.dependencies.items) |dep| { - checkForDependencyLoop(b, dep) catch |err| { - if (err == error.DependencyLoopDetected) { - std.debug.print(" {s}\n", .{s.name}); + try step_stack.append(s); + + for (s.dependencies.items) |dep| { + checkForDependencyLoop(b, dep, step_stack) catch |err| { + if (err == error.DependencyLoopDetected) { + std.debug.print(" {s}\n", .{s.name}); + } + return err; + }; } - return err; - }; - } - s.loop_tag = .done; + s.loop_tag = .done; + }, + .done => {}, + } +} + +fn workerMakeOneStep(wg: *std.Thread.WaitGroup, b: *std.Build, s: *std.Build.Step) void { + defer wg.finish(); + + _ = b; + + if (s.make()) |_| { + s.result = .success; + } else |err| { + s.result = .{ .failure = .{ + .err_code = err, + } }; + } } fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { for (s.dependencies.items) |dep| { try makeOneStep(b, dep); } - try s.make(); } diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 16b1640e70..a22ea04754 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -4,7 +4,13 @@ makeFn: *const fn (self: *Step) anyerror!void, dependencies: std.ArrayList(*Step), /// Used only during a pre-check for dependency loops. loop_tag: enum { unstarted, started, done }, -done_flag: bool, +result: union(enum) { + not_done, + success, + failure: struct { + err_code: anyerror, + }, +}, pub const Id = enum { top_level, @@ -62,7 +68,7 @@ pub fn init( .makeFn = makeFn, .dependencies = std.ArrayList(*Step).init(allocator), .loop_tag = .unstarted, - .done_flag = false, + .result = .not_done, }; } @@ -71,10 +77,8 @@ pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step { } pub fn make(self: *Step) !void { - if (self.done_flag) return; - + assert(self.result == .not_done); try self.makeFn(self); - self.done_flag = true; } pub fn dependOn(self: *Step, other: *Step) void { @@ -96,3 +100,4 @@ const Step = @This(); const std = @import("../std.zig"); const Build = std.Build; const Allocator = std.mem.Allocator; +const assert = std.debug.assert; From 1fa1484288dc7431f73facb8c423b71670d6914e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Feb 2023 00:18:47 -0700 Subject: [PATCH 139/294] build runner: proper threaded dependency management After sorting the step stack so that dependencies can be popped before their dependants are popped, there is still a situation left to handle correctly: Example: A depends on: B C D depends on: E F They will be ordered like this: A B C D E F If there are 6+ cores, then all of them will be evaluated at once, incorrectly evaluating A and D before their dependencies. Starting evaluation of F and then E is correct, but waiting until they are done is not correct because it should start working on B and C as well. This commit solves the problem by computing dependants in the dependency loop checking logic, and then having workers queue up their dependants when they finish their own work. --- lib/build_runner.zig | 113 +++++++++++++++++++++++++++++------------ lib/std/Build/Step.zig | 31 +++++++---- 2 files changed, 100 insertions(+), 44 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index c8f0b9493c..87a9226bf1 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -7,6 +7,7 @@ const mem = std.mem; const process = std.process; const ArrayList = std.ArrayList; const File = std.fs.File; +const Step = std.Build.Step; pub const dependencies = @import("@dependencies"); @@ -258,7 +259,7 @@ pub fn main() !void { } fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { - var step_stack = ArrayList(*std.Build.Step).init(b.allocator); + var step_stack = ArrayList(*Step).init(b.allocator); defer step_stack.deinit(); if (step_names.len == 0) { @@ -290,28 +291,35 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { { var wait_group: std.Thread.WaitGroup = .{}; defer wait_group.wait(); - var i = step_stack.items.len; + // Here we spawn the initial set of tasks with a nice heuristic - + // dependency order. Each worker when it finishes a step will then + // check whether it should run any dependants. + var i = step_stack.items.len; while (i > 0) { i -= 1; const step = step_stack.items[i]; wait_group.start(); - thread_pool.spawn(workerMakeOneStep, .{ &wait_group, b, step }) catch - @panic("unhandled error"); + thread_pool.spawn(workerMakeOneStep, .{ &wait_group, &thread_pool, b, step }) catch + @panic("OOM"); } } var any_failed = false; for (step_stack.items) |s| { - switch (s.result) { - .not_done => unreachable, + switch (s.state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + .running => unreachable, + .dependency_failure => continue, .success => continue, - .failure => |f| { + .failure => { any_failed = true; std.debug.print("{s}: {s}\n", .{ - s.name, @errorName(f.err_code), + s.name, @errorName(s.result.err_code), }); }, } @@ -324,20 +332,20 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { fn checkForDependencyLoop( b: *std.Build, - s: *std.Build.Step, - step_stack: *ArrayList(*std.Build.Step), + s: *Step, + step_stack: *ArrayList(*Step), ) !void { - switch (s.loop_tag) { - .started => { + switch (s.state) { + .precheck_started => { std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); return error.DependencyLoopDetected; }, - .unstarted => { - s.loop_tag = .started; - - try step_stack.append(s); + .precheck_unstarted => { + s.state = .precheck_started; for (s.dependencies.items) |dep| { + try step_stack.append(dep); + try dep.dependants.append(b.allocator, s); checkForDependencyLoop(b, dep, step_stack) catch |err| { if (err == error.DependencyLoopDetected) { std.debug.print(" {s}\n", .{s.name}); @@ -346,31 +354,70 @@ fn checkForDependencyLoop( }; } - s.loop_tag = .done; + s.state = .precheck_done; }, - .done => {}, + .precheck_done => {}, + + // These don't happen until we actually run the step graph. + .dependency_failure => unreachable, + .running => unreachable, + .success => unreachable, + .failure => unreachable, } } -fn workerMakeOneStep(wg: *std.Thread.WaitGroup, b: *std.Build, s: *std.Build.Step) void { +fn workerMakeOneStep( + wg: *std.Thread.WaitGroup, + thread_pool: *std.Thread.Pool, + b: *std.Build, + s: *Step, +) void { defer wg.finish(); - _ = b; - - if (s.make()) |_| { - s.result = .success; - } else |err| { - s.result = .{ .failure = .{ - .err_code = err, - } }; - } -} - -fn makeOneStep(b: *std.Build, s: *std.Build.Step) anyerror!void { + // First, check the conditions for running this step. If they are not met, + // then we return without doing the step, relying on another worker to + // queue this step up again when dependencies are met. for (s.dependencies.items) |dep| { - try makeOneStep(b, dep); + switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) { + .success => continue, + .failure, .dependency_failure => { + @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst); + return; + }, + .precheck_done, .running => { + // dependency is not finished yet. + return; + }, + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + } + } + + // Avoid running steps twice. + if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) { + // Another worker got the job. + return; + } + + // I suspect we will want to pass `b` to make() in a future modification. + // For example, CompileStep does some sus things with modifying the saved + // *Build object in install header steps that might be able to be removed + // by passing the *Build object through the make() functions. + s.make() catch |err| { + s.result = .{ + .err_code = err, + }; + @atomicStore(Step.State, &s.state, .failure, .SeqCst); + return; + }; + + @atomicStore(Step.State, &s.state, .success, .SeqCst); + + // Successful completion of a step, so we queue up its dependants as well. + for (s.dependants.items) |dep| { + wg.start(); + thread_pool.spawn(workerMakeOneStep, .{ wg, thread_pool, b, dep }) catch @panic("OOM"); } - try s.make(); } fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index a22ea04754..1226c78c73 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -2,16 +2,25 @@ id: Id, name: []const u8, makeFn: *const fn (self: *Step) anyerror!void, dependencies: std.ArrayList(*Step), -/// Used only during a pre-check for dependency loops. -loop_tag: enum { unstarted, started, done }, -result: union(enum) { - not_done, - success, - failure: struct { - err_code: anyerror, - }, +/// This field is empty during execution of the user's build script, and +/// then populated during dependency loop checking in the build runner. +dependants: std.ArrayListUnmanaged(*Step), +state: State, +/// Populated only if state is success. +result: struct { + err_code: anyerror, }, +pub const State = enum { + precheck_unstarted, + precheck_started, + precheck_done, + running, + dependency_failure, + success, + failure, +}; + pub const Id = enum { top_level, compile, @@ -67,8 +76,9 @@ pub fn init( .name = allocator.dupe(u8, name) catch @panic("OOM"), .makeFn = makeFn, .dependencies = std.ArrayList(*Step).init(allocator), - .loop_tag = .unstarted, - .result = .not_done, + .dependants = .{}, + .state = .precheck_unstarted, + .result = undefined, }; } @@ -77,7 +87,6 @@ pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step { } pub fn make(self: *Step) !void { - assert(self.result == .not_done); try self.makeFn(self); } From 9580fbcf3596a39ba4c7d7af2f3a1df0e0abb746 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Feb 2023 02:53:05 -0700 Subject: [PATCH 140/294] build system: capture stderr and report it later Instead of dumping directly to stderr. This prevents processes running simultaneously from racing their stderr against each other. For now it only reports at the end, but an improvement would be to report as soon as a failed step occurs. --- build.zig | 5 +- lib/build_runner.zig | 8 +-- lib/std/Build.zig | 121 +++++++++++++++++++++++++++-------------- lib/std/Build/Step.zig | 6 +- 4 files changed, 90 insertions(+), 50 deletions(-) diff --git a/build.zig b/build.zig index 12e5d014e2..797c980739 100644 --- a/build.zig +++ b/build.zig @@ -676,10 +676,7 @@ fn addCxxKnownPath( ) !void { if (!std.process.can_spawn) return error.RequiredLibraryNotFound; - const path_padded = try b.exec(&[_][]const u8{ - ctx.cxx_compiler, - b.fmt("-print-file-name={s}", .{objname}), - }); + const path_padded = b.exec(&.{ ctx.cxx_compiler, b.fmt("-print-file-name={s}", .{objname}) }); var tokenizer = mem.tokenize(u8, path_padded, "\r\n"); const path_unpadded = tokenizer.next().?; if (mem.eql(u8, path_unpadded, objname)) { diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 87a9226bf1..6ba583c59d 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -318,8 +318,8 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { .success => continue, .failure => { any_failed = true; - std.debug.print("{s}: {s}\n", .{ - s.name, @errorName(s.result.err_code), + std.debug.print("{s}: {s}\n{s}", .{ + s.name, @errorName(s.result.err_code), s.result.stderr, }); }, } @@ -404,9 +404,7 @@ fn workerMakeOneStep( // *Build object in install header steps that might be able to be removed // by passing the *Build object through the make() functions. s.make() catch |err| { - s.result = .{ - .err_code = err, - }; + s.result.err_code = err; @atomicStore(Step.State, &s.state, .failure, .SeqCst); return; }; diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 168180e383..225e4a58aa 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1133,17 +1133,24 @@ pub fn spawnChild(self: *Build, argv: []const []const u8) !void { return self.spawnChildEnvMap(null, self.env_map, argv); } -fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { - if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd}); +fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 { + var buf = ArrayList(u8).init(ally); + if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd}); for (argv) |arg| { - std.debug.print("{s} ", .{arg}); + try buf.writer().print("{s} ", .{arg}); } - std.debug.print("\n", .{}); + try buf.append('\n'); + return buf.toOwnedSlice(); +} + +fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void { + const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM"); + std.debug.print("{s}", .{text}); } pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void { if (self.verbose) { - printCmd(cwd, argv); + printCmd(self.allocator, cwd, argv); } if (!std.process.can_spawn) @@ -1162,13 +1169,13 @@ pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, .Exited => |code| { if (code != 0) { log.err("The following command exited with error code {}:", .{code}); - printCmd(cwd, argv); + printCmd(self.allocator, cwd, argv); return error.UncleanExit; } }, else => { log.err("The following command terminated unexpectedly:", .{}); - printCmd(cwd, argv); + printCmd(self.allocator, cwd, argv); return error.UncleanExit; }, @@ -1381,57 +1388,91 @@ pub fn execAllowFail( } } -pub fn execFromStep(self: *Build, argv: []const []const u8, src_step: ?*Step) ![]u8 { +pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]u8 { assert(argv.len != 0); - if (self.verbose) { - printCmd(null, argv); + if (b.verbose) { + printCmd(b.allocator, null, argv); } if (!std.process.can_spawn) { - if (src_step) |s| log.err("{s}...", .{s.name}); - log.err("Unable to spawn the following command: cannot spawn child process", .{}); - printCmd(null, argv); - std.os.abort(); + s.result.stderr = b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ + try allocPrintCmd(b.allocator, null, argv), + }); + return error.CannotSpawnProcesses; } var code: u8 = undefined; - return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) { - error.ExecNotSupported => { - if (src_step) |s| log.err("{s}...", .{s.name}); - log.err("Unable to spawn the following command: cannot spawn child process", .{}); - printCmd(null, argv); - std.os.abort(); - }, + const result = unwrapExecResult(&code, std.ChildProcess.exec(.{ + .allocator = b.allocator, + .argv = argv, + .env_map = b.env_map, + .max_output_bytes = 10 * 1024 * 1024, + })) catch |err| switch (err) { error.FileNotFound => { - if (src_step) |s| log.err("{s}...", .{s.name}); - log.err("Unable to spawn the following command: file not found", .{}); - printCmd(null, argv); - std.os.exit(@truncate(u8, code)); + s.result.stderr = b.fmt("unable to spawn the following command: file not found\n{s}", .{ + try allocPrintCmd(b.allocator, null, argv), + }); + return error.ExecFailed; }, error.ExitCodeFailure => { - if (src_step) |s| log.err("{s}...", .{s.name}); - if (self.prominent_compile_errors) { - log.err("The step exited with error code {d}", .{code}); - } else { - log.err("The following command exited with error code {d}:", .{code}); - printCmd(null, argv); - } - - std.os.exit(@truncate(u8, code)); + s.result.stderr = b.fmt("the following command exited with error code {d}:\n{s}", .{ + code, try allocPrintCmd(b.allocator, null, argv), + }); + return error.ExecFailed; }, error.ProcessTerminated => { - if (src_step) |s| log.err("{s}...", .{s.name}); - log.err("The following command terminated unexpectedly:", .{}); - printCmd(null, argv); - std.os.exit(@truncate(u8, code)); + s.result.stderr = b.fmt("the following command terminated unexpectedly:\n{s}", .{ + try allocPrintCmd(b.allocator, null, argv), + }); + return error.ExecFailed; }, else => |e| return e, }; + + s.result.stderr = result.stderr; + return result.stdout; } -pub fn exec(self: *Build, argv: []const []const u8) ![]u8 { - return self.execFromStep(argv, null); +fn unwrapExecResult( + code_ptr: *u8, + wrapped: std.ChildProcess.ExecError!std.ChildProcess.ExecResult, +) !std.ChildProcess.ExecResult { + const result = try wrapped; + switch (result.term) { + .Exited => |code| { + code_ptr.* = code; + if (code != 0) { + return error.ExitCodeFailure; + } + return result; + }, + .Signal, .Stopped, .Unknown => |code| { + _ = code; + return error.ProcessTerminated; + }, + } +} + +/// This is a helper function to be called from build.zig scripts, *not* from +/// inside step make() functions. If any errors occur, it fails the build with +/// a helpful message. +pub fn exec(b: *Build, argv: []const []const u8) []u8 { + if (!std.process.can_spawn) { + std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}", .{ + try allocPrintCmd(b.allocator, null, argv), + }); + process.exit(1); + } + + var code: u8 = undefined; + return b.execAllowFail(argv, &code, .Inherit) catch |err| { + const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM"); + std.debug.print("unable to spawn the following command: {s}\n{s}", .{ + @errorName(err), printed_cmd, + }); + process.exit(1); + }; } pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 1226c78c73..44998f4a44 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -9,6 +9,7 @@ state: State, /// Populated only if state is success. result: struct { err_code: anyerror, + stderr: []u8, }, pub const State = enum { @@ -78,7 +79,10 @@ pub fn init( .dependencies = std.ArrayList(*Step).init(allocator), .dependants = .{}, .state = .precheck_unstarted, - .result = undefined, + .result = .{ + .err_code = undefined, + .stderr = &.{}, + }, }; } From 02381c037221937e941e03c5e7439383dde8a2a1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Feb 2023 12:47:45 -0700 Subject: [PATCH 141/294] std.Build: improve debugging of misconfigured steps * Step.init() now takes an options struct * Step.init() now captures a small stack trace and stores it in the Step so that it can be accessed when printing user-friendly debugging information, including the lines of code that created the step in question. --- lib/std/Build.zig | 88 ++++++++++++++++++++------- lib/std/Build/CheckFileStep.zig | 6 +- lib/std/Build/CheckObjectStep.zig | 6 +- lib/std/Build/CompileStep.zig | 6 +- lib/std/Build/ConfigHeaderStep.zig | 8 ++- lib/std/Build/EmulatableRunStep.zig | 6 +- lib/std/Build/FmtStep.zig | 6 +- lib/std/Build/InstallArtifactStep.zig | 6 +- lib/std/Build/InstallDirStep.zig | 8 ++- lib/std/Build/InstallFileStep.zig | 6 +- lib/std/Build/LogStep.zig | 6 +- lib/std/Build/ObjCopyStep.zig | 11 ++-- lib/std/Build/OptionsStep.zig | 6 +- lib/std/Build/RemoveDirStep.zig | 6 +- lib/std/Build/RunStep.zig | 8 ++- lib/std/Build/Step.zig | 48 +++++++++++---- lib/std/Build/TranslateCStep.zig | 6 +- lib/std/Build/WriteFileStep.zig | 6 +- test/link/macho/uuid/build.zig | 22 ++++--- test/tests.zig | 12 +++- 20 files changed, 209 insertions(+), 68 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 225e4a58aa..3ebdba4c70 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -227,12 +227,19 @@ pub fn create( .h_dir = undefined, .dest_dir = env_map.get("DESTDIR"), .installed_files = ArrayList(InstalledFile).init(allocator), - .install_tls = TopLevelStep{ - .step = Step.initNoOp(.top_level, "install", allocator), + .install_tls = .{ + .step = Step.init(allocator, .{ + .id = .top_level, + .name = "install", + }), .description = "Copy build artifacts to prefix path", }, - .uninstall_tls = TopLevelStep{ - .step = Step.init(.top_level, "uninstall", allocator, makeUninstall), + .uninstall_tls = .{ + .step = Step.init(allocator, .{ + .id = .top_level, + .name = "uninstall", + .makeFn = makeUninstall, + }), .description = "Remove build artifacts from prefix path", }, .zig_lib_dir = null, @@ -264,11 +271,18 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc child.* = .{ .allocator = allocator, .install_tls = .{ - .step = Step.initNoOp(.top_level, "install", allocator), + .step = Step.init(allocator, .{ + .id = .top_level, + .name = "install", + }), .description = "Copy build artifacts to prefix path", }, .uninstall_tls = .{ - .step = Step.init(.top_level, "uninstall", allocator, makeUninstall), + .step = Step.init(allocator, .{ + .id = .top_level, + .name = "uninstall", + .makeFn = makeUninstall, + }), .description = "Remove build artifacts from prefix path", }, .user_input_options = UserInputOptionsMap.init(allocator), @@ -634,7 +648,11 @@ pub fn addConfigHeader( options: ConfigHeaderStep.Options, values: anytype, ) *ConfigHeaderStep { - const config_header_step = ConfigHeaderStep.create(b, options); + var options_copy = options; + if (options_copy.first_ret_addr == null) + options_copy.first_ret_addr = @returnAddress(); + + const config_header_step = ConfigHeaderStep.create(b, options_copy); config_header_step.addValues(values); return config_header_step; } @@ -858,7 +876,10 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ pub fn step(self: *Build, name: []const u8, description: []const u8) *Step { const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM"); step_info.* = TopLevelStep{ - .step = Step.initNoOp(.top_level, name, self.allocator), + .step = Step.init(self.allocator, .{ + .id = .top_level, + .name = name, + }), .description = self.dupe(description), }; self.top_level_steps.put(self.allocator, step_info.step.name, step_info) catch @panic("OOM"); @@ -1153,7 +1174,7 @@ pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, printCmd(self.allocator, cwd, argv); } - if (!std.process.can_spawn) + if (!process.can_spawn) return error.ExecNotSupported; var child = std.ChildProcess.init(argv, self.allocator); @@ -1355,7 +1376,7 @@ pub fn execAllowFail( ) ExecError![]u8 { assert(argv.len != 0); - if (!std.process.can_spawn) + if (!process.can_spawn) return error.ExecNotSupported; const max_output_size = 400 * 1024; @@ -1395,7 +1416,7 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]u8 { printCmd(b.allocator, null, argv); } - if (!std.process.can_spawn) { + if (!process.can_spawn) { s.result.stderr = b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ try allocPrintCmd(b.allocator, null, argv), }); @@ -1458,7 +1479,7 @@ fn unwrapExecResult( /// inside step make() functions. If any errors occur, it fails the build with /// a helpful message. pub fn exec(b: *Build, argv: []const []const u8) []u8 { - if (!std.process.can_spawn) { + if (!process.can_spawn) { std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}", .{ try allocPrintCmd(b.allocator, null, argv), }); @@ -1539,7 +1560,7 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency { const full_path = b.pathFromRoot("build.zig.zon"); std.debug.print("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file.\n", .{ name, full_path }); - std.process.exit(1); + process.exit(1); } fn dependencyInner( @@ -1555,7 +1576,7 @@ fn dependencyInner( std.debug.print("unable to open '{s}': {s}\n", .{ build_root_string, @errorName(err), }); - std.process.exit(1); + process.exit(1); }, }; const sub_builder = b.createChild(name, build_root, args) catch @panic("unhandled error"); @@ -1599,7 +1620,7 @@ pub const GeneratedFile = struct { pub fn getPath(self: GeneratedFile) []const u8 { return self.path orelse std.debug.panic( - "getPath() was called on a GeneratedFile that wasn't build yet. Is there a missing Step dependency on step '{s}'?", + "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?", .{self.step.name}, ); } @@ -1639,12 +1660,16 @@ pub const FileSource = union(enum) { } /// Should only be called during make(), returns a path relative to the build root or absolute. - pub fn getPath(self: FileSource, builder: *Build) []const u8 { - const path = switch (self) { - .path => |p| builder.pathFromRoot(p), - .generated => |gen| gen.getPath(), - }; - return path; + pub fn getPath(self: FileSource, src_builder: *Build) []const u8 { + switch (self) { + .path => |p| return src_builder.pathFromRoot(p), + .generated => |gen| return gen.path orelse { + std.debug.getStderrMutex().lock(); + const stderr = std.io.getStdErr(); + dumpBadGetPathHelp(gen.step, stderr, src_builder) catch {}; + @panic("unable to get path"); + }, + } } /// Duplicates the file source for a given builder. @@ -1656,6 +1681,27 @@ pub const FileSource = union(enum) { } }; +fn dumpBadGetPathHelp(s: *Step, stderr: fs.File, src_builder: *Build) anyerror!void { + try stderr.writer().print( + \\getPath() was called on a GeneratedFile that wasn't built yet. + \\ source package path: {s} + \\ Is there a missing Step dependency on step '{s}'? + \\ The step was created by this stack trace: + \\ + , .{ + src_builder.build_root.path orelse ".", + s.name, + }); + const debug_info = std.debug.getSelfDebugInfo() catch |err| { + try stderr.writer().print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); + return; + }; + std.debug.writeStackTrace(s.getStackTrace(), stderr.writer(), debug_info.allocator, debug_info, std.debug.detectTTYConfig(stderr)) catch |err| { + try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); + return; + }; +} + /// Allocates a new string for assigning a value to a named macro. /// If the value is omitted, it is set to 1. /// `name` and `value` need not live longer than the function call. diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index b08a797e84..5b2e5b4e5b 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -21,7 +21,11 @@ pub fn create( const self = builder.allocator.create(CheckFileStep) catch @panic("OOM"); self.* = CheckFileStep{ .builder = builder, - .step = Step.init(.check_file, "CheckFile", builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .check_file, + .name = "CheckFile", + .makeFn = make, + }), .source = source.dupe(builder), .expected_matches = builder.dupeStrings(expected_matches), }; diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig index 5cb096581f..1775a5d39a 100644 --- a/lib/std/Build/CheckObjectStep.zig +++ b/lib/std/Build/CheckObjectStep.zig @@ -27,7 +27,11 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std const self = gpa.create(CheckObjectStep) catch @panic("OOM"); self.* = .{ .builder = builder, - .step = Step.init(.check_file, "CheckObject", gpa, make), + .step = Step.init(gpa, .{ + .id = .check_file, + .name = "CheckObject", + .makeFn = make, + }), .source = source.dupe(builder), .checks = std.ArrayList(Check).init(gpa), .obj_format = obj_format, diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 49d2fae68d..ce0ede9510 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -326,7 +326,11 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep { .root_src = root_src, .name = name, .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator), - .step = Step.init(base_id, name, builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = base_id, + .name = name, + .makeFn = make, + }), .version = options.version, .out_filename = undefined, .out_h_filename = builder.fmt("{s}.h", .{name}), diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index 595c1018f7..fb8d92e4e2 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -46,6 +46,7 @@ pub const Options = struct { style: Style = .blank, max_bytes: usize = 2 * 1024 * 1024, include_path: ?[]const u8 = null, + first_ret_addr: ?usize = null, }; pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep { @@ -56,7 +57,12 @@ pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep { builder.fmt("configure {s} header", .{@tagName(options.style)}); self.* = .{ .builder = builder, - .step = Step.init(base_id, name, builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = base_id, + .name = name, + .makeFn = make, + .first_ret_addr = options.first_ret_addr orelse @returnAddress(), + }), .style = options.style, .values = std.StringArrayHashMap(Value).init(builder.allocator), diff --git a/lib/std/Build/EmulatableRunStep.zig b/lib/std/Build/EmulatableRunStep.zig index d4b5238524..6556f4b8c0 100644 --- a/lib/std/Build/EmulatableRunStep.zig +++ b/lib/std/Build/EmulatableRunStep.zig @@ -56,7 +56,11 @@ pub fn create(builder: *std.Build, name: []const u8, artifact: *CompileStep) *Em self.* = .{ .builder = builder, - .step = Step.init(.emulatable_run, name, builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .emulatable_run, + .name = name, + .makeFn = make, + }), .exe = artifact, .env_map = null, .cwd = null, diff --git a/lib/std/Build/FmtStep.zig b/lib/std/Build/FmtStep.zig index 4a5efde2bd..04b8ccb3e4 100644 --- a/lib/std/Build/FmtStep.zig +++ b/lib/std/Build/FmtStep.zig @@ -12,7 +12,11 @@ pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep { const self = builder.allocator.create(FmtStep) catch @panic("OOM"); const name = "zig fmt"; self.* = FmtStep{ - .step = Step.init(.fmt, name, builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .fmt, + .name = name, + .makeFn = make, + }), .builder = builder, .argv = builder.allocator.alloc([]u8, paths.len + 2) catch @panic("OOM"), }; diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index c419c85fdf..43dcffede8 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -19,7 +19,11 @@ pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep const self = builder.allocator.create(InstallArtifactStep) catch @panic("OOM"); self.* = InstallArtifactStep{ .builder = builder, - .step = Step.init(.install_artifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = base_id, + .name = builder.fmt("install {s}", .{artifact.step.name}), + .makeFn = make, + }), .artifact = artifact, .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { .obj => @panic("Cannot install a .obj build artifact."), diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig index 41dbb3e35a..54a37af1d4 100644 --- a/lib/std/Build/InstallDirStep.zig +++ b/lib/std/Build/InstallDirStep.zig @@ -45,9 +45,13 @@ pub fn init( options: Options, ) InstallDirStep { builder.pushInstalledFile(options.install_dir, options.install_subdir); - return InstallDirStep{ + return .{ .builder = builder, - .step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .install_dir, + .name = builder.fmt("install {s}/", .{options.source_dir}), + .makeFn = make, + }), .options = options.dupe(builder), }; } diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig index 8c8d8ad2d4..70dee5d45d 100644 --- a/lib/std/Build/InstallFileStep.zig +++ b/lib/std/Build/InstallFileStep.zig @@ -24,7 +24,11 @@ pub fn init( builder.pushInstalledFile(dir, dest_rel_path); return InstallFileStep{ .builder = builder, - .step = Step.init(.install_file, builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .install_file, + .name = builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), + .makeFn = make, + }), .source = source.dupe(builder), .dir = dir.dupe(builder), .dest_rel_path = builder.dupePath(dest_rel_path), diff --git a/lib/std/Build/LogStep.zig b/lib/std/Build/LogStep.zig index 6d51df8cbd..008ed6e8bb 100644 --- a/lib/std/Build/LogStep.zig +++ b/lib/std/Build/LogStep.zig @@ -12,7 +12,11 @@ data: []const u8, pub fn init(builder: *std.Build, data: []const u8) LogStep { return LogStep{ .builder = builder, - .step = Step.init(.log, builder.fmt("log {s}", .{data}), builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .log, + .name = builder.fmt("log {s}", .{data}), + .makeFn = make, + }), .data = builder.dupe(data), }; } diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index aea5b8975c..549bb1ed00 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -44,12 +44,11 @@ pub fn create( ) *ObjCopyStep { const self = builder.allocator.create(ObjCopyStep) catch @panic("OOM"); self.* = ObjCopyStep{ - .step = Step.init( - base_id, - builder.fmt("objcopy {s}", .{file_source.getDisplayName()}), - builder.allocator, - make, - ), + .step = Step.init(builder.allocator, .{ + .id = base_id, + .name = builder.fmt("objcopy {s}", .{file_source.getDisplayName()}), + .makeFn = make, + }), .builder = builder, .file_source = file_source, .basename = options.basename orelse file_source.getDisplayName(), diff --git a/lib/std/Build/OptionsStep.zig b/lib/std/Build/OptionsStep.zig index e5c3e23821..8e86578e30 100644 --- a/lib/std/Build/OptionsStep.zig +++ b/lib/std/Build/OptionsStep.zig @@ -22,7 +22,11 @@ pub fn create(builder: *std.Build) *OptionsStep { const self = builder.allocator.create(OptionsStep) catch @panic("OOM"); self.* = .{ .builder = builder, - .step = Step.init(.options, "options", builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = base_id, + .name = "options", + .makeFn = make, + }), .generated_file = undefined, .contents = std.ArrayList(u8).init(builder.allocator), .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator), diff --git a/lib/std/Build/RemoveDirStep.zig b/lib/std/Build/RemoveDirStep.zig index f3b71dcec1..acde60a745 100644 --- a/lib/std/Build/RemoveDirStep.zig +++ b/lib/std/Build/RemoveDirStep.zig @@ -13,7 +13,11 @@ dir_path: []const u8, pub fn init(builder: *std.Build, dir_path: []const u8) RemoveDirStep { return RemoveDirStep{ .builder = builder, - .step = Step.init(.remove_dir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .remove_dir, + .name = builder.fmt("RemoveDir {s}", .{dir_path}), + .makeFn = make, + }), .dir_path = builder.dupePath(dir_path), }; } diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 1aae37d2f3..815916f380 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -69,9 +69,13 @@ pub const Arg = union(enum) { pub fn create(builder: *std.Build, name: []const u8) *RunStep { const self = builder.allocator.create(RunStep) catch @panic("OOM"); - self.* = RunStep{ + self.* = .{ .builder = builder, - .step = Step.init(base_id, name, builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = base_id, + .name = name, + .makeFn = make, + }), .argv = ArrayList(Arg).init(builder.allocator), .cwd = null, .env_map = null, diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 44998f4a44..7bab2d9ae4 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -11,6 +11,11 @@ result: struct { err_code: anyerror, stderr: []u8, }, +/// The return addresss associated with creation of this step that can be useful +/// to print along with debugging messages. +debug_stack_trace: [n_debug_stack_frames]usize, + +const n_debug_stack_frames = 4; pub const State = enum { precheck_unstarted, @@ -66,16 +71,26 @@ pub const Id = enum { } }; -pub fn init( +pub const Options = struct { id: Id, name: []const u8, - allocator: Allocator, - makeFn: *const fn (self: *Step) anyerror!void, -) Step { - return Step{ - .id = id, - .name = allocator.dupe(u8, name) catch @panic("OOM"), - .makeFn = makeFn, + makeFn: *const fn (self: *Step) anyerror!void = makeNoOp, + first_ret_addr: ?usize = null, +}; + +pub fn init(allocator: Allocator, options: Options) Step { + var addresses = [1]usize{0} ** n_debug_stack_frames; + const first_ret_addr = options.first_ret_addr orelse @returnAddress(); + var stack_trace = std.builtin.StackTrace{ + .instruction_addresses = &addresses, + .index = 0, + }; + std.debug.captureStackTrace(first_ret_addr, &stack_trace); + + return .{ + .id = options.id, + .name = allocator.dupe(u8, options.name) catch @panic("OOM"), + .makeFn = options.makeFn, .dependencies = std.ArrayList(*Step).init(allocator), .dependants = .{}, .state = .precheck_unstarted, @@ -83,13 +98,10 @@ pub fn init( .err_code = undefined, .stderr = &.{}, }, + .debug_stack_trace = addresses, }; } -pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step { - return init(id, name, allocator, makeNoOp); -} - pub fn make(self: *Step) !void { try self.makeFn(self); } @@ -98,6 +110,18 @@ pub fn dependOn(self: *Step, other: *Step) void { self.dependencies.append(other) catch @panic("OOM"); } +pub fn getStackTrace(s: *Step) std.builtin.StackTrace { + const stack_addresses = &s.debug_stack_trace; + var len: usize = 0; + while (len < n_debug_stack_frames and stack_addresses[len] != 0) { + len += 1; + } + return .{ + .instruction_addresses = stack_addresses, + .index = len, + }; +} + fn makeNoOp(self: *Step) anyerror!void { _ = self; } diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig index fb0adfd0ae..8c3a254d6a 100644 --- a/lib/std/Build/TranslateCStep.zig +++ b/lib/std/Build/TranslateCStep.zig @@ -30,7 +30,11 @@ pub fn create(builder: *std.Build, options: Options) *TranslateCStep { const self = builder.allocator.create(TranslateCStep) catch @panic("OOM"); const source = options.source_file.dupe(builder); self.* = TranslateCStep{ - .step = Step.init(.translate_c, "translate-c", builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .translate_c, + .name = "translate-c", + .makeFn = make, + }), .builder = builder, .source = source, .include_dirs = std.ArrayList([]const u8).init(builder.allocator), diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 3a30aba190..4f5e768237 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -37,7 +37,11 @@ pub const Contents = union(enum) { pub fn init(builder: *std.Build) WriteFileStep { return .{ .builder = builder, - .step = Step.init(.write_file, "writefile", builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .write_file, + .name = "writefile", + .makeFn = make, + }), .files = .{}, .output_source_files = .{}, }; diff --git a/test/link/macho/uuid/build.zig b/test/link/macho/uuid/build.zig index 5a8c14ae37..ec7664cd72 100644 --- a/test/link/macho/uuid/build.zig +++ b/test/link/macho/uuid/build.zig @@ -90,10 +90,13 @@ const InstallWithRename = struct { const self = builder.allocator.create(InstallWithRename) catch @panic("OOM"); self.* = InstallWithRename{ .builder = builder, - .step = Step.init(.custom, builder.fmt("install and rename: {s} -> {s}", .{ - source.getDisplayName(), - name, - }), builder.allocator, make), + .step = Step.init(builder.allocator, .{ + .id = .custom, + .name = builder.fmt("install and rename: {s} -> {s}", .{ + source.getDisplayName(), name, + }), + .makeFn = make, + }), .source = source, .name = builder.dupe(name), }; @@ -123,15 +126,14 @@ const CompareUuid = struct { const self = builder.allocator.create(CompareUuid) catch @panic("OOM"); self.* = CompareUuid{ .builder = builder, - .step = Step.init( - .custom, - builder.fmt("compare uuid: {s} and {s}", .{ + .step = Step.init(builder.allocator, .{ + .id = .custom, + .name = builder.fmt("compare uuid: {s} and {s}", .{ lhs, rhs, }), - builder.allocator, - make, - ), + .makeFn = make, + }), .lhs = lhs, .rhs = rhs, }; diff --git a/test/tests.zig b/test/tests.zig index 035311372f..e470a4f251 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -858,7 +858,11 @@ pub const StackTracesContext = struct { const allocator = context.b.allocator; const ptr = allocator.create(RunAndCompareStep) catch unreachable; ptr.* = RunAndCompareStep{ - .step = Step.init(.custom, "StackTraceCompareOutputStep", allocator, make), + .step = Step.init(allocator, .{ + .id = .custom, + .name = "StackTraceCompareOutputStep", + .makeFn = make, + }), .context = context, .exe = exe, .name = name, @@ -1194,7 +1198,11 @@ pub const GenHContext = struct { const allocator = context.b.allocator; const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; ptr.* = GenHCmpOutputStep{ - .step = Step.init(.Custom, "ParseCCmpOutput", allocator, make), + .step = Step.init(allocator, .{ + .id = .custom, + .name = "ParseCCmpOutput", + .makeFn = make, + }), .context = context, .obj = obj, .name = name, From 8d384722937edcfdcb3c461d316ed97405e6e2ee Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Feb 2023 13:12:47 -0700 Subject: [PATCH 142/294] std.Build: further enhance debug message for bad getPath() Now it also shows the step stack trace of the step whose make function is being run. --- lib/std/Build.zig | 48 ++++++++++++++++++++++++++----- lib/std/Build/InstallFileStep.zig | 2 +- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 3ebdba4c70..75fa7ec1dd 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1661,13 +1661,20 @@ pub const FileSource = union(enum) { /// Should only be called during make(), returns a path relative to the build root or absolute. pub fn getPath(self: FileSource, src_builder: *Build) []const u8 { + return getPath2(self, src_builder, null); + } + + /// Should only be called during make(), returns a path relative to the build root or absolute. + /// asking_step is only used for debugging purposes; it's the step being run that is asking for + /// the path. + pub fn getPath2(self: FileSource, src_builder: *Build, asking_step: ?*Step) []const u8 { switch (self) { .path => |p| return src_builder.pathFromRoot(p), .generated => |gen| return gen.path orelse { std.debug.getStderrMutex().lock(); const stderr = std.io.getStdErr(); - dumpBadGetPathHelp(gen.step, stderr, src_builder) catch {}; - @panic("unable to get path"); + dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {}; + @panic("misconfigured build script"); }, } } @@ -1681,25 +1688,52 @@ pub const FileSource = union(enum) { } }; -fn dumpBadGetPathHelp(s: *Step, stderr: fs.File, src_builder: *Build) anyerror!void { - try stderr.writer().print( +/// In this function the stderr mutex has already been locked. +fn dumpBadGetPathHelp( + s: *Step, + stderr: fs.File, + src_builder: *Build, + asking_step: ?*Step, +) anyerror!void { + const w = stderr.writer(); + try w.print( \\getPath() was called on a GeneratedFile that wasn't built yet. \\ source package path: {s} \\ Is there a missing Step dependency on step '{s}'? - \\ The step was created by this stack trace: \\ , .{ src_builder.build_root.path orelse ".", s.name, }); + + const tty_config = std.debug.detectTTYConfig(stderr); + tty_config.setColor(w, .Red) catch {}; + try stderr.writeAll(" The step was created by this stack trace:\n"); + tty_config.setColor(w, .Reset) catch {}; + const debug_info = std.debug.getSelfDebugInfo() catch |err| { - try stderr.writer().print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); + try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); return; }; - std.debug.writeStackTrace(s.getStackTrace(), stderr.writer(), debug_info.allocator, debug_info, std.debug.detectTTYConfig(stderr)) catch |err| { + const ally = debug_info.allocator; + std.debug.writeStackTrace(s.getStackTrace(), w, ally, debug_info, tty_config) catch |err| { try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); return; }; + if (asking_step) |as| { + tty_config.setColor(w, .Red) catch {}; + try stderr.writeAll(" The step that is missing a dependency on the above step was created by this stack trace:\n"); + tty_config.setColor(w, .Reset) catch {}; + + std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| { + try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); + return; + }; + } + + tty_config.setColor(w, .Red) catch {}; + try stderr.writeAll(" Hope that helps. Proceeding to panic.\n"); + tty_config.setColor(w, .Reset) catch {}; } /// Allocates a new string for assigning a value to a named macro. diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig index 70dee5d45d..e6c57a8050 100644 --- a/lib/std/Build/InstallFileStep.zig +++ b/lib/std/Build/InstallFileStep.zig @@ -38,7 +38,7 @@ pub fn init( fn make(step: *Step) !void { const self = @fieldParentPtr(InstallFileStep, "step", step); const src_builder = self.override_source_builder orelse self.builder; - const full_src_path = self.source.getPath(src_builder); + const full_src_path = self.source.getPath2(src_builder, step); const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path); try self.builder.updateFile(full_src_path, full_dest_path); } From 7ebaa05bb138ea397859edf8ec96a9209482ae8e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Feb 2023 15:03:05 -0700 Subject: [PATCH 143/294] std.Progress: add lock_stderr and unlock_stderr API users can take advantage of these to freely write to the terminal which has an ongoing progress display, similar to what Ninja does when compiling C/C++ objects and a warning or error message is printed. --- lib/std/Progress.zig | 65 +++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index f0b0e2dbd5..64084e761f 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -192,32 +192,28 @@ pub fn refresh(self: *Progress) void { return self.refreshWithHeldLock(); } -fn refreshWithHeldLock(self: *Progress) void { - const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal; - if (is_dumb and self.dont_print_on_dumb) return; - - const file = self.terminal orelse return; - - var end: usize = 0; - if (self.columns_written > 0) { +fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void { + const file = p.terminal orelse return; + var end = end_ptr.*; + if (p.columns_written > 0) { // restore the cursor position by moving the cursor // `columns_written` cells to the left, then clear the rest of the // line - if (self.supports_ansi_escape_codes) { - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len; - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; + if (p.supports_ansi_escape_codes) { + end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len; + end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; } else if (builtin.os.tag == .windows) winapi: { - std.debug.assert(self.is_windows_terminal); + std.debug.assert(p.is_windows_terminal); var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) { // stop trying to write to this file - self.terminal = null; + p.terminal = null; break :winapi; } var cursor_pos = windows.COORD{ - .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written), + .X = info.dwCursorPosition.X - @intCast(windows.SHORT, p.columns_written), .Y = info.dwCursorPosition.Y, }; @@ -235,7 +231,7 @@ fn refreshWithHeldLock(self: *Progress) void { &written, ) != windows.TRUE) { // stop trying to write to this file - self.terminal = null; + p.terminal = null; break :winapi; } if (windows.kernel32.FillConsoleOutputCharacterW( @@ -246,22 +242,33 @@ fn refreshWithHeldLock(self: *Progress) void { &written, ) != windows.TRUE) { // stop trying to write to this file - self.terminal = null; + p.terminal = null; break :winapi; } if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) { // stop trying to write to this file - self.terminal = null; + p.terminal = null; break :winapi; } } else { // we are in a "dumb" terminal like in acme or writing to a file - self.output_buffer[end] = '\n'; + p.output_buffer[end] = '\n'; end += 1; } - self.columns_written = 0; + p.columns_written = 0; } + end_ptr.* = end; +} + +fn refreshWithHeldLock(self: *Progress) void { + const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal; + if (is_dumb and self.dont_print_on_dumb) return; + + const file = self.terminal orelse return; + + var end: usize = 0; + clearWithHeldLock(self, &end); if (!self.done) { var need_ellipse = false; @@ -318,6 +325,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { self.columns_written = 0; } +/// Allows the caller to freely write to stderr until unlock_stderr() is called. +/// During the lock, the progress information is cleared from the terminal. +pub fn lock_stderr(p: *Progress) void { + p.update_mutex.lock(); + if (p.terminal) |file| { + var end: usize = 0; + clearWithHeldLock(p, &end); + _ = file.write(p.output_buffer[0..end]) catch { + // stop trying to write to this file + p.terminal = null; + }; + } + std.debug.getStderrMutex().lock(); +} + +pub fn unlock_stderr(p: *Progress) void { + std.debug.getStderrMutex().unlock(); + p.update_mutex.unlock(); +} + fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { const amt = written.len; From c5edd8b7f87f8432bbf058b3456393793118906e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Feb 2023 15:04:10 -0700 Subject: [PATCH 144/294] std.Build: better handling of stderr of child processes With this commit, the build runner now communicates progress towards completion of the step graph to the terminal, while also printing the stderr of child processes as soon as possible, without clobbering each other, and without clobbering the CLI progress output. --- lib/build_runner.zig | 54 ++++++++++++++++++++++++++++++++++-------- lib/std/Build.zig | 52 ++++++++++++++-------------------------- lib/std/Build/Step.zig | 26 ++++++++++++++++++-- 3 files changed, 86 insertions(+), 46 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 6ba583c59d..c1af3798ff 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -243,14 +243,22 @@ pub fn main() !void { } } + var progress: std.Progress = .{}; + const main_progress_node = progress.start("", 0); + defer main_progress_node.end(); + builder.debug_log_scopes = debug_log_scopes.items; builder.resolveInstallPrefix(install_prefix, dir_list); - try builder.runBuild(root); + { + var prog_node = main_progress_node.start("user build.zig logic", 0); + defer prog_node.end(); + try builder.runBuild(root); + } if (builder.validateUserInputDidItFail()) usageAndErr(builder, true, stderr_stream); - runStepNames(builder, targets.items) catch |err| { + runStepNames(builder, targets.items, main_progress_node) catch |err| { switch (err) { error.UncleanExit => process.exit(1), else => return err, @@ -258,7 +266,11 @@ pub fn main() !void { }; } -fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { +fn runStepNames( + b: *std.Build, + step_names: []const []const u8, + parent_prog_node: *std.Progress.Node, +) !void { var step_stack = ArrayList(*Step).init(b.allocator); defer step_stack.deinit(); @@ -289,6 +301,9 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { defer thread_pool.deinit(); { + var step_prog = parent_prog_node.start("run steps", step_stack.items.len); + defer step_prog.end(); + var wait_group: std.Thread.WaitGroup = .{}; defer wait_group.wait(); @@ -301,8 +316,9 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { const step = step_stack.items[i]; wait_group.start(); - thread_pool.spawn(workerMakeOneStep, .{ &wait_group, &thread_pool, b, step }) catch - @panic("OOM"); + thread_pool.spawn(workerMakeOneStep, .{ + &wait_group, &thread_pool, b, step, &step_prog, + }) catch @panic("OOM"); } } @@ -312,14 +328,18 @@ fn runStepNames(b: *std.Build, step_names: []const []const u8) !void { switch (s.state) { .precheck_unstarted => unreachable, .precheck_started => unreachable, - .precheck_done => unreachable, .running => unreachable, - .dependency_failure => continue, + // precheck_done is equivalent to dependency_failure in the case of + // transitive dependencies. For example: + // A -> B -> C (failure) + // B will be marked as dependency_failure, while A may never be queued, and thus + // remain in the initial state of precheck_done. + .dependency_failure, .precheck_done => continue, .success => continue, .failure => { any_failed = true; - std.debug.print("{s}: {s}\n{s}", .{ - s.name, @errorName(s.result.err_code), s.result.stderr, + std.debug.print("{s}: {s}\n", .{ + s.name, @errorName(s.result.err_code), }); }, } @@ -371,6 +391,7 @@ fn workerMakeOneStep( thread_pool: *std.Thread.Pool, b: *std.Build, s: *Step, + prog_node: *std.Progress.Node, ) void { defer wg.finish(); @@ -399,6 +420,10 @@ fn workerMakeOneStep( return; } + var sub_prog_node = prog_node.start(s.name, 0); + sub_prog_node.activate(); + defer sub_prog_node.end(); + // I suspect we will want to pass `b` to make() in a future modification. // For example, CompileStep does some sus things with modifying the saved // *Build object in install header steps that might be able to be removed @@ -406,6 +431,13 @@ fn workerMakeOneStep( s.make() catch |err| { s.result.err_code = err; @atomicStore(Step.State, &s.state, .failure, .SeqCst); + + sub_prog_node.context.lock_stderr(); + defer sub_prog_node.context.unlock_stderr(); + + for (s.result.error_msgs.items) |msg| { + std.io.getStdErr().writeAll(msg) catch return; + } return; }; @@ -414,7 +446,9 @@ fn workerMakeOneStep( // Successful completion of a step, so we queue up its dependants as well. for (s.dependants.items) |dep| { wg.start(); - thread_pool.spawn(workerMakeOneStep, .{ wg, thread_pool, b, dep }) catch @panic("OOM"); + thread_pool.spawn(workerMakeOneStep, .{ + wg, thread_pool, b, dep, prog_node, + }) catch @panic("OOM"); } } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 75fa7ec1dd..92ace4e60b 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1417,59 +1417,43 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]u8 { } if (!process.can_spawn) { - s.result.stderr = b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ + try s.result.error_msgs.append(b.allocator, b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ try allocPrintCmd(b.allocator, null, argv), - }); + })); return error.CannotSpawnProcesses; } - var code: u8 = undefined; - const result = unwrapExecResult(&code, std.ChildProcess.exec(.{ + const result = std.ChildProcess.exec(.{ .allocator = b.allocator, .argv = argv, .env_map = b.env_map, .max_output_bytes = 10 * 1024 * 1024, - })) catch |err| switch (err) { - error.FileNotFound => { - s.result.stderr = b.fmt("unable to spawn the following command: file not found\n{s}", .{ - try allocPrintCmd(b.allocator, null, argv), - }); - return error.ExecFailed; - }, - error.ExitCodeFailure => { - s.result.stderr = b.fmt("the following command exited with error code {d}:\n{s}", .{ - code, try allocPrintCmd(b.allocator, null, argv), - }); - return error.ExecFailed; - }, - error.ProcessTerminated => { - s.result.stderr = b.fmt("the following command terminated unexpectedly:\n{s}", .{ - try allocPrintCmd(b.allocator, null, argv), - }); - return error.ExecFailed; - }, - else => |e| return e, + }) catch |err| { + try s.result.error_msgs.append(b.allocator, b.fmt("unable to spawn the following command: {s}\n{s}", .{ + @errorName(err), try allocPrintCmd(b.allocator, null, argv), + })); + return error.ExecFailed; }; - s.result.stderr = result.stderr; - return result.stdout; -} + if (result.stderr.len != 0) { + try s.result.error_msgs.append(b.allocator, result.stderr); + } -fn unwrapExecResult( - code_ptr: *u8, - wrapped: std.ChildProcess.ExecError!std.ChildProcess.ExecResult, -) !std.ChildProcess.ExecResult { - const result = try wrapped; switch (result.term) { .Exited => |code| { - code_ptr.* = code; if (code != 0) { + try s.result.error_msgs.append(b.allocator, b.fmt("the following command exited with error code {d}:\n{s}", .{ + code, try allocPrintCmd(b.allocator, null, argv), + })); return error.ExitCodeFailure; } - return result; + return result.stdout; }, .Signal, .Stopped, .Unknown => |code| { _ = code; + try s.result.error_msgs.append(b.allocator, b.fmt("the following command terminated unexpectedly:\n{s}", .{ + try allocPrintCmd(b.allocator, null, argv), + })); return error.ProcessTerminated; }, } diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 7bab2d9ae4..42698f2190 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -9,7 +9,7 @@ state: State, /// Populated only if state is success. result: struct { err_code: anyerror, - stderr: []u8, + error_msgs: std.ArrayListUnmanaged([]const u8), }, /// The return addresss associated with creation of this step that can be useful /// to print along with debugging messages. @@ -96,7 +96,7 @@ pub fn init(allocator: Allocator, options: Options) Step { .state = .precheck_unstarted, .result = .{ .err_code = undefined, - .stderr = &.{}, + .error_msgs = .{}, }, .debug_stack_trace = addresses, }; @@ -133,6 +133,28 @@ pub fn cast(step: *Step, comptime T: type) ?*T { return null; } +/// For debugging purposes, prints identifying information about this Step. +pub fn dump(step: *Step) void { + std.debug.getStderrMutex().lock(); + defer std.debug.getStderrMutex().unlock(); + + const stderr = std.io.getStdErr(); + const w = stderr.writer(); + const tty_config = std.debug.detectTTYConfig(stderr); + const debug_info = std.debug.getSelfDebugInfo() catch |err| { + w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{ + @errorName(err), + }) catch {}; + return; + }; + const ally = debug_info.allocator; + w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; + std.debug.writeStackTrace(step.getStackTrace(), w, ally, debug_info, tty_config) catch |err| { + stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {}; + return; + }; +} + const Step = @This(); const std = @import("../std.zig"); const Build = std.Build; From 26486c7f235596ffdcbfcd44283a787b049561b8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Feb 2023 15:08:37 -0700 Subject: [PATCH 145/294] build runner: show stderr even on successful steps run --- lib/build_runner.zig | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index c1af3798ff..b2d89ba79c 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -428,16 +428,21 @@ fn workerMakeOneStep( // For example, CompileStep does some sus things with modifying the saved // *Build object in install header steps that might be able to be removed // by passing the *Build object through the make() functions. - s.make() catch |err| { - s.result.err_code = err; - @atomicStore(Step.State, &s.state, .failure, .SeqCst); + const make_result = s.make(); + // No matter the result, we want to display error/warning messages. + if (s.result.error_msgs.items.len > 0) { sub_prog_node.context.lock_stderr(); defer sub_prog_node.context.unlock_stderr(); for (s.result.error_msgs.items) |msg| { - std.io.getStdErr().writeAll(msg) catch return; + std.io.getStdErr().writeAll(msg) catch break; } + } + + make_result catch |err| { + s.result.err_code = err; + @atomicStore(Step.State, &s.state, .failure, .SeqCst); return; }; From 96d798db8b704a2fd367e58a19d6fe853a8a76a8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 19 Feb 2023 10:14:59 -0700 Subject: [PATCH 146/294] update to new for loop syntax --- lib/build_runner.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index b2d89ba79c..24fd3440a3 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -279,7 +279,7 @@ fn runStepNames( } else { try step_stack.resize(step_names.len); - for (step_names) |step_name, i| { + for (step_names, 0..) |step_name, i| { const s = b.top_level_steps.get(step_name) orelse { std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name}); process.exit(1); From cb094700631ea1ae238ea678c192ce4f85fbecc0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 19 Feb 2023 16:21:36 -0700 Subject: [PATCH 147/294] zig build: add a -j option for limiting concurrency --- lib/build_runner.zig | 20 ++++++++++++++++++-- lib/std/Thread/Pool.zig | 11 +++++++++-- src/main.zig | 4 ++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 24fd3440a3..e12d46f03d 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -87,6 +87,7 @@ pub fn main() !void { var targets = ArrayList([]const u8).init(allocator); var debug_log_scopes = ArrayList([]const u8).init(allocator); + var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = allocator }; const stderr_stream = io.getStdErr().writer(); const stdout_stream = io.getStdOut().writer(); @@ -231,6 +232,19 @@ pub fn main() !void { }; } else if (mem.eql(u8, arg, "-fno-reference-trace")) { builder.reference_trace = null; + } else if (mem.startsWith(u8, arg, "-j")) { + const num = arg["-j".len..]; + const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| { + std.debug.print("unable to parse jobs count '{s}': {s}", .{ + num, @errorName(err), + }); + process.exit(1); + }; + if (n_jobs < 1) { + std.debug.print("number of jobs must be at least 1\n", .{}); + process.exit(1); + } + thread_pool_options.n_jobs = n_jobs; } else if (mem.eql(u8, arg, "--")) { builder.args = argsRest(args, arg_idx); break; @@ -258,7 +272,7 @@ pub fn main() !void { if (builder.validateUserInputDidItFail()) usageAndErr(builder, true, stderr_stream); - runStepNames(builder, targets.items, main_progress_node) catch |err| { + runStepNames(builder, targets.items, main_progress_node, thread_pool_options) catch |err| { switch (err) { error.UncleanExit => process.exit(1), else => return err, @@ -270,6 +284,7 @@ fn runStepNames( b: *std.Build, step_names: []const []const u8, parent_prog_node: *std.Progress.Node, + thread_pool_options: std.Thread.Pool.Options, ) !void { var step_stack = ArrayList(*Step).init(b.allocator); defer step_stack.deinit(); @@ -297,7 +312,7 @@ fn runStepNames( } var thread_pool: std.Thread.Pool = undefined; - try thread_pool.init(b.allocator); + try thread_pool.init(thread_pool_options); defer thread_pool.deinit(); { @@ -523,6 +538,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\ --verbose Print commands before executing them \\ --color [auto|off|on] Enable or disable colored error messages \\ --prominent-compile-errors Output compile errors formatted for a human to read + \\ -j Limit concurrent jobs (default is to use all CPU cores) \\ \\Project-Specific Options: \\ diff --git a/lib/std/Thread/Pool.zig b/lib/std/Thread/Pool.zig index 930befbac5..ed1a4dc052 100644 --- a/lib/std/Thread/Pool.zig +++ b/lib/std/Thread/Pool.zig @@ -17,7 +17,14 @@ const Runnable = struct { const RunProto = *const fn (*Runnable) void; -pub fn init(pool: *Pool, allocator: std.mem.Allocator) !void { +pub const Options = struct { + allocator: std.mem.Allocator, + n_jobs: ?u32 = null, +}; + +pub fn init(pool: *Pool, options: Options) !void { + const allocator = options.allocator; + pool.* = .{ .allocator = allocator, .threads = &[_]std.Thread{}, @@ -27,7 +34,7 @@ pub fn init(pool: *Pool, allocator: std.mem.Allocator) !void { return; } - const thread_count = std.math.max(1, std.Thread.getCpuCount() catch 1); + const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1); pool.threads = try allocator.alloc(std.Thread, thread_count); errdefer allocator.free(pool.threads); diff --git a/src/main.zig b/src/main.zig index dd0faa628c..3a06d0272f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2999,7 +2999,7 @@ fn buildOutputType( defer zig_lib_directory.handle.close(); var thread_pool: ThreadPool = undefined; - try thread_pool.init(gpa); + try thread_pool.init(.{ .allocator = gpa }); defer thread_pool.deinit(); var libc_installation: ?LibCInstallation = null; @@ -4201,7 +4201,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi .basename = exe_basename, }; var thread_pool: ThreadPool = undefined; - try thread_pool.init(gpa); + try thread_pool.init(.{ .allocator = gpa }); defer thread_pool.deinit(); var cleanup_build_runner_dir: ?fs.Dir = null; From c641af3cba8f0e8e6bba0ba26dce18bd225a832e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 19 Feb 2023 16:23:59 -0700 Subject: [PATCH 148/294] CI: use -j1 like a coward Zig's build script has several race conditions preventing proper concurrent builds from working. By using -j1 for now, finishing this branch (concurrent zig builds) is untangled from the separate problem of correcting concurrency issues with zig's own build script. In other words, let's solve one problem at a time. --- ci/aarch64-linux-debug.sh | 1 + ci/aarch64-linux-release.sh | 1 + ci/aarch64-macos.sh | 1 + ci/x86_64-linux-debug.sh | 1 + ci/x86_64-linux-release.sh | 1 + ci/x86_64-macos-debug.sh | 1 + ci/x86_64-macos-release.sh | 1 + test/tests.zig | 4 ++++ 8 files changed, 11 insertions(+) diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index 94f40c557b..90ed8bbc35 100644 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -60,6 +60,7 @@ stage3-debug/bin/zig build -Dtarget=arm-linux-musleabihf # TODO: add -fqemu back to this line stage3-debug/bin/zig build test docs \ + -j1 \ -fwasmtime \ -Dstatic-llvm \ -Dtarget=native-native-musl \ diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index 65d6063f25..e99e3e1c08 100644 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -60,6 +60,7 @@ stage3-release/bin/zig build -Dtarget=arm-linux-musleabihf # TODO: add -fqemu back to this line stage3-release/bin/zig build test docs \ + -j1 \ -fwasmtime \ -Dstatic-llvm \ -Dtarget=native-native-musl \ diff --git a/ci/aarch64-macos.sh b/ci/aarch64-macos.sh index b4533e149f..634a60b600 100755 --- a/ci/aarch64-macos.sh +++ b/ci/aarch64-macos.sh @@ -44,6 +44,7 @@ PATH="$HOME/local/bin:$PATH" cmake .. \ $HOME/local/bin/ninja install stage3-release/bin/zig build test docs \ + -j1 \ --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index 7f2382f04a..1267a2d753 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -58,6 +58,7 @@ stage3-debug/bin/zig fmt --check .. \ stage3-debug/bin/zig build -Dtarget=arm-linux-musleabihf stage3-debug/bin/zig build test docs \ + -j1 \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index cdb24e4a6f..3305f5e951 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -59,6 +59,7 @@ stage3-release/bin/zig fmt --check .. \ stage3-release/bin/zig build -Dtarget=arm-linux-musleabihf stage3-release/bin/zig build test docs \ + -j1 \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ diff --git a/ci/x86_64-macos-debug.sh b/ci/x86_64-macos-debug.sh index c24ff5d295..edd35970b8 100755 --- a/ci/x86_64-macos-debug.sh +++ b/ci/x86_64-macos-debug.sh @@ -48,6 +48,7 @@ cmake .. \ make $JOBS install stage3/bin/zig build test docs \ + -j1 \ --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ diff --git a/ci/x86_64-macos-release.sh b/ci/x86_64-macos-release.sh index a4dedb4446..65dea3afcb 100755 --- a/ci/x86_64-macos-release.sh +++ b/ci/x86_64-macos-release.sh @@ -48,6 +48,7 @@ cmake .. \ make $JOBS install stage3/bin/zig build test docs \ + -j1 \ --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ diff --git a/test/tests.zig b/test/tests.zig index e470a4f251..fe2efbc06e 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1067,6 +1067,10 @@ pub const StandaloneContext = struct { zig_args.append(rel_zig_exe) catch unreachable; zig_args.append("build") catch unreachable; + // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, + // and then remove this! + zig_args.append("-j1") catch @panic("OOM"); + zig_args.append("--build-file") catch unreachable; zig_args.append(b.pathFromRoot(build_file)) catch unreachable; From ee693bfe04522efe556cb416050326187f168aea Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Jan 2022 18:03:59 -0700 Subject: [PATCH 149/294] std.os.linux: add ptrace --- lib/std/os/linux.zig | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index b151e5f235..218ed71ddb 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1820,6 +1820,23 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize { return syscall3(.seccomp, operation, flags, @ptrToInt(args)); } +pub fn ptrace( + req: u32, + pid: pid_t, + addr: usize, + data: usize, + addr2: usize, +) usize { + return syscall5( + .ptrace, + req, + @bitCast(usize, @as(isize, pid)), + addr, + data, + addr2, + ); +} + pub const E = switch (native_arch) { .mips, .mipsel => @import("linux/errno/mips.zig").E, .sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E, @@ -5721,3 +5738,40 @@ pub const AUDIT = struct { } }; }; + +pub const PTRACE = struct { + pub const TRACEME = 0; + pub const PEEKTEXT = 1; + pub const PEEKDATA = 2; + pub const PEEKUSER = 3; + pub const POKETEXT = 4; + pub const POKEDATA = 5; + pub const POKEUSER = 6; + pub const CONT = 7; + pub const KILL = 8; + pub const SINGLESTEP = 9; + pub const GETREGS = 12; + pub const SETREGS = 13; + pub const GETFPREGS = 14; + pub const SETFPREGS = 15; + pub const ATTACH = 16; + pub const DETACH = 17; + pub const GETFPXREGS = 18; + pub const SETFPXREGS = 19; + pub const SYSCALL = 24; + pub const SETOPTIONS = 0x4200; + pub const GETEVENTMSG = 0x4201; + pub const GETSIGINFO = 0x4202; + pub const SETSIGINFO = 0x4203; + pub const GETREGSET = 0x4204; + pub const SETREGSET = 0x4205; + pub const SEIZE = 0x4206; + pub const INTERRUPT = 0x4207; + pub const LISTEN = 0x4208; + pub const PEEKSIGINFO = 0x4209; + pub const GETSIGMASK = 0x420a; + pub const SETSIGMASK = 0x420b; + pub const SECCOMP_GET_FILTER = 0x420c; + pub const SECCOMP_GET_METADATA = 0x420d; + pub const GET_SYSCALL_INFO = 0x420e; +}; From ae8e7c8f5a6065d12087aa547971001a399667b9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Jan 2022 17:04:03 -0700 Subject: [PATCH 150/294] stage2: hot code swapping PoC * CLI supports --listen to accept commands on a socket * make it able to produce an updated executable while it is running --- lib/std/child_process.zig | 1 + src/Compilation.zig | 7 ++ src/link.zig | 13 +++ src/main.zig | 187 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 3bdef3177a..f8cba85874 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -185,6 +185,7 @@ pub const ChildProcess = struct { } /// Blocks until child process terminates and then cleans up all resources. + /// TODO: set the pid to undefined in this function. pub fn wait(self: *ChildProcess) !Term { const term = if (builtin.os.tag == .windows) try self.waitWindows() diff --git a/src/Compilation.zig b/src/Compilation.zig index 63dd229ec5..83e0f0d5d9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5663,3 +5663,10 @@ pub fn compilerRtStrip(comp: Compilation) bool { return true; } } + +pub fn hotCodeSwap(comp: *Compilation, pid: std.os.pid_t) !void { + comp.bin_file.child_pid = pid; + try comp.makeBinFileWritable(); + try comp.update(); + try comp.makeBinFileExecutable(); +} diff --git a/src/link.zig b/src/link.zig index 1ecbeadd7f..34a16a1a1b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -264,6 +264,8 @@ pub const File = struct { /// of this linking operation. lock: ?Cache.Lock = null, + child_pid: ?std.os.pid_t = null, + /// Attempts incremental linking, if the file already exists. If /// incremental linking fails, falls back to truncating the file and /// rewriting it. A malicious file is detected as incremental link failure @@ -376,6 +378,17 @@ pub const File = struct { if (build_options.only_c) unreachable; if (base.file != null) return; const emit = base.options.emit orelse return; + if (base.child_pid != null) { + // If we try to open the output file in write mode while it is running, + // it will return ETXTBSY. So instead, we copy the file, atomically rename it + // over top of the exe path, and then proceed normally. This changes the inode, + // avoiding the error. + const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{ + emit.sub_path, std.crypto.random.int(u32), + }); + try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); + try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); + } base.file = try emit.directory.handle.createFile(emit.sub_path, .{ .truncate = false, .read = true, diff --git a/src/main.zig b/src/main.zig index 3a06d0272f..a485fdb9e2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -687,6 +687,7 @@ fn buildOutputType( var function_sections = false; var no_builtin = false; var watch = false; + var listen_addr: ?std.net.Ip4Address = null; var debug_compile_errors = false; var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK"); var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC"); @@ -1144,6 +1145,17 @@ fn buildOutputType( } else { try log_scopes.append(gpa, args_iter.nextOrFatal()); } + } else if (mem.eql(u8, arg, "--listen")) { + const next_arg = args_iter.nextOrFatal(); + // example: --listen 127.0.0.1:9000 + var it = std.mem.split(u8, next_arg, ":"); + const host = it.next().?; + const port_text = it.next() orelse "14735"; + const port = std.fmt.parseInt(u16, port_text, 10) catch |err| + fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) }); + listen_addr = std.net.Ip4Address.parse(host, port) catch |err| + fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }); + watch = true; } else if (mem.eql(u8, arg, "--debug-link-snapshot")) { if (!build_options.enable_link_snapshots) { std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{}); @@ -3353,6 +3365,125 @@ fn buildOutputType( var last_cmd: ReplCmd = .help; + if (listen_addr) |ip4_addr| { + var server = std.net.StreamServer.init(.{ + .reuse_address = true, + }); + defer server.deinit(); + + try server.listen(.{ .in = ip4_addr }); + + while (true) { + const conn = try server.accept(); + defer conn.stream.close(); + + var buf: [100]u8 = undefined; + var child_pid: ?i32 = null; + + while (true) { + try comp.makeBinFileExecutable(); + + const amt = try conn.stream.read(&buf); + const line = buf[0..amt]; + const actual_line = mem.trimRight(u8, line, "\r\n "); + + const cmd: ReplCmd = blk: { + if (mem.eql(u8, actual_line, "update")) { + break :blk .update; + } else if (mem.eql(u8, actual_line, "exit")) { + break; + } else if (mem.eql(u8, actual_line, "help")) { + break :blk .help; + } else if (mem.eql(u8, actual_line, "run")) { + break :blk .run; + } else if (mem.eql(u8, actual_line, "update-and-run")) { + break :blk .update_and_run; + } else if (actual_line.len == 0) { + break :blk last_cmd; + } else { + try stderr.print("unknown command: {s}\n", .{actual_line}); + continue; + } + }; + last_cmd = cmd; + switch (cmd) { + .update => { + tracy.frameMark(); + if (output_mode == .Exe) { + try comp.makeBinFileWritable(); + } + updateModule(gpa, comp, hook) catch |err| switch (err) { + error.SemanticAnalyzeFail => continue, + else => |e| return e, + }; + }, + .help => { + try stderr.writeAll(repl_help); + }, + .run => { + tracy.frameMark(); + try runOrTest( + comp, + gpa, + arena, + test_exec_args.items, + self_exe_path.?, + arg_mode, + target_info, + watch, + &comp_destroyed, + all_args, + runtime_args_start, + link_libc, + ); + }, + .update_and_run => { + tracy.frameMark(); + if (child_pid) |pid| { + try conn.stream.writer().print("hot code swap requested for pid {d}", .{pid}); + try comp.hotCodeSwap(pid); + + var errors = try comp.getAllErrorsAlloc(); + defer errors.deinit(comp.gpa); + + if (errors.list.len != 0) { + const ttyconf: std.debug.TTY.Config = switch (comp.color) { + .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .on => .escape_codes, + .off => .no_color, + }; + for (errors.list) |full_err_msg| { + try full_err_msg.renderToWriter(ttyconf, conn.stream.writer(), "error:", .Red, 0); + } + continue; + } + } else { + if (output_mode == .Exe) { + try comp.makeBinFileWritable(); + } + updateModule(gpa, comp, hook) catch |err| switch (err) { + error.SemanticAnalyzeFail => continue, + else => |e| return e, + }; + try comp.makeBinFileExecutable(); + + child_pid = try runOrTestHotSwap( + comp, + gpa, + arena, + test_exec_args.items, + self_exe_path.?, + arg_mode, + all_args, + runtime_args_start, + ); + } + }, + } + } + } + } + while (watch) { try stderr.print("(zig) ", .{}); try comp.makeBinFileExecutable(); @@ -3631,6 +3762,62 @@ fn runOrTest( } } +fn runOrTestHotSwap( + comp: *Compilation, + gpa: Allocator, + arena: Allocator, + test_exec_args: []const ?[]const u8, + self_exe_path: []const u8, + arg_mode: ArgMode, + all_args: []const []const u8, + runtime_args_start: ?usize, +) !i32 { + const exe_emit = comp.bin_file.options.emit.?; + // A naive `directory.join` here will indeed get the correct path to the binary, + // however, in the case of cwd, we actually want `./foo` so that the path can be executed. + const exe_path = try fs.path.join(arena, &[_][]const u8{ + exe_emit.directory.path orelse ".", exe_emit.sub_path, + }); + + var argv = std.ArrayList([]const u8).init(gpa); + defer argv.deinit(); + + if (test_exec_args.len == 0) { + // when testing pass the zig_exe_path to argv + if (arg_mode == .zig_test) + try argv.appendSlice(&[_][]const u8{ + exe_path, self_exe_path, + }) + // when running just pass the current exe + else + try argv.appendSlice(&[_][]const u8{ + exe_path, + }); + } else { + for (test_exec_args) |arg| { + if (arg) |a| { + try argv.append(a); + } else { + try argv.appendSlice(&[_][]const u8{ + exe_path, self_exe_path, + }); + } + } + } + if (runtime_args_start) |i| { + try argv.appendSlice(all_args[i..]); + } + var child = std.ChildProcess.init(argv.items, arena); + + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + try child.spawn(); + + return child.pid; +} + const AfterUpdateHook = union(enum) { none, print_emit_bin_dir_path, From 50a2bb58d23a686f52b389c062f0299cc2a5f8ed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 14 Jan 2022 18:04:11 -0700 Subject: [PATCH 151/294] link: PTRACE_ATTACH/PTRACE_DETACH --- src/link.zig | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/link.zig b/src/link.zig index 34a16a1a1b..a2f40eed65 100644 --- a/src/link.zig +++ b/src/link.zig @@ -378,7 +378,7 @@ pub const File = struct { if (build_options.only_c) unreachable; if (base.file != null) return; const emit = base.options.emit orelse return; - if (base.child_pid != null) { + if (base.child_pid) |pid| { // If we try to open the output file in write mode while it is running, // it will return ETXTBSY. So instead, we copy the file, atomically rename it // over top of the exe path, and then proceed normally. This changes the inode, @@ -388,6 +388,11 @@ pub const File = struct { }); try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); + + switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0, 0))) { + .SUCCESS => {}, + else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), + } } base.file = try emit.directory.handle.createFile(emit.sub_path, .{ .truncate = false, @@ -437,6 +442,13 @@ pub const File = struct { } f.close(); base.file = null; + + if (base.child_pid) |pid| { + switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0, 0))) { + .SUCCESS => {}, + else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), + } + } }, .c, .spirv, .nvptx => {}, } From 4f4ddf5ef2f2e2ee2774e0a5311b099ea19d9ce2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 21 Jan 2022 18:47:29 -0700 Subject: [PATCH 152/294] hot code swapping PoC working - improve fn prototypes of process_vm_writev - make the memory writable in the ELF file - force the linker to always append the function - write updates with process_vm_writev --- lib/std/os/linux.zig | 20 ++++++++++---------- src/link/Elf.zig | 41 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 218ed71ddb..15b0dbc17b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1716,26 +1716,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u ); } -pub fn process_vm_readv(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize { +pub fn process_vm_readv(pid: pid_t, local: []iovec, remote: []const iovec_const, flags: usize) usize { return syscall6( .process_vm_readv, @bitCast(usize, @as(isize, pid)), - @ptrToInt(local), - local_count, - @ptrToInt(remote), - remote_count, + @ptrToInt(local.ptr), + local.len, + @ptrToInt(remote.ptr), + remote.len, flags, ); } -pub fn process_vm_writev(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize { +pub fn process_vm_writev(pid: pid_t, local: []const iovec_const, remote: []const iovec_const, flags: usize) usize { return syscall6( .process_vm_writev, @bitCast(usize, @as(isize, pid)), - @ptrToInt(local), - local_count, - @ptrToInt(remote), - remote_count, + @ptrToInt(local.ptr), + local.len, + @ptrToInt(remote.ptr), + remote.len, flags, ); } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index a91722d072..e69b937867 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -467,7 +467,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { .p_paddr = entry_addr, .p_memsz = file_size, .p_align = p_align, - .p_flags = elf.PF_X | elf.PF_R, + .p_flags = elf.PF_X | elf.PF_R | elf.PF_W, }); self.entry_addr = null; self.phdr_table_dirty = true; @@ -493,7 +493,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { .p_paddr = got_addr, .p_memsz = file_size, .p_align = p_align, - .p_flags = elf.PF_R, + .p_flags = elf.PF_R | elf.PF_W, }); self.phdr_table_dirty = true; } @@ -516,7 +516,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { .p_paddr = rodata_addr, .p_memsz = file_size, .p_align = p_align, - .p_flags = elf.PF_R, + .p_flags = elf.PF_R | elf.PF_W, }); self.phdr_table_dirty = true; } @@ -2451,6 +2451,23 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s const phdr_index = self.sections.items(.phdr_index)[shdr_index]; const section_offset = local_sym.st_value - self.program_headers.items[phdr_index].p_vaddr; const file_offset = self.sections.items(.shdr)[shdr_index].sh_offset + section_offset; + + if (self.base.child_pid) |pid| { + var code_vec: [1]std.os.iovec_const = .{.{ + .iov_base = code.ptr, + .iov_len = code.len, + }}; + var remote_vec: [1]std.os.iovec_const = .{.{ + .iov_base = @intToPtr([*]u8, local_sym.st_value), + .iov_len = code.len, + }}; + const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0); + switch (std.os.errno(rc)) { + .SUCCESS => assert(rc == code.len), + else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + } + } + try self.base.file.?.pwriteAll(code, file_offset); return local_sym; @@ -2820,6 +2837,8 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { const endian = self.base.options.target.cpu.arch.endian(); const shdr = &self.sections.items(.shdr)[self.got_section_index.?]; const off = shdr.sh_offset + @as(u64, entry_size) * index; + const phdr = &self.program_headers.items[self.phdr_got_index.?]; + const vaddr = phdr.p_vaddr + @as(u64, entry_size) * index; switch (entry_size) { 2 => { var buf: [2]u8 = undefined; @@ -2835,6 +2854,22 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { var buf: [8]u8 = undefined; mem.writeInt(u64, &buf, self.offset_table.items[index], endian); try self.base.file.?.pwriteAll(&buf, off); + + if (self.base.child_pid) |pid| { + var local_vec: [1]std.os.iovec_const = .{.{ + .iov_base = &buf, + .iov_len = buf.len, + }}; + var remote_vec: [1]std.os.iovec_const = .{.{ + .iov_base = @intToPtr([*]u8, vaddr), + .iov_len = buf.len, + }}; + const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); + switch (std.os.errno(rc)) { + .SUCCESS => assert(rc == buf.len), + else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + } + } }, else => unreachable, } From c911de825b3b9932e84345f6cec910553b21e203 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Feb 2023 11:44:03 -0700 Subject: [PATCH 153/294] link.Elf: keep the logic for updates but condition on hcs --- src/link/Elf.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index e69b937867..b849dcc9d2 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2166,7 +2166,7 @@ fn allocateAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64, alignme // First we look for an appropriately sized free list node. // The list is unordered. We'll just take the first thing that works. const vaddr = blk: { - var i: usize = 0; + var i: usize = if (self.base.child_pid == null) 0 else free_list.items.len; while (i < free_list.items.len) { const big_atom_index = free_list.items[i]; const big_atom = self.getAtom(big_atom_index); @@ -2397,7 +2397,7 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s const atom = self.getAtom(atom_index); const shdr_index = decl_metadata.shdr; - if (atom.getSymbol(self).st_size != 0) { + if (atom.getSymbol(self).st_size != 0 and self.base.child_pid == null) { const local_sym = atom.getSymbolPtr(self); local_sym.st_name = try self.shstrtab.insert(gpa, decl_name); local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; From 4db5bc7b2132d8794d98077a67fc410be9dc98bd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Feb 2023 16:18:15 -0700 Subject: [PATCH 154/294] std.mem.copy: update to new for loop syntax --- lib/std/mem.zig | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index b9b5fb1004..a486713e1c 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -196,13 +196,8 @@ test "Allocator.resize" { /// dest.len must be >= source.len. /// If the slices overlap, dest.ptr must be <= src.ptr. pub fn copy(comptime T: type, dest: []T, source: []const T) void { - // TODO instead of manually doing this check for the whole array - // and turning off runtime safety, the compiler should detect loops like - // this and automatically omit safety checks for loops - @setRuntimeSafety(false); - assert(dest.len >= source.len); - for (source, 0..) |s, i| - dest[i] = s; + for (dest[0..source.len], source) |*d, s| + d.* = s; } /// Copy all of source into dest at position 0. @@ -611,8 +606,8 @@ test "lessThan" { pub fn eql(comptime T: type, a: []const T, b: []const T) bool { if (a.len != b.len) return false; if (a.ptr == b.ptr) return true; - for (a, 0..) |item, index| { - if (b[index] != item) return false; + for (a, b) |a_elem, b_elem| { + if (a_elem != b_elem) return false; } return true; } From 572cb24d1a4f70c662ddf17df72d27dec44bc4fc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Feb 2023 16:18:43 -0700 Subject: [PATCH 155/294] progress towards semantic error serialization Introduces std.zig.ErrorBundle which is a trivially serializeable set of compilation errors. This is in the standard library so that both the compiler and the build runner can use it. The idea is they will use it to communicate compilation errors over a binary protocol. The binary encoding of ErrorBundle is a bit problematic - I got a little too aggressive with compaction. I need to change it in a follow-up commit to use some indirection in the error message list, otherwise iteration is too unergonomic. In fact it's so problematic right now that the logic getAllErrorsAlloc() actually fails to produce a viable ErrorBundle because it puts SourceLocation data in between the root level ErrorMessage data. This commit has a simplification - redundant logic for rendering AST errors to stderr has been removed in favor of moving the logic for lowering AST errors into AstGen. So even if we get parse errors, the errors will get lowered into ZIR before being reported. I believe this will be useful when working on --autofix. Either way, some redundant brittle logic was happily deleted. In Compilation, updateSubCompilation() is improved to properly perform error reporting when a sub-compilation object fails. It no longer dumps directly to stderr; instead it populates an ErrorBundle object, which gets added to the parent one during getAllErrorsAlloc(). In package fetching code, instead of dumping directly to stderr, it now populates an ErrorBundle object, and gets properly reported at the CLI layer of abstraction. --- lib/std/zig.zig | 1 + lib/std/zig/ErrorBundle.zig | 419 ++++++++++++++++ src/AstGen.zig | 96 +++- src/Compilation.zig | 947 +++++++++++++++--------------------- src/Module.zig | 60 +-- src/Package.zig | 108 ++-- src/Sema.zig | 25 +- src/glibc.zig | 10 +- src/libcxx.zig | 4 +- src/libtsan.zig | 2 +- src/libunwind.zig | 2 +- src/main.zig | 263 ++++------ src/mingw.zig | 12 +- src/musl.zig | 14 +- src/wasi_libc.zig | 14 +- 15 files changed, 1068 insertions(+), 909 deletions(-) create mode 100644 lib/std/zig/ErrorBundle.zig diff --git a/lib/std/zig.zig b/lib/std/zig.zig index f85cf75e60..ecff8e99bd 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -3,6 +3,7 @@ const tokenizer = @import("zig/tokenizer.zig"); const fmt = @import("zig/fmt.zig"); const assert = std.debug.assert; +pub const ErrorBundle = @import("zig/ErrorBundle.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const fmtId = fmt.fmtId; diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig new file mode 100644 index 0000000000..dee19818a8 --- /dev/null +++ b/lib/std/zig/ErrorBundle.zig @@ -0,0 +1,419 @@ +//! To support incremental compilation, errors are stored in various places +//! so that they can be created and destroyed appropriately. This structure +//! is used to collect all the errors from the various places into one +//! convenient place for API users to consume. + +string_bytes: std.ArrayListUnmanaged(u8), +/// The first thing in this array is a ErrorMessageListIndex. +extra: std.ArrayListUnmanaged(u32), + +// An index into `extra` pointing at an `ErrorMessage`. +pub const MessageIndex = enum(u32) { + _, +}; + +/// After the header is: +/// * string_bytes +/// * extra (little endian) +pub const Header = struct { + string_bytes_len: u32, + extra_len: u32, +}; + +/// Trailing: ErrorMessage for each len +pub const ErrorMessageList = struct { + len: u32, + start: u32, +}; + +/// Trailing: +/// * ReferenceTrace for each reference_trace_len +pub const SourceLocation = struct { + /// null terminated string index + src_path: u32, + line: u32, + column: u32, + /// byte offset of starting token + span_start: u32, + /// byte offset of main error location + span_main: u32, + /// byte offset of end of last token + span_end: u32, + /// null terminated string index, possibly null. + /// Does not include the trailing newline. + source_line: u32 = 0, + reference_trace_len: u32 = 0, +}; + +/// Trailing: +/// * ErrorMessage for each notes_len. +pub const ErrorMessage = struct { + /// null terminated string index + msg: u32, + /// Usually one, but incremented for redundant messages. + count: u32 = 1, + /// 0 or the index into extra of a SourceLocation + src_loc: u32 = 0, + notes_len: u32 = 0, +}; + +pub const ReferenceTrace = struct { + /// null terminated string index + /// Except for the sentinel ReferenceTrace element, in which case: + /// * 0 means remaining references hidden + /// * >0 means N references hidden + decl_name: u32, + /// Index into extra of a SourceLocation + /// If this is 0, this is the sentinel ReferenceTrace element. + src_loc: u32, +}; + +pub fn init(eb: *ErrorBundle, gpa: Allocator) !void { + eb.* = .{ + .string_bytes = .{}, + .extra = .{}, + }; + + // So that 0 can be used to indicate a null string. + try eb.string_bytes.append(gpa, 0); + + _ = try addExtra(eb, gpa, ErrorMessageList{ + .len = 0, + .start = 0, + }); +} + +pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void { + eb.string_bytes.deinit(gpa); + eb.extra.deinit(gpa); + eb.* = undefined; +} + +pub fn addString(eb: *ErrorBundle, gpa: Allocator, s: []const u8) !u32 { + const index = @intCast(u32, eb.string_bytes.items.len); + try eb.string_bytes.ensureUnusedCapacity(gpa, s.len + 1); + eb.string_bytes.appendSliceAssumeCapacity(s); + eb.string_bytes.appendAssumeCapacity(0); + return index; +} + +pub fn printString(eb: *ErrorBundle, gpa: Allocator, comptime fmt: []const u8, args: anytype) !u32 { + const index = @intCast(u32, eb.string_bytes.items.len); + try eb.string_bytes.writer(gpa).print(fmt, args); + try eb.string_bytes.append(gpa, 0); + return index; +} + +pub fn addErrorMessage(eb: *ErrorBundle, gpa: Allocator, em: ErrorMessage) !void { + if (eb.errorMessageCount() == 0) { + eb.setStartIndex(@intCast(u32, eb.extra.items.len)); + } + _ = try addExtra(eb, gpa, em); +} + +pub fn addSourceLocation(eb: *ErrorBundle, gpa: Allocator, sl: SourceLocation) !u32 { + return addExtra(eb, gpa, sl); +} + +pub fn addReferenceTrace(eb: *ErrorBundle, gpa: Allocator, rt: ReferenceTrace) !void { + _ = try addExtra(eb, gpa, rt); +} + +pub fn addBundle(eb: *ErrorBundle, gpa: Allocator, other: ErrorBundle) !void { + // Skip over the initial ErrorMessageList len field. + const root_fields_len = @typeInfo(ErrorMessageList).Struct.fields.len; + const other_list = other.extraData(ErrorMessageList, 0).data; + const other_extra = other.extra.items[root_fields_len..]; + + try eb.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.items.len); + try eb.extra.ensureUnusedCapacity(gpa, other_extra.len); + + const new_string_base = @intCast(u32, eb.string_bytes.items.len); + const new_data_base = @intCast(u32, eb.extra.items.len - root_fields_len); + + eb.string_bytes.appendSliceAssumeCapacity(other.string_bytes.items); + eb.extra.appendSliceAssumeCapacity(other_extra); + + // Now we must offset the string indexes and extra indexes of the newly + // added extra. + var index = new_data_base + other_list.start; + for (0..other_list.len) |_| { + index = try patchMessage(eb, index, new_string_base, new_data_base); + } +} + +fn patchMessage(eb: *ErrorBundle, msg_idx: usize, new_string_base: u32, new_data_base: u32) !u32 { + var msg = eb.extraData(ErrorMessage, msg_idx); + if (msg.data.msg != 0) msg.data.msg += new_string_base; + if (msg.data.src_loc != 0) msg.data.src_loc += new_data_base; + eb.setExtra(msg_idx, msg.data); + + try patchSrcLoc(eb, msg.data.src_loc, new_string_base, new_data_base); + + var index = @intCast(u32, msg.end); + for (0..msg.data.notes_len) |_| { + index = try patchMessage(eb, index, new_string_base, new_data_base); + } + return index; +} + +fn patchSrcLoc(eb: *ErrorBundle, idx: usize, new_string_base: u32, new_data_base: u32) !void { + if (idx == 0) return; + + var src_loc = eb.extraData(SourceLocation, idx); + if (src_loc.data.src_path != 0) src_loc.data.src_path += new_string_base; + if (src_loc.data.source_line != 0) src_loc.data.source_line += new_string_base; + eb.setExtra(idx, src_loc.data); + + var index = src_loc.end; + for (0..src_loc.data.reference_trace_len) |_| { + var ref_trace = eb.extraData(ReferenceTrace, index); + if (ref_trace.data.decl_name != 0) ref_trace.data.decl_name += new_string_base; + if (ref_trace.data.src_loc != 0) ref_trace.data.src_loc += new_data_base; + eb.setExtra(index, ref_trace.data); + try patchSrcLoc(eb, ref_trace.data.src_loc, new_string_base, new_data_base); + index = ref_trace.end; + } +} + +fn addExtra(eb: *ErrorBundle, gpa: Allocator, extra: anytype) Allocator.Error!u32 { + const fields = @typeInfo(@TypeOf(extra)).Struct.fields; + try eb.extra.ensureUnusedCapacity(gpa, fields.len); + return addExtraAssumeCapacity(eb, extra); +} + +fn addExtraAssumeCapacity(eb: *ErrorBundle, extra: anytype) u32 { + const fields = @typeInfo(@TypeOf(extra)).Struct.fields; + const result = @intCast(u32, eb.extra.items.len); + eb.extra.items.len += fields.len; + setExtra(eb, result, extra); + return result; +} + +fn setExtra(eb: *ErrorBundle, index: usize, extra: anytype) void { + const fields = @typeInfo(@TypeOf(extra)).Struct.fields; + var i = index; + inline for (fields) |field| { + eb.extra.items[i] = switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }; + i += 1; + } +} + +pub fn errorMessageCount(eb: ErrorBundle) u32 { + return eb.extra.items[0]; +} + +pub fn setErrorMessageCount(eb: *ErrorBundle, count: u32) void { + eb.extra.items[0] = count; +} + +pub fn incrementCount(eb: *ErrorBundle, delta: u32) void { + eb.extra.items[0] += delta; +} + +pub fn getStartIndex(eb: ErrorBundle) u32 { + return eb.extra.items[1]; +} + +pub fn setStartIndex(eb: *ErrorBundle, index: u32) void { + eb.extra.items[1] = index; +} + +pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage { + return eb.extraData(ErrorMessage, @enumToInt(index)).data; +} + +pub fn getSourceLocation(eb: ErrorBundle, index: u32) SourceLocation { + assert(index != 0); + return eb.extraData(SourceLocation, index).data; +} + +/// Returns the requested data, as well as the new index which is at the start of the +/// trailers for the object. +fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, end: usize } { + const fields = @typeInfo(T).Struct.fields; + var i: usize = index; + var result: T = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => eb.extra.items[i], + else => @compileError("bad field type"), + }; + i += 1; + } + return .{ + .data = result, + .end = i, + }; +} + +/// Given an index into `string_bytes` returns the null-terminated string found there. +pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 { + const string_bytes = eb.string_bytes.items; + var end: usize = index; + while (string_bytes[end] != 0) { + end += 1; + } + return string_bytes[index..end :0]; +} + +pub fn renderToStdErr(eb: ErrorBundle, ttyconf: std.debug.TTY.Config) void { + std.debug.getStderrMutex().lock(); + defer std.debug.getStderrMutex().unlock(); + const stderr = std.io.getStdErr(); + return renderToWriter(eb, ttyconf, stderr.writer()) catch return; +} + +pub fn renderToWriter( + eb: ErrorBundle, + ttyconf: std.debug.TTY.Config, + writer: anytype, +) anyerror!void { + const list = eb.extraData(ErrorMessageList, 0).data; + var index: usize = list.start; + for (0..list.len) |_| { + const err_msg = eb.extraData(ErrorMessage, index); + index = try renderErrorMessageToWriter(eb, err_msg.data, err_msg.end, ttyconf, writer, "error", .Red, 0); + } +} + +fn renderErrorMessageToWriter( + eb: ErrorBundle, + err_msg: ErrorMessage, + end_index: usize, + ttyconf: std.debug.TTY.Config, + stderr: anytype, + kind: []const u8, + color: std.debug.TTY.Color, + indent: usize, +) anyerror!usize { + var counting_writer = std.io.countingWriter(stderr); + const counting_stderr = counting_writer.writer(); + if (err_msg.src_loc != 0) { + const src = eb.extraData(SourceLocation, err_msg.src_loc); + try counting_stderr.writeByteNTimes(' ', indent); + try ttyconf.setColor(stderr, .Bold); + try counting_stderr.print("{s}:{d}:{d}: ", .{ + eb.nullTerminatedString(src.data.src_path), + src.data.line + 1, + src.data.column + 1, + }); + try ttyconf.setColor(stderr, color); + try counting_stderr.writeAll(kind); + try counting_stderr.writeAll(": "); + // This is the length of the part before the error message: + // e.g. "file.zig:4:5: error: " + const prefix_len = @intCast(usize, counting_stderr.context.bytes_written); + try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .Bold); + if (err_msg.count == 1) { + try writeMsg(eb, err_msg, stderr, prefix_len); + try stderr.writeByte('\n'); + } else { + try writeMsg(eb, err_msg, stderr, prefix_len); + try ttyconf.setColor(stderr, .Dim); + try stderr.print(" ({d} times)\n", .{err_msg.count}); + } + try ttyconf.setColor(stderr, .Reset); + if (src.data.source_line != 0) { + const line = eb.nullTerminatedString(src.data.source_line); + for (line) |b| switch (b) { + '\t' => try stderr.writeByte(' '), + else => try stderr.writeByte(b), + }; + try stderr.writeByte('\n'); + // TODO basic unicode code point monospace width + const before_caret = src.data.span_main - src.data.span_start; + // -1 since span.main includes the caret + const after_caret = src.data.span_end - src.data.span_main -| 1; + try stderr.writeByteNTimes(' ', src.data.column - before_caret); + try ttyconf.setColor(stderr, .Green); + try stderr.writeByteNTimes('~', before_caret); + try stderr.writeByte('^'); + try stderr.writeByteNTimes('~', after_caret); + try stderr.writeByte('\n'); + try ttyconf.setColor(stderr, .Reset); + } + var index = end_index; + for (0..err_msg.notes_len) |_| { + const note = eb.extraData(ErrorMessage, index); + index = try renderErrorMessageToWriter(eb, note.data, note.end, ttyconf, stderr, "note", .Cyan, indent); + } + if (src.data.reference_trace_len > 0) { + try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .Dim); + try stderr.print("referenced by:\n", .{}); + var ref_index = src.end; + for (0..src.data.reference_trace_len) |_| { + const ref_trace = eb.extraData(ReferenceTrace, ref_index); + ref_index = ref_trace.end; + if (ref_trace.data.src_loc != 0) { + const ref_src = eb.getSourceLocation(ref_trace.data.src_loc); + try stderr.print(" {s}: {s}:{d}:{d}\n", .{ + eb.nullTerminatedString(ref_trace.data.decl_name), + eb.nullTerminatedString(ref_src.src_path), + ref_src.line + 1, + ref_src.column + 1, + }); + } else if (ref_trace.data.decl_name != 0) { + const count = ref_trace.data.decl_name; + try stderr.print( + " {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n", + .{ count, count + src.data.reference_trace_len - 1 }, + ); + } else { + try stderr.print( + " remaining reference traces hidden; use '-freference-trace' to see all reference traces\n", + .{}, + ); + } + } + try stderr.writeByte('\n'); + try ttyconf.setColor(stderr, .Reset); + } + return index; + } else { + try ttyconf.setColor(stderr, color); + try stderr.writeByteNTimes(' ', indent); + try stderr.writeAll(kind); + try stderr.writeAll(": "); + try ttyconf.setColor(stderr, .Reset); + const msg = eb.nullTerminatedString(err_msg.msg); + if (err_msg.count == 1) { + try stderr.print("{s}\n", .{msg}); + } else { + try stderr.print("{s}", .{msg}); + try ttyconf.setColor(stderr, .Dim); + try stderr.print(" ({d} times)\n", .{err_msg.count}); + } + try ttyconf.setColor(stderr, .Reset); + var index = end_index; + for (0..err_msg.notes_len) |_| { + const note = eb.extraData(ErrorMessage, index); + index = try renderErrorMessageToWriter(eb, note.data, note.end, ttyconf, stderr, "note", .Cyan, indent + 4); + } + return index; + } +} + +/// Splits the error message up into lines to properly indent them +/// to allow for long, good-looking error messages. +/// +/// This is used to split the message in `@compileError("hello\nworld")` for example. +fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, stderr: anytype, indent: usize) !void { + var lines = std.mem.split(u8, eb.nullTerminatedString(err_msg.msg), "\n"); + while (lines.next()) |line| { + try stderr.writeAll(line); + if (lines.index == null) break; + try stderr.writeByte('\n'); + try stderr.writeByteNTimes(' ', indent); + } +} + +const std = @import("std"); +const ErrorBundle = @This(); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; diff --git a/src/AstGen.zig b/src/AstGen.zig index 7b2138a535..8ac67a7107 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -133,6 +133,8 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { try astgen.extra.ensureTotalCapacity(gpa, tree.nodes.len + reserved_count); astgen.extra.items.len += reserved_count; + try lowerAstErrors(&astgen); + var top_scope: Scope.Top = .{}; var gz_instructions: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; @@ -10401,27 +10403,11 @@ fn appendErrorTokNotes( args: anytype, notes: []const u32, ) !void { - @setCold(true); - const string_bytes = &astgen.string_bytes; - const msg = @intCast(u32, string_bytes.items.len); - try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args); - const notes_index: u32 = if (notes.len != 0) blk: { - const notes_start = astgen.extra.items.len; - try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len); - astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len)); - astgen.extra.appendSliceAssumeCapacity(notes); - break :blk @intCast(u32, notes_start); - } else 0; - try astgen.compile_errors.append(astgen.gpa, .{ - .msg = msg, - .node = 0, - .token = token, - .byte_offset = 0, - .notes = notes_index, - }); + return appendErrorTokNotesOff(astgen, token, 0, format, args, notes); } -/// Same as `fail`, except given an absolute byte offset. +/// Same as `fail`, except given a token plus an offset from its starting byte +/// offset. fn failOff( astgen: *AstGen, token: Ast.TokenIndex, @@ -10429,27 +10415,36 @@ fn failOff( comptime format: []const u8, args: anytype, ) InnerError { - try appendErrorOff(astgen, token, byte_offset, format, args); + try appendErrorTokNotesOff(astgen, token, byte_offset, format, args, &.{}); return error.AnalysisFail; } -fn appendErrorOff( +fn appendErrorTokNotesOff( astgen: *AstGen, token: Ast.TokenIndex, byte_offset: u32, comptime format: []const u8, args: anytype, -) Allocator.Error!void { + notes: []const u32, +) !void { @setCold(true); + const gpa = astgen.gpa; const string_bytes = &astgen.string_bytes; const msg = @intCast(u32, string_bytes.items.len); - try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args); - try astgen.compile_errors.append(astgen.gpa, .{ + try string_bytes.writer(gpa).print(format ++ "\x00", args); + const notes_index: u32 = if (notes.len != 0) blk: { + const notes_start = astgen.extra.items.len; + try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len); + astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len)); + astgen.extra.appendSliceAssumeCapacity(notes); + break :blk @intCast(u32, notes_start); + } else 0; + try astgen.compile_errors.append(gpa, .{ .msg = msg, .node = 0, .token = token, .byte_offset = byte_offset, - .notes = 0, + .notes = notes_index, }); } @@ -10458,6 +10453,16 @@ fn errNoteTok( token: Ast.TokenIndex, comptime format: []const u8, args: anytype, +) Allocator.Error!u32 { + return errNoteTokOff(astgen, token, 0, format, args); +} + +fn errNoteTokOff( + astgen: *AstGen, + token: Ast.TokenIndex, + byte_offset: u32, + comptime format: []const u8, + args: anytype, ) Allocator.Error!u32 { @setCold(true); const string_bytes = &astgen.string_bytes; @@ -10467,7 +10472,7 @@ fn errNoteTok( .msg = msg, .node = 0, .token = token, - .byte_offset = 0, + .byte_offset = byte_offset, .notes = 0, }); } @@ -12634,3 +12639,42 @@ fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void { }, } }); } + +fn lowerAstErrors(astgen: *AstGen) !void { + const tree = astgen.tree; + if (tree.errors.len == 0) return; + + const gpa = astgen.gpa; + const parse_err = tree.errors[0]; + + var msg: std.ArrayListUnmanaged(u8) = .{}; + defer msg.deinit(gpa); + + const token_starts = tree.tokens.items(.start); + const token_tags = tree.tokens.items(.tag); + + var notes: std.ArrayListUnmanaged(u32) = .{}; + defer notes.deinit(gpa); + + if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) { + const tok = parse_err.token + @boolToInt(parse_err.token_is_prev); + const bad_off = @intCast(u32, tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len); + const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off; + try notes.append(gpa, try astgen.errNoteTokOff(tok, bad_off, "invalid byte: '{'}'", .{ + std.zig.fmtEscapes(tree.source[byte_abs..][0..1]), + })); + } + + for (tree.errors[1..]) |note| { + if (!note.is_note) break; + + msg.clearRetainingCapacity(); + try tree.renderError(note, msg.writer(gpa)); + try notes.append(gpa, try astgen.errNoteTok(note.token, "{s}", .{msg.items})); + } + + const extra_offset = tree.errorOffset(parse_err); + msg.clearRetainingCapacity(); + try tree.renderError(parse_err, msg.writer(gpa)); + try astgen.appendErrorTokNotesOff(parse_err.token, extra_offset, "{s}", .{msg.items}, notes.items); +} diff --git a/src/Compilation.zig b/src/Compilation.zig index 83e0f0d5d9..f5a92c66ea 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -9,6 +9,7 @@ const log = std.log.scoped(.compilation); const Target = std.Target; const ThreadPool = std.Thread.Pool; const WaitGroup = std.Thread.WaitGroup; +const ErrorBundle = std.zig.ErrorBundle; const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; @@ -334,12 +335,41 @@ pub const MiscTask = enum { libssp, zig_libc, analyze_pkg, + + @"musl crti.o", + @"musl crtn.o", + @"musl crt1.o", + @"musl rcrt1.o", + @"musl Scrt1.o", + @"musl libc.a", + @"musl libc.so", + + @"wasi crt1-reactor.o", + @"wasi crt1-command.o", + @"wasi libc.a", + @"libwasi-emulated-process-clocks.a", + @"libwasi-emulated-getpid.a", + @"libwasi-emulated-mman.a", + @"libwasi-emulated-signal.a", + + @"glibc crti.o", + @"glibc crtn.o", + @"glibc Scrt1.o", + @"glibc libc_nonshared.a", + @"glibc shared object", + + @"mingw-w64 crt2.o", + @"mingw-w64 dllcrt2.o", + @"mingw-w64 mingw32.lib", + @"mingw-w64 msvcrt-os.lib", + @"mingw-w64 mingwex.lib", + @"mingw-w64 uuid.lib", }; pub const MiscError = struct { /// Allocated with gpa. msg: []u8, - children: ?AllErrors = null, + children: ?ErrorBundle = null, pub fn deinit(misc_err: *MiscError, gpa: Allocator) void { gpa.free(misc_err.msg); @@ -365,448 +395,6 @@ pub const LldError = struct { } }; -/// To support incremental compilation, errors are stored in various places -/// so that they can be created and destroyed appropriately. This structure -/// is used to collect all the errors from the various places into one -/// convenient place for API users to consume. It is allocated into 1 arena -/// and freed all at once. -pub const AllErrors = struct { - arena: std.heap.ArenaAllocator.State, - list: []const Message, - - pub const Message = union(enum) { - src: struct { - msg: []const u8, - src_path: []const u8, - line: u32, - column: u32, - span: Module.SrcLoc.Span, - /// Usually one, but incremented for redundant messages. - count: u32 = 1, - /// Does not include the trailing newline. - source_line: ?[]const u8, - notes: []const Message = &.{}, - reference_trace: []Message = &.{}, - - /// Splits the error message up into lines to properly indent them - /// to allow for long, good-looking error messages. - /// - /// This is used to split the message in `@compileError("hello\nworld")` for example. - fn writeMsg(src: @This(), stderr: anytype, indent: usize) !void { - var lines = mem.split(u8, src.msg, "\n"); - while (lines.next()) |line| { - try stderr.writeAll(line); - if (lines.index == null) break; - try stderr.writeByte('\n'); - try stderr.writeByteNTimes(' ', indent); - } - } - }, - plain: struct { - msg: []const u8, - notes: []Message = &.{}, - /// Usually one, but incremented for redundant messages. - count: u32 = 1, - }, - - pub fn incrementCount(msg: *Message) void { - switch (msg.*) { - .src => |*src| { - src.count += 1; - }, - .plain => |*plain| { - plain.count += 1; - }, - } - } - - pub fn renderToStdErr(msg: Message, ttyconf: std.debug.TTY.Config) void { - std.debug.getStderrMutex().lock(); - defer std.debug.getStderrMutex().unlock(); - const stderr = std.io.getStdErr(); - return msg.renderToWriter(ttyconf, stderr.writer(), "error", .Red, 0) catch return; - } - - pub fn renderToWriter( - msg: Message, - ttyconf: std.debug.TTY.Config, - stderr: anytype, - kind: []const u8, - color: std.debug.TTY.Color, - indent: usize, - ) anyerror!void { - var counting_writer = std.io.countingWriter(stderr); - const counting_stderr = counting_writer.writer(); - switch (msg) { - .src => |src| { - try counting_stderr.writeByteNTimes(' ', indent); - try ttyconf.setColor(stderr, .Bold); - try counting_stderr.print("{s}:{d}:{d}: ", .{ - src.src_path, - src.line + 1, - src.column + 1, - }); - try ttyconf.setColor(stderr, color); - try counting_stderr.writeAll(kind); - try counting_stderr.writeAll(": "); - // This is the length of the part before the error message: - // e.g. "file.zig:4:5: error: " - const prefix_len = @intCast(usize, counting_stderr.context.bytes_written); - try ttyconf.setColor(stderr, .Reset); - try ttyconf.setColor(stderr, .Bold); - if (src.count == 1) { - try src.writeMsg(stderr, prefix_len); - try stderr.writeByte('\n'); - } else { - try src.writeMsg(stderr, prefix_len); - try ttyconf.setColor(stderr, .Dim); - try stderr.print(" ({d} times)\n", .{src.count}); - } - try ttyconf.setColor(stderr, .Reset); - if (src.source_line) |line| { - for (line) |b| switch (b) { - '\t' => try stderr.writeByte(' '), - else => try stderr.writeByte(b), - }; - try stderr.writeByte('\n'); - // TODO basic unicode code point monospace width - const before_caret = src.span.main - src.span.start; - // -1 since span.main includes the caret - const after_caret = src.span.end - src.span.main -| 1; - try stderr.writeByteNTimes(' ', src.column - before_caret); - try ttyconf.setColor(stderr, .Green); - try stderr.writeByteNTimes('~', before_caret); - try stderr.writeByte('^'); - try stderr.writeByteNTimes('~', after_caret); - try stderr.writeByte('\n'); - try ttyconf.setColor(stderr, .Reset); - } - for (src.notes) |note| { - try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent); - } - if (src.reference_trace.len != 0) { - try ttyconf.setColor(stderr, .Reset); - try ttyconf.setColor(stderr, .Dim); - try stderr.print("referenced by:\n", .{}); - for (src.reference_trace) |reference| { - switch (reference) { - .src => |ref_src| try stderr.print(" {s}: {s}:{d}:{d}\n", .{ - ref_src.msg, - ref_src.src_path, - ref_src.line + 1, - ref_src.column + 1, - }), - .plain => |plain| if (plain.count != 0) { - try stderr.print( - " {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n", - .{ plain.count, plain.count + src.reference_trace.len - 1 }, - ); - } else { - try stderr.print( - " remaining reference traces hidden; use '-freference-trace' to see all reference traces\n", - .{}, - ); - }, - } - } - try stderr.writeByte('\n'); - try ttyconf.setColor(stderr, .Reset); - } - }, - .plain => |plain| { - try ttyconf.setColor(stderr, color); - try stderr.writeByteNTimes(' ', indent); - try stderr.writeAll(kind); - try stderr.writeAll(": "); - try ttyconf.setColor(stderr, .Reset); - if (plain.count == 1) { - try stderr.print("{s}\n", .{plain.msg}); - } else { - try stderr.print("{s}", .{plain.msg}); - try ttyconf.setColor(stderr, .Dim); - try stderr.print(" ({d} times)\n", .{plain.count}); - } - try ttyconf.setColor(stderr, .Reset); - for (plain.notes) |note| { - try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent + 4); - } - }, - } - } - - pub const HashContext = struct { - pub fn hash(ctx: HashContext, key: *Message) u64 { - _ = ctx; - var hasher = std.hash.Wyhash.init(0); - - switch (key.*) { - .src => |src| { - hasher.update(src.msg); - hasher.update(src.src_path); - std.hash.autoHash(&hasher, src.line); - std.hash.autoHash(&hasher, src.column); - std.hash.autoHash(&hasher, src.span.main); - }, - .plain => |plain| { - hasher.update(plain.msg); - }, - } - - return hasher.final(); - } - - pub fn eql(ctx: HashContext, a: *Message, b: *Message) bool { - _ = ctx; - switch (a.*) { - .src => |a_src| switch (b.*) { - .src => |b_src| { - return mem.eql(u8, a_src.msg, b_src.msg) and - mem.eql(u8, a_src.src_path, b_src.src_path) and - a_src.line == b_src.line and - a_src.column == b_src.column and - a_src.span.main == b_src.span.main; - }, - .plain => return false, - }, - .plain => |a_plain| switch (b.*) { - .src => return false, - .plain => |b_plain| { - return mem.eql(u8, a_plain.msg, b_plain.msg); - }, - }, - } - } - }; - }; - - pub fn deinit(self: *AllErrors, gpa: Allocator) void { - self.arena.promote(gpa).deinit(); - } - - pub fn add( - module: *Module, - arena: *std.heap.ArenaAllocator, - errors: *std.ArrayList(Message), - module_err_msg: Module.ErrorMsg, - ) !void { - const allocator = arena.allocator(); - - const notes_buf = try allocator.alloc(Message, module_err_msg.notes.len); - var note_i: usize = 0; - - // De-duplicate error notes. The main use case in mind for this is - // too many "note: called from here" notes when eval branch quota is reached. - var seen_notes = std.HashMap( - *Message, - void, - Message.HashContext, - std.hash_map.default_max_load_percentage, - ).init(allocator); - const err_source = module_err_msg.src_loc.file_scope.getSource(module.gpa) catch |err| { - const file_path = try module_err_msg.src_loc.file_scope.fullPath(allocator); - try errors.append(.{ - .plain = .{ - .msg = try std.fmt.allocPrint(allocator, "unable to load '{s}': {s}", .{ - file_path, @errorName(err), - }), - }, - }); - return; - }; - const err_span = try module_err_msg.src_loc.span(module.gpa); - const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main); - - for (module_err_msg.notes) |module_note| { - const source = try module_note.src_loc.file_scope.getSource(module.gpa); - const span = try module_note.src_loc.span(module.gpa); - const loc = std.zig.findLineColumn(source.bytes, span.main); - const file_path = try module_note.src_loc.file_scope.fullPath(allocator); - const note = ¬es_buf[note_i]; - note.* = .{ - .src = .{ - .src_path = file_path, - .msg = try allocator.dupe(u8, module_note.msg), - .span = span, - .line = @intCast(u32, loc.line), - .column = @intCast(u32, loc.column), - .source_line = if (err_loc.eql(loc)) null else try allocator.dupe(u8, loc.source_line), - }, - }; - const gop = try seen_notes.getOrPut(note); - if (gop.found_existing) { - gop.key_ptr.*.incrementCount(); - } else { - note_i += 1; - } - } - - const reference_trace = try allocator.alloc(Message, module_err_msg.reference_trace.len); - for (reference_trace, 0..) |*reference, i| { - const module_reference = module_err_msg.reference_trace[i]; - if (module_reference.hidden != 0) { - reference.* = .{ .plain = .{ .msg = undefined, .count = module_reference.hidden } }; - break; - } else if (module_reference.decl == null) { - reference.* = .{ .plain = .{ .msg = undefined, .count = 0 } }; - break; - } - const source = try module_reference.src_loc.file_scope.getSource(module.gpa); - const span = try module_reference.src_loc.span(module.gpa); - const loc = std.zig.findLineColumn(source.bytes, span.main); - const file_path = try module_reference.src_loc.file_scope.fullPath(allocator); - reference.* = .{ - .src = .{ - .src_path = file_path, - .msg = try allocator.dupe(u8, std.mem.sliceTo(module_reference.decl.?, 0)), - .span = span, - .line = @intCast(u32, loc.line), - .column = @intCast(u32, loc.column), - .source_line = null, - }, - }; - } - const file_path = try module_err_msg.src_loc.file_scope.fullPath(allocator); - try errors.append(.{ - .src = .{ - .src_path = file_path, - .msg = try allocator.dupe(u8, module_err_msg.msg), - .span = err_span, - .line = @intCast(u32, err_loc.line), - .column = @intCast(u32, err_loc.column), - .notes = notes_buf[0..note_i], - .reference_trace = reference_trace, - .source_line = if (module_err_msg.src_loc.lazy == .entire_file) null else try allocator.dupe(u8, err_loc.source_line), - }, - }); - } - - pub fn addZir( - arena: Allocator, - errors: *std.ArrayList(Message), - file: *Module.File, - ) !void { - assert(file.zir_loaded); - assert(file.tree_loaded); - assert(file.source_loaded); - const payload_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)]; - assert(payload_index != 0); - - const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index); - const items_len = header.data.items_len; - var extra_index = header.end; - var item_i: usize = 0; - while (item_i < items_len) : (item_i += 1) { - const item = file.zir.extraData(Zir.Inst.CompileErrors.Item, extra_index); - extra_index = item.end; - const err_span = blk: { - if (item.data.node != 0) { - break :blk Module.SrcLoc.nodeToSpan(&file.tree, item.data.node); - } - const token_starts = file.tree.tokens.items(.start); - const start = token_starts[item.data.token] + item.data.byte_offset; - const end = start + @intCast(u32, file.tree.tokenSlice(item.data.token).len) - item.data.byte_offset; - break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start }; - }; - const err_loc = std.zig.findLineColumn(file.source, err_span.main); - - var notes: []Message = &[0]Message{}; - if (item.data.notes != 0) { - const block = file.zir.extraData(Zir.Inst.Block, item.data.notes); - const body = file.zir.extra[block.end..][0..block.data.body_len]; - notes = try arena.alloc(Message, body.len); - for (notes, 0..) |*note, i| { - const note_item = file.zir.extraData(Zir.Inst.CompileErrors.Item, body[i]); - const msg = file.zir.nullTerminatedString(note_item.data.msg); - const span = blk: { - if (note_item.data.node != 0) { - break :blk Module.SrcLoc.nodeToSpan(&file.tree, note_item.data.node); - } - const token_starts = file.tree.tokens.items(.start); - const start = token_starts[note_item.data.token] + note_item.data.byte_offset; - const end = start + @intCast(u32, file.tree.tokenSlice(note_item.data.token).len) - item.data.byte_offset; - break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start }; - }; - const loc = std.zig.findLineColumn(file.source, span.main); - - note.* = .{ - .src = .{ - .src_path = try file.fullPath(arena), - .msg = try arena.dupe(u8, msg), - .span = span, - .line = @intCast(u32, loc.line), - .column = @intCast(u32, loc.column), - .notes = &.{}, // TODO rework this function to be recursive - .source_line = if (loc.eql(err_loc)) null else try arena.dupe(u8, loc.source_line), - }, - }; - } - } - - const msg = file.zir.nullTerminatedString(item.data.msg); - try errors.append(.{ - .src = .{ - .src_path = try file.fullPath(arena), - .msg = try arena.dupe(u8, msg), - .span = err_span, - .line = @intCast(u32, err_loc.line), - .column = @intCast(u32, err_loc.column), - .notes = notes, - .source_line = try arena.dupe(u8, err_loc.source_line), - }, - }); - } - } - - fn addPlain( - arena: *std.heap.ArenaAllocator, - errors: *std.ArrayList(Message), - msg: []const u8, - ) !void { - _ = arena; - try errors.append(.{ .plain = .{ .msg = msg } }); - } - - fn addPlainWithChildren( - arena: *std.heap.ArenaAllocator, - errors: *std.ArrayList(Message), - msg: []const u8, - optional_children: ?AllErrors, - ) !void { - const allocator = arena.allocator(); - const duped_msg = try allocator.dupe(u8, msg); - if (optional_children) |*children| { - try errors.append(.{ .plain = .{ - .msg = duped_msg, - .notes = try dupeList(children.list, allocator), - } }); - } else { - try errors.append(.{ .plain = .{ .msg = duped_msg } }); - } - } - - fn dupeList(list: []const Message, arena: Allocator) Allocator.Error![]Message { - const duped_list = try arena.alloc(Message, list.len); - for (list, 0..) |item, i| { - duped_list[i] = switch (item) { - .src => |src| .{ .src = .{ - .msg = try arena.dupe(u8, src.msg), - .src_path = try arena.dupe(u8, src.src_path), - .line = src.line, - .column = src.column, - .span = src.span, - .source_line = if (src.source_line) |s| try arena.dupe(u8, s) else null, - .notes = try dupeList(src.notes, arena), - } }, - .plain => |plain| .{ .plain = .{ - .msg = try arena.dupe(u8, plain.msg), - .notes = try dupeList(plain.notes, arena), - } }, - }; - } - return duped_list; - } -}; - pub const Directory = Cache.Directory; pub const EmitLoc = struct { @@ -2891,7 +2479,7 @@ pub fn makeBinFileWritable(self: *Compilation) !void { } /// This function is temporally single-threaded. -pub fn totalErrorCount(self: *Compilation) usize { +pub fn totalErrorCount(self: *Compilation) u32 { var total: usize = self.failed_c_objects.count() + self.misc_failures.count() + @boolToInt(self.alloc_failure_occurred) + self.lld_errors.items.len; @@ -2951,17 +2539,16 @@ pub fn totalErrorCount(self: *Compilation) usize { } } - return total; + return @intCast(u32, total); } /// This function is temporally single-threaded. -pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { - var arena = std.heap.ArenaAllocator.init(self.gpa); - errdefer arena.deinit(); - const arena_allocator = arena.allocator(); +pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { + const gpa = self.gpa; - var errors = std.ArrayList(AllErrors.Message).init(self.gpa); - defer errors.deinit(); + var bundle: ErrorBundle = undefined; + try bundle.init(gpa); + errdefer bundle.deinit(gpa); { var it = self.failed_c_objects.iterator(); @@ -2970,53 +2557,63 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { const err_msg = entry.value_ptr.*; // TODO these fields will need to be adjusted when we have proper // C error reporting bubbling up. - try errors.append(.{ - .src = .{ - .src_path = try arena_allocator.dupe(u8, c_object.src.src_path), - .msg = try std.fmt.allocPrint(arena_allocator, "unable to build C object: {s}", .{ - err_msg.msg, - }), - .span = .{ .start = 0, .end = 1, .main = 0 }, + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.printString(gpa, "unable to build C object: {s}", .{ + err_msg.msg, + }), + .src_loc = try bundle.addSourceLocation(gpa, .{ + .src_path = try bundle.addString(gpa, c_object.src.src_path), + .span_start = 0, + .span_main = 0, + .span_end = 1, .line = err_msg.line, .column = err_msg.column, - .source_line = null, // TODO - }, + .source_line = 0, // TODO + }), + }); + bundle.incrementCount(1); + } + } + + for (self.lld_errors.items) |lld_error| { + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, lld_error.msg), + .notes_len = @intCast(u32, lld_error.context_lines.len), + }); + bundle.incrementCount(1); + + for (lld_error.context_lines) |context_line| { + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, context_line), }); } } - for (self.lld_errors.items) |lld_error| { - const notes = try arena_allocator.alloc(AllErrors.Message, lld_error.context_lines.len); - for (lld_error.context_lines, 0..) |context_line, i| { - notes[i] = .{ .plain = .{ - .msg = try arena_allocator.dupe(u8, context_line), - } }; - } - - try errors.append(.{ - .plain = .{ - .msg = try arena_allocator.dupe(u8, lld_error.msg), - .notes = notes, - }, - }); - } for (self.misc_failures.values()) |*value| { - try AllErrors.addPlainWithChildren(&arena, &errors, value.msg, value.children); + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, value.msg), + .notes_len = if (value.children) |b| b.errorMessageCount() else 0, + }); + if (value.children) |b| try bundle.addBundle(gpa, b); + bundle.incrementCount(1); } if (self.alloc_failure_occurred) { - try AllErrors.addPlain(&arena, &errors, "memory allocation failure"); + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, "memory allocation failure"), + }); + bundle.incrementCount(1); } if (self.bin_file.options.module) |module| { { var it = module.failed_files.iterator(); while (it.next()) |entry| { if (entry.value_ptr.*) |msg| { - try AllErrors.add(module, &arena, &errors, msg.*); + try addModuleErrorMsg(gpa, &bundle, msg.*); } else { // Must be ZIR errors. In order for ZIR errors to exist, the parsing // must have completed successfully. const tree = try entry.key_ptr.*.getTree(module.gpa); assert(tree.errors.len == 0); - try AllErrors.addZir(arena_allocator, &errors, entry.key_ptr.*); + try addZirErrorMessages(gpa, &bundle, entry.key_ptr.*); } } } @@ -3024,7 +2621,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { var it = module.failed_embed_files.iterator(); while (it.next()) |entry| { const msg = entry.value_ptr.*; - try AllErrors.add(module, &arena, &errors, msg.*); + try addModuleErrorMsg(gpa, &bundle, msg.*); } } { @@ -3034,23 +2631,21 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { // Skip errors for Decls within files that had a parse failure. // We'll try again once parsing succeeds. if (decl.getFileScope().okToReportErrors()) { - try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*); + try addModuleErrorMsg(gpa, &bundle, entry.value_ptr.*.*); if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| { - if (c_error.path) |some| - try errors.append(.{ - .src = .{ - .src_path = try arena_allocator.dupe(u8, std.mem.span(some)), - .span = .{ .start = c_error.offset, .end = c_error.offset + 1, .main = c_error.offset }, - .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)), - .line = c_error.line, - .column = c_error.column, - .source_line = if (c_error.source_line) |line| try arena_allocator.dupe(u8, std.mem.span(line)) else null, - }, - }) - else - try errors.append(.{ - .plain = .{ .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)) }, - }); + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, std.mem.span(c_error.msg)), + .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(gpa, .{ + .src_path = try bundle.addString(gpa, std.mem.span(some)), + .span_start = c_error.offset, + .span_main = c_error.offset, + .span_end = c_error.offset + 1, + .line = c_error.line, + .column = c_error.column, + .source_line = if (c_error.source_line) |line| try bundle.addString(gpa, std.mem.span(line)) else 0, + }) else 0, + }); + bundle.incrementCount(1); }; } } @@ -3062,45 +2657,40 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { // Skip errors for Decls within files that had a parse failure. // We'll try again once parsing succeeds. if (decl.getFileScope().okToReportErrors()) { - try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*); + try addModuleErrorMsg(gpa, &bundle, entry.value_ptr.*.*); } } } for (module.failed_exports.values()) |value| { - try AllErrors.add(module, &arena, &errors, value.*); + try addModuleErrorMsg(gpa, &bundle, value.*); } } - if (errors.items.len == 0) { + if (bundle.errorMessageCount() == 0) { if (self.link_error_flags.no_entry_point_found) { - try errors.append(.{ - .plain = .{ - .msg = try std.fmt.allocPrint(arena_allocator, "no entry point found", .{}), - }, + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, "no entry point found"), }); + bundle.incrementCount(1); } } if (self.link_error_flags.missing_libc) { - const notes = try arena_allocator.create([2]AllErrors.Message); - notes.* = .{ - .{ .plain = .{ - .msg = try arena_allocator.dupe(u8, "run 'zig libc -h' to learn about libc installations"), - } }, - .{ .plain = .{ - .msg = try arena_allocator.dupe(u8, "run 'zig targets' to see the targets for which zig can always provide libc"), - } }, - }; - try errors.append(.{ - .plain = .{ - .msg = try std.fmt.allocPrint(arena_allocator, "libc not available", .{}), - .notes = notes, - }, + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, "libc not available"), + .notes_len = 2, }); + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, "run 'zig libc -h' to learn about libc installations"), + }); + try bundle.addErrorMessage(gpa, .{ + .msg = try bundle.addString(gpa, "run 'zig targets' to see the targets for which zig can always provide libc"), + }); + bundle.incrementCount(1); } if (self.bin_file.options.module) |module| { - if (errors.items.len == 0 and module.compile_log_decls.count() != 0) { + if (bundle.errorMessageCount() == 0 and module.compile_log_decls.count() != 0) { const keys = module.compile_log_decls.keys(); const values = module.compile_log_decls.values(); // First one will be the error; subsequent ones will be notes. @@ -3121,16 +2711,259 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }; } - try AllErrors.add(module, &arena, &errors, err_msg); + try addModuleErrorMsg(gpa, &bundle, err_msg); } } - assert(errors.items.len == self.totalErrorCount()); + assert(self.totalErrorCount() == bundle.errorMessageCount()); - return AllErrors{ - .list = try arena_allocator.dupe(AllErrors.Message, errors.items), - .arena = arena.state, + return bundle; +} + +pub const ErrorNoteHashContext = struct { + eb: *const ErrorBundle, + + pub fn hash(ctx: ErrorNoteHashContext, key: ErrorBundle.ErrorMessage) u32 { + var hasher = std.hash.Wyhash.init(0); + + hasher.update(ctx.eb.nullTerminatedString(key.msg)); + if (key.src_loc != 0) { + const src = ctx.eb.getSourceLocation(key.src_loc); + hasher.update(ctx.eb.nullTerminatedString(src.src_path)); + std.hash.autoHash(&hasher, src.line); + std.hash.autoHash(&hasher, src.column); + std.hash.autoHash(&hasher, src.span_main); + } + + return @truncate(u32, hasher.final()); + } + + pub fn eql( + ctx: ErrorNoteHashContext, + a: ErrorBundle.ErrorMessage, + b: ErrorBundle.ErrorMessage, + b_index: usize, + ) bool { + _ = b_index; + const msg_a = ctx.eb.nullTerminatedString(a.msg); + const msg_b = ctx.eb.nullTerminatedString(b.msg); + if (!std.mem.eql(u8, msg_a, msg_b)) return false; + + if (a.src_loc == 0 and b.src_loc == 0) return true; + if (a.src_loc == 0 or b.src_loc == 0) return false; + const src_a = ctx.eb.getSourceLocation(a.src_loc); + const src_b = ctx.eb.getSourceLocation(b.src_loc); + + const src_path_a = ctx.eb.nullTerminatedString(src_a.src_path); + const src_path_b = ctx.eb.nullTerminatedString(src_b.src_path); + + return std.mem.eql(u8, src_path_a, src_path_b) and + src_a.line == src_b.line and + src_a.column == src_b.column and + src_a.span_main == src_b.span_main; + } +}; + +pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Module.ErrorMsg) !void { + const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| { + const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa); + defer gpa.free(file_path); + try eb.addErrorMessage(gpa, .{ + .msg = try eb.printString(gpa, "unable to load '{s}': {s}", .{ + file_path, @errorName(err), + }), + }); + eb.incrementCount(1); + return; }; + const err_span = try module_err_msg.src_loc.span(gpa); + const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main); + const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa); + defer gpa.free(file_path); + + var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .{}; + defer ref_traces.deinit(gpa); + + for (module_err_msg.reference_trace) |module_reference| { + if (module_reference.hidden != 0) { + try ref_traces.append(gpa, .{ + .decl_name = module_reference.hidden, + .src_loc = 0, + }); + break; + } else if (module_reference.decl == null) { + try ref_traces.append(gpa, .{ + .decl_name = 0, + .src_loc = 0, + }); + break; + } + const source = try module_reference.src_loc.file_scope.getSource(gpa); + const span = try module_reference.src_loc.span(gpa); + const loc = std.zig.findLineColumn(source.bytes, span.main); + const rt_file_path = try module_reference.src_loc.file_scope.fullPath(gpa); + defer gpa.free(rt_file_path); + try ref_traces.append(gpa, .{ + .decl_name = try eb.addString(gpa, std.mem.sliceTo(module_reference.decl.?, 0)), + .src_loc = try eb.addSourceLocation(gpa, .{ + .src_path = try eb.addString(gpa, rt_file_path), + .span_start = span.start, + .span_main = span.main, + .span_end = span.end, + .line = @intCast(u32, loc.line), + .column = @intCast(u32, loc.column), + .source_line = 0, + }), + }); + } + + const src_loc = try eb.addSourceLocation(gpa, .{ + .src_path = try eb.addString(gpa, file_path), + .span_start = err_span.start, + .span_main = err_span.main, + .span_end = err_span.end, + .line = @intCast(u32, err_loc.line), + .column = @intCast(u32, err_loc.column), + .source_line = if (module_err_msg.src_loc.lazy == .entire_file) + 0 + else + try eb.addString(gpa, err_loc.source_line), + .reference_trace_len = @intCast(u32, ref_traces.items.len), + }); + + for (ref_traces.items) |rt| { + try eb.addReferenceTrace(gpa, rt); + } + + // De-duplicate error notes. The main use case in mind for this is + // too many "note: called from here" notes when eval branch quota is reached. + var notes: std.ArrayHashMapUnmanaged(ErrorBundle.ErrorMessage, void, ErrorNoteHashContext, true) = .{}; + defer notes.deinit(gpa); + + for (module_err_msg.notes) |module_note| { + const source = try module_note.src_loc.file_scope.getSource(gpa); + const span = try module_note.src_loc.span(gpa); + const loc = std.zig.findLineColumn(source.bytes, span.main); + const note_file_path = try module_note.src_loc.file_scope.fullPath(gpa); + defer gpa.free(note_file_path); + + const gop = try notes.getOrPutContext(gpa, .{ + .msg = try eb.addString(gpa, module_note.msg), + .src_loc = try eb.addSourceLocation(gpa, .{ + .src_path = try eb.addString(gpa, note_file_path), + .span_start = span.start, + .span_main = span.main, + .span_end = span.end, + .line = @intCast(u32, loc.line), + .column = @intCast(u32, loc.column), + .source_line = if (err_loc.eql(loc)) 0 else try eb.addString(gpa, loc.source_line), + }), + }, .{ .eb = eb }); + if (gop.found_existing) { + gop.key_ptr.count += 1; + } + } + + try eb.addErrorMessage(gpa, .{ + .msg = try eb.addString(gpa, module_err_msg.msg), + .src_loc = src_loc, + .notes_len = @intCast(u32, notes.entries.len), + }); + eb.incrementCount(1); + + for (notes.keys()) |note| { + try eb.addErrorMessage(gpa, note); + } +} + +pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File) !void { + assert(file.zir_loaded); + assert(file.tree_loaded); + assert(file.source_loaded); + const payload_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)]; + assert(payload_index != 0); + + const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index); + const items_len = header.data.items_len; + var extra_index = header.end; + for (0..items_len) |_| { + const item = file.zir.extraData(Zir.Inst.CompileErrors.Item, extra_index); + extra_index = item.end; + const err_span = blk: { + if (item.data.node != 0) { + break :blk Module.SrcLoc.nodeToSpan(&file.tree, item.data.node); + } + const token_starts = file.tree.tokens.items(.start); + const start = token_starts[item.data.token] + item.data.byte_offset; + const end = start + @intCast(u32, file.tree.tokenSlice(item.data.token).len) - item.data.byte_offset; + break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start }; + }; + const err_loc = std.zig.findLineColumn(file.source, err_span.main); + + var notes: []ErrorBundle.ErrorMessage = &.{}; + defer gpa.free(notes); + + if (item.data.notes != 0) { + const block = file.zir.extraData(Zir.Inst.Block, item.data.notes); + const body = file.zir.extra[block.end..][0..block.data.body_len]; + notes = try gpa.alloc(ErrorBundle.ErrorMessage, body.len); + for (notes, body) |*note, body_elem| { + const note_item = file.zir.extraData(Zir.Inst.CompileErrors.Item, body_elem); + const msg = file.zir.nullTerminatedString(note_item.data.msg); + const span = blk: { + if (note_item.data.node != 0) { + break :blk Module.SrcLoc.nodeToSpan(&file.tree, note_item.data.node); + } + const token_starts = file.tree.tokens.items(.start); + const start = token_starts[note_item.data.token] + note_item.data.byte_offset; + const end = start + @intCast(u32, file.tree.tokenSlice(note_item.data.token).len) - item.data.byte_offset; + break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start }; + }; + const loc = std.zig.findLineColumn(file.source, span.main); + const src_path = try file.fullPath(gpa); + defer gpa.free(src_path); + + note.* = .{ + .msg = try eb.addString(gpa, msg), + .src_loc = try eb.addSourceLocation(gpa, .{ + .src_path = try eb.addString(gpa, src_path), + .span_start = span.start, + .span_main = span.main, + .span_end = span.end, + .line = @intCast(u32, loc.line), + .column = @intCast(u32, loc.column), + .source_line = if (loc.eql(err_loc)) + 0 + else + try eb.addString(gpa, loc.source_line), + }), + .notes_len = 0, // TODO rework this function to be recursive + }; + } + } + + const msg = file.zir.nullTerminatedString(item.data.msg); + const src_path = try file.fullPath(gpa); + defer gpa.free(src_path); + try eb.addErrorMessage(gpa, .{ + .msg = try eb.addString(gpa, msg), + .src_loc = try eb.addSourceLocation(gpa, .{ + .src_path = try eb.addString(gpa, src_path), + .span_start = err_span.start, + .span_main = err_span.main, + .span_end = err_span.end, + .line = @intCast(u32, err_loc.line), + .column = @intCast(u32, err_loc.column), + .source_line = try eb.addString(gpa, err_loc.source_line), + }), + .notes_len = @intCast(u32, notes.len), + }); + + for (notes) |note| { + try eb.addErrorMessage(gpa, note); + } + } + eb.incrementCount(items_len); } pub fn getCompileLogOutput(self: *Compilation) []const u8 { @@ -5417,34 +5250,29 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca return buffer.toOwnedSliceSentinel(0); } -pub fn updateSubCompilation(sub_compilation: *Compilation) !void { - try sub_compilation.update(); +pub fn updateSubCompilation( + parent_comp: *Compilation, + sub_comp: *Compilation, + misc_task: MiscTask, +) !void { + try sub_comp.update(); - // Look for compilation errors in this sub_compilation - // TODO instead of logging these errors, handle them in the callsites - // of updateSubCompilation and attach them as sub-errors, properly - // surfacing the errors. You can see an example of this already - // done inside buildOutputFromZig. - var errors = try sub_compilation.getAllErrorsAlloc(); - defer errors.deinit(sub_compilation.gpa); + // Look for compilation errors in this sub compilation + const gpa = parent_comp.gpa; + var keep_errors = false; + var errors = try sub_comp.getAllErrorsAlloc(); + defer if (!keep_errors) errors.deinit(gpa); - if (errors.list.len != 0) { - for (errors.list) |full_err_msg| { - switch (full_err_msg) { - .src => |src| { - log.err("{s}:{d}:{d}: {s}", .{ - src.src_path, - src.line + 1, - src.column + 1, - src.msg, - }); - }, - .plain => |plain| { - log.err("{s}", .{plain.msg}); - }, - } - } - return error.BuildingLibCObjectFailed; + if (errors.errorMessageCount() > 0) { + try parent_comp.misc_failures.ensureUnusedCapacity(gpa, 1); + parent_comp.misc_failures.putAssumeCapacityNoClobber(misc_task, .{ + .msg = try std.fmt.allocPrint(gpa, "sub-compilation of {s} failed", .{ + @tagName(misc_task), + }), + .children = errors, + }); + keep_errors = true; + return error.SubCompilationFailed; } } @@ -5520,23 +5348,7 @@ fn buildOutputFromZig( }); defer sub_compilation.destroy(); - try sub_compilation.update(); - // Look for compilation errors in this sub_compilation. - var keep_errors = false; - var errors = try sub_compilation.getAllErrorsAlloc(); - defer if (!keep_errors) errors.deinit(sub_compilation.gpa); - - if (errors.list.len != 0) { - try comp.misc_failures.ensureUnusedCapacity(comp.gpa, 1); - comp.misc_failures.putAssumeCapacityNoClobber(misc_task_tag, .{ - .msg = try std.fmt.allocPrint(comp.gpa, "sub-compilation of {s} failed", .{ - @tagName(misc_task_tag), - }), - .children = errors, - }); - keep_errors = true; - return error.SubCompilationFailed; - } + try comp.updateSubCompilation(sub_compilation, misc_task_tag); assert(out.* == null); out.* = Compilation.CRTFile{ @@ -5551,6 +5363,7 @@ pub fn build_crt_file( comp: *Compilation, root_name: []const u8, output_mode: std.builtin.OutputMode, + misc_task_tag: MiscTask, c_source_files: []const Compilation.CSourceFile, ) !void { const tracy_trace = trace(@src()); @@ -5611,7 +5424,7 @@ pub fn build_crt_file( }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, misc_task_tag); try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); diff --git a/src/Module.zig b/src/Module.zig index 8c52176edd..1520a7d1b2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3756,67 +3756,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void { file.source_loaded = true; file.tree = try Ast.parse(gpa, source, .zig); - defer if (!file.tree_loaded) file.tree.deinit(gpa); - - if (file.tree.errors.len != 0) { - const parse_err = file.tree.errors[0]; - - var msg = std.ArrayList(u8).init(gpa); - defer msg.deinit(); - - const token_starts = file.tree.tokens.items(.start); - const token_tags = file.tree.tokens.items(.tag); - - const extra_offset = file.tree.errorOffset(parse_err); - try file.tree.renderError(parse_err, msg.writer()); - const err_msg = try gpa.create(ErrorMsg); - err_msg.* = .{ - .src_loc = .{ - .file_scope = file, - .parent_decl_node = 0, - .lazy = if (extra_offset == 0) .{ - .token_abs = parse_err.token, - } else .{ - .byte_abs = token_starts[parse_err.token] + extra_offset, - }, - }, - .msg = try msg.toOwnedSlice(), - }; - if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) { - const bad_off = @intCast(u32, file.tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len); - const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off; - try mod.errNoteNonLazy(.{ - .file_scope = file, - .parent_decl_node = 0, - .lazy = .{ .byte_abs = byte_abs }, - }, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])}); - } - - for (file.tree.errors[1..]) |note| { - if (!note.is_note) break; - - try file.tree.renderError(note, msg.writer()); - err_msg.notes = try mod.gpa.realloc(err_msg.notes, err_msg.notes.len + 1); - err_msg.notes[err_msg.notes.len - 1] = .{ - .src_loc = .{ - .file_scope = file, - .parent_decl_node = 0, - .lazy = .{ .token_abs = note.token }, - }, - .msg = try msg.toOwnedSlice(), - }; - } - - { - comp.mutex.lock(); - defer comp.mutex.unlock(); - try mod.failed_files.putNoClobber(gpa, file, err_msg); - } - file.status = .parse_failure; - return error.AnalysisFail; - } file.tree_loaded = true; + // Any potential AST errors are converted to ZIR errors here. file.zir = try AstGen.generate(gpa, file.tree); file.zir_loaded = true; file.status = .success_zir; diff --git a/src/Package.zig b/src/Package.zig index 87d52197bd..febcc51788 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -225,7 +225,7 @@ pub fn fetchAndAddDependencies( dependencies_source: *std.ArrayList(u8), build_roots_source: *std.ArrayList(u8), name_prefix: []const u8, - color: main.Color, + error_bundle: *std.zig.ErrorBundle, all_modules: *AllModules, ) !void { const max_bytes = 10 * 1024 * 1024; @@ -250,7 +250,7 @@ pub fn fetchAndAddDependencies( if (ast.errors.len > 0) { const file_path = try directory.join(arena, &.{Manifest.basename}); - try main.printErrsMsgToStdErr(gpa, arena, ast, file_path, color); + try main.putAstErrorsIntoBundle(gpa, ast, file_path, error_bundle); return error.PackageFetchFailed; } @@ -258,23 +258,18 @@ pub fn fetchAndAddDependencies( defer manifest.deinit(gpa); if (manifest.errors.len > 0) { - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; const file_path = try directory.join(arena, &.{Manifest.basename}); for (manifest.errors) |msg| { - Report.renderErrorMessage(ast, file_path, ttyconf, msg, &.{}); + try Report.addErrorMessage(gpa, ast, file_path, error_bundle, 0, msg); } return error.PackageFetchFailed; } const report: Report = .{ + .gpa = gpa, .ast = &ast, .directory = directory, - .color = color, - .arena = arena, + .error_bundle = error_bundle, }; var any_error = false; @@ -307,7 +302,7 @@ pub fn fetchAndAddDependencies( dependencies_source, build_roots_source, sub_prefix, - color, + error_bundle, all_modules, ); @@ -348,10 +343,10 @@ pub fn createFilePkg( } const Report = struct { + gpa: Allocator, ast: *const std.zig.Ast, directory: Compilation.Directory, - color: main.Color, - arena: Allocator, + error_bundle: *std.zig.ErrorBundle, fn fail( report: Report, @@ -359,52 +354,48 @@ const Report = struct { comptime fmt_string: []const u8, fmt_args: anytype, ) error{ PackageFetchFailed, OutOfMemory } { - return failWithNotes(report, &.{}, tok, fmt_string, fmt_args); - } + const gpa = report.gpa; - fn failWithNotes( - report: Report, - notes: []const Compilation.AllErrors.Message, - tok: std.zig.Ast.TokenIndex, - comptime fmt_string: []const u8, - fmt_args: anytype, - ) error{ PackageFetchFailed, OutOfMemory } { - const ttyconf: std.debug.TTY.Config = switch (report.color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; - const file_path = try report.directory.join(report.arena, &.{Manifest.basename}); - renderErrorMessage(report.ast.*, file_path, ttyconf, .{ + const file_path = try report.directory.join(gpa, &.{Manifest.basename}); + defer gpa.free(file_path); + + const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args); + defer gpa.free(msg); + + try addErrorMessage(report.gpa, report.ast.*, file_path, report.error_bundle, 0, .{ .tok = tok, .off = 0, - .msg = try std.fmt.allocPrint(report.arena, fmt_string, fmt_args), - }, notes); + .msg = msg, + }); + return error.PackageFetchFailed; } - fn renderErrorMessage( + fn addErrorMessage( + gpa: Allocator, ast: std.zig.Ast, file_path: []const u8, - ttyconf: std.debug.TTY.Config, + eb: *std.zig.ErrorBundle, + notes_len: u32, msg: Manifest.ErrorMessage, - notes: []const Compilation.AllErrors.Message, - ) void { + ) error{OutOfMemory}!void { const token_starts = ast.tokens.items(.start); const start_loc = ast.tokenLocation(0, msg.tok); - Compilation.AllErrors.Message.renderToStdErr(.{ .src = .{ - .msg = msg.msg, - .src_path = file_path, - .line = @intCast(u32, start_loc.line), - .column = @intCast(u32, start_loc.column), - .span = .{ - .start = token_starts[msg.tok], - .end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len), - .main = token_starts[msg.tok] + msg.off, - }, - .source_line = ast.source[start_loc.line_start..start_loc.line_end], - .notes = notes, - } }, ttyconf); + + try eb.addErrorMessage(gpa, .{ + .msg = try eb.addString(gpa, msg.msg), + .src_loc = try eb.addSourceLocation(gpa, .{ + .src_path = try eb.addString(gpa, file_path), + .span_start = token_starts[msg.tok], + .span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len), + .span_main = token_starts[msg.tok] + msg.off, + .line = @intCast(u32, start_loc.line), + .column = @intCast(u32, start_loc.column), + .source_line = try eb.addString(gpa, ast.source[start_loc.line_start..start_loc.line_end]), + }), + .notes_len = notes_len, + }); + eb.incrementCount(1); } }; @@ -504,9 +495,7 @@ fn fetchAndUnpack( // by default, so the same logic applies for buffering the reader as for gzip. try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz); } else { - return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{ - uri.path, - }); + return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{uri.path}); } // TODO: delete files not included in the package prior to computing the package hash. @@ -533,10 +522,19 @@ fn fetchAndUnpack( }); } } else { - const notes: [1]Compilation.AllErrors.Message = .{.{ .plain = .{ - .msg = try std.fmt.allocPrint(report.arena, "expected .hash = \"{s}\",", .{&actual_hex}), - } }}; - return report.failWithNotes(¬es, dep.url_tok, "url field is missing corresponding hash field", .{}); + const file_path = try report.directory.join(gpa, &.{Manifest.basename}); + defer gpa.free(file_path); + + const eb = report.error_bundle; + try Report.addErrorMessage(gpa, report.ast.*, file_path, eb, 1, .{ + .tok = dep.url_tok, + .off = 0, + .msg = "url field is missing corresponding hash field", + }); + try eb.addErrorMessage(gpa, .{ + .msg = try eb.printString(gpa, "expected .hash = \"{s}\",", .{&actual_hex}), + }); + return error.PackageFetchFailed; } const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path}); diff --git a/src/Sema.zig b/src/Sema.zig index 8b6c269246..237936547e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2211,29 +2211,26 @@ pub fn fail( fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { @setCold(true); + const gpa = sema.gpa; if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) { if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation; - var arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer arena.deinit(); - var errors = std.ArrayList(Compilation.AllErrors.Message).init(sema.gpa); - defer errors.deinit(); - - Compilation.AllErrors.add(sema.mod, &arena, &errors, err_msg.*) catch unreachable; - + var errors: std.zig.ErrorBundle = undefined; + errors.init(gpa) catch unreachable; + Compilation.addModuleErrorMsg(gpa, &errors, err_msg.*) catch unreachable; std.debug.print("compile error during Sema:\n", .{}); - Compilation.AllErrors.Message.renderToStdErr(errors.items[0], .no_color); + errors.renderToStdErr(.no_color); crash_report.compilerPanic("unexpected compile error occurred", null, null); } const mod = sema.mod; ref: { - errdefer err_msg.destroy(mod.gpa); + errdefer err_msg.destroy(gpa); if (err_msg.src_loc.lazy == .unneeded) { return error.NeededSourceLocation; } - try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1); - try mod.failed_files.ensureUnusedCapacity(mod.gpa, 1); + try mod.failed_decls.ensureUnusedCapacity(gpa, 1); + try mod.failed_files.ensureUnusedCapacity(gpa, 1); const max_references = blk: { if (sema.mod.comp.reference_trace) |num| break :blk num; @@ -2243,11 +2240,11 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { }; var referenced_by = if (sema.func) |some| some.owner_decl else sema.owner_decl_index; - var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(sema.gpa); + var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(gpa); defer reference_stack.deinit(); // Avoid infinite loops. - var seen = std.AutoHashMap(Module.Decl.Index, void).init(sema.gpa); + var seen = std.AutoHashMap(Module.Decl.Index, void).init(gpa); defer seen.deinit(); var cur_reference_trace: u32 = 0; @@ -2288,7 +2285,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { if (gop.found_existing) { // If there are multiple errors for the same Decl, prefer the first one added. sema.err = null; - err_msg.destroy(mod.gpa); + err_msg.destroy(gpa); } else { sema.err = err_msg; gop.value_ptr.* = err_msg; diff --git a/src/glibc.zig b/src/glibc.zig index 3021e7c7ba..530f35531a 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -196,7 +196,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-DASSEMBLER", "-Wa,--noexecstack", }); - return comp.build_crt_file("crti", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crti", .Obj, .@"glibc crti.o", &[1]Compilation.CSourceFile{ .{ .src_path = try start_asm_path(comp, arena, "crti.S"), .cache_exempt_flags = args.items, @@ -215,7 +215,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-DASSEMBLER", "-Wa,--noexecstack", }); - return comp.build_crt_file("crtn", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crtn", .Obj, .@"glibc crtn.o", &[1]Compilation.CSourceFile{ .{ .src_path = try start_asm_path(comp, arena, "crtn.S"), .cache_exempt_flags = args.items, @@ -265,7 +265,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .cache_exempt_flags = args.items, }; }; - return comp.build_crt_file("Scrt1", .Obj, &[_]Compilation.CSourceFile{ start_o, abi_note_o }); + return comp.build_crt_file("Scrt1", .Obj, .@"glibc Scrt1.o", &[_]Compilation.CSourceFile{ start_o, abi_note_o }); }, .libc_nonshared_a => { const s = path.sep_str; @@ -366,7 +366,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { files_index += 1; } const files = files_buf[0..files_index]; - return comp.build_crt_file("c_nonshared", .Lib, files); + return comp.build_crt_file("c_nonshared", .Lib, .@"glibc libc_nonshared.a", files); }, } } @@ -1105,7 +1105,7 @@ fn buildSharedLib( }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, .@"glibc shared object"); } // Return true if glibc has crti/crtn sources for that architecture. diff --git a/src/libcxx.zig b/src/libcxx.zig index 7ca405cf15..c17352c562 100644 --- a/src/libcxx.zig +++ b/src/libcxx.zig @@ -258,7 +258,7 @@ pub fn buildLibCXX(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, .libcxx); assert(comp.libcxx_static_lib == null); comp.libcxx_static_lib = Compilation.CRTFile{ @@ -418,7 +418,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, .libcxxabi); assert(comp.libcxxabi_static_lib == null); comp.libcxxabi_static_lib = Compilation.CRTFile{ diff --git a/src/libtsan.zig b/src/libtsan.zig index 16e40c16f8..1399b6b76c 100644 --- a/src/libtsan.zig +++ b/src/libtsan.zig @@ -235,7 +235,7 @@ pub fn buildTsan(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, .libtsan); assert(comp.tsan_static_lib == null); comp.tsan_static_lib = Compilation.CRTFile{ diff --git a/src/libunwind.zig b/src/libunwind.zig index a20b5e81f7..667195a369 100644 --- a/src/libunwind.zig +++ b/src/libunwind.zig @@ -130,7 +130,7 @@ pub fn buildStaticLib(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, .libunwind); assert(comp.libunwind_static_lib == null); diff --git a/src/main.zig b/src/main.zig index a485fdb9e2..14b63017e5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -24,6 +24,8 @@ const clang = @import("clang.zig"); const Cache = std.Build.Cache; const target_util = @import("target.zig"); const crash_report = @import("crash_report.zig"); +const Module = @import("Module.zig"); +const AstGen = @import("AstGen.zig"); pub const std_options = struct { pub const wasiCwd = wasi_cwd; @@ -3446,15 +3448,13 @@ fn buildOutputType( var errors = try comp.getAllErrorsAlloc(); defer errors.deinit(comp.gpa); - if (errors.list.len != 0) { + if (errors.errorMessageCount() > 0) { const ttyconf: std.debug.TTY.Config = switch (comp.color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - for (errors.list) |full_err_msg| { - try full_err_msg.renderToWriter(ttyconf, conn.stream.writer(), "error:", .Red, 0); - } + try errors.renderToWriter(ttyconf, conn.stream.writer()); continue; } } else { @@ -3830,15 +3830,13 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void var errors = try comp.getAllErrorsAlloc(); defer errors.deinit(comp.gpa); - if (errors.list.len != 0) { + if (errors.errorMessageCount() > 0) { const ttyconf: std.debug.TTY.Config = switch (comp.color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - for (errors.list) |full_err_msg| { - full_err_msg.renderToStdErr(ttyconf); - } + errors.renderToStdErr(ttyconf); const log_text = comp.getCompileLogOutput(); if (log_text.len != 0) { std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); @@ -4438,9 +4436,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi var all_modules: Package.AllModules = .{}; defer all_modules.deinit(gpa); + var errors: std.zig.ErrorBundle = undefined; + try errors.init(gpa); + defer errors.deinit(gpa); + // Here we borrow main package's table and will replace it with a fresh // one after this process completes. - build_pkg.fetchAndAddDependencies( + const fetch_result = build_pkg.fetchAndAddDependencies( &main_pkg, arena, &thread_pool, @@ -4451,12 +4453,19 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi &dependencies_source, &build_roots_source, "", - color, + &errors, &all_modules, - ) catch |err| switch (err) { - error.PackageFetchFailed => process.exit(1), - else => |e| return e, - }; + ); + if (errors.errorMessageCount() > 0) { + const ttyconf: std.debug.TTY.Config = switch (color) { + .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .on => .escape_codes, + .off => .no_color, + }; + errors.renderToStdErr(ttyconf); + process.exit(1); + } + try fetch_result; try dependencies_source.appendSlice("};\npub const build_root = struct {\n"); try dependencies_source.appendSlice(build_roots_source.items); @@ -4543,7 +4552,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi } fn readSourceFileToEndAlloc( - allocator: mem.Allocator, + allocator: Allocator, input: *const fs.File, size_hint: ?usize, ) ![:0]u8 { @@ -4687,12 +4696,9 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void }; defer tree.deinit(gpa); - try printErrsMsgToStdErr(gpa, arena, tree, "", color); + try printAstErrorsToStderr(gpa, tree, "", color); var has_ast_error = false; if (check_ast_flag) { - const Module = @import("Module.zig"); - const AstGen = @import("AstGen.zig"); - var file: Module.File = .{ .status = .never_loaded, .source_loaded = true, @@ -4715,20 +4721,16 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var arena_instance = std.heap.ArenaAllocator.init(gpa); - defer arena_instance.deinit(); - var errors = std.ArrayList(Compilation.AllErrors.Message).init(gpa); - defer errors.deinit(); - - try Compilation.AllErrors.addZir(arena_instance.allocator(), &errors, &file); + var errors: std.zig.ErrorBundle = undefined; + try errors.init(gpa); + defer errors.deinit(gpa); + try Compilation.addZirErrorMessages(gpa, &errors, &file); const ttyconf: std.debug.TTY.Config = switch (color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - for (errors.items) |full_err_msg| { - full_err_msg.renderToStdErr(ttyconf); - } + errors.renderToStdErr(ttyconf); has_ast_error = true; } } @@ -4875,12 +4877,13 @@ fn fmtPathFile( if (stat.kind == .Directory) return error.IsDir; + const gpa = fmt.gpa; const source_code = try readSourceFileToEndAlloc( - fmt.gpa, + gpa, &source_file, std.math.cast(usize, stat.size) orelse return error.FileTooBig, ); - defer fmt.gpa.free(source_code); + defer gpa.free(source_code); source_file.close(); file_closed = true; @@ -4888,19 +4891,16 @@ fn fmtPathFile( // Add to set after no longer possible to get error.IsDir. if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; - var tree = try Ast.parse(fmt.gpa, source_code, .zig); - defer tree.deinit(fmt.gpa); + var tree = try Ast.parse(gpa, source_code, .zig); + defer tree.deinit(gpa); - try printErrsMsgToStdErr(fmt.gpa, fmt.arena, tree, file_path, fmt.color); + try printAstErrorsToStderr(gpa, tree, file_path, fmt.color); if (tree.errors.len != 0) { fmt.any_error = true; return; } if (fmt.check_ast) { - const Module = @import("Module.zig"); - const AstGen = @import("AstGen.zig"); - var file: Module.File = .{ .status = .never_loaded, .source_loaded = true, @@ -4919,31 +4919,27 @@ fn fmtPathFile( .root_decl = .none, }; - file.pkg = try Package.create(fmt.gpa, null, file.sub_file_path); - defer file.pkg.destroy(fmt.gpa); + file.pkg = try Package.create(gpa, null, file.sub_file_path); + defer file.pkg.destroy(gpa); if (stat.size > max_src_size) return error.FileTooBig; - file.zir = try AstGen.generate(fmt.gpa, file.tree); + file.zir = try AstGen.generate(gpa, file.tree); file.zir_loaded = true; - defer file.zir.deinit(fmt.gpa); + defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var arena_instance = std.heap.ArenaAllocator.init(fmt.gpa); - defer arena_instance.deinit(); - var errors = std.ArrayList(Compilation.AllErrors.Message).init(fmt.gpa); - defer errors.deinit(); - - try Compilation.AllErrors.addZir(arena_instance.allocator(), &errors, &file); + var errors: std.zig.ErrorBundle = undefined; + try errors.init(gpa); + defer errors.deinit(gpa); + try Compilation.addZirErrorMessages(gpa, &errors, &file); const ttyconf: std.debug.TTY.Config = switch (fmt.color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - for (errors.items) |full_err_msg| { - full_err_msg.renderToStdErr(ttyconf); - } + errors.renderToStdErr(ttyconf); fmt.any_error = true; } } @@ -4971,100 +4967,53 @@ fn fmtPathFile( } } -pub fn printErrsMsgToStdErr( - gpa: mem.Allocator, - arena: mem.Allocator, +fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void { + var error_bundle: std.zig.ErrorBundle = undefined; + try error_bundle.init(gpa); + defer error_bundle.deinit(gpa); + + try putAstErrorsIntoBundle(gpa, tree, path, &error_bundle); + + const ttyconf: std.debug.TTY.Config = switch (color) { + .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .on => .escape_codes, + .off => .no_color, + }; + error_bundle.renderToStdErr(ttyconf); +} + +pub fn putAstErrorsIntoBundle( + gpa: Allocator, tree: Ast, path: []const u8, - color: Color, + error_bundle: *std.zig.ErrorBundle, ) !void { - const parse_errors: []const Ast.Error = tree.errors; - var i: usize = 0; - while (i < parse_errors.len) : (i += 1) { - const parse_error = parse_errors[i]; - const lok_token = parse_error.token; - const token_tags = tree.tokens.items(.tag); - const start_loc = tree.tokenLocation(0, lok_token); - const source_line = tree.source[start_loc.line_start..start_loc.line_end]; + var file: Module.File = .{ + .status = .never_loaded, + .source_loaded = true, + .zir_loaded = false, + .sub_file_path = path, + .source = tree.source, + .stat = .{ + .size = 0, + .inode = 0, + .mtime = 0, + }, + .tree = tree, + .tree_loaded = true, + .zir = undefined, + .pkg = undefined, + .root_decl = .none, + }; - var text_buf = std.ArrayList(u8).init(gpa); - defer text_buf.deinit(); - const writer = text_buf.writer(); - try tree.renderError(parse_error, writer); - const text = try arena.dupe(u8, text_buf.items); + file.pkg = try Package.create(gpa, null, path); + defer file.pkg.destroy(gpa); - var notes_buffer: [2]Compilation.AllErrors.Message = undefined; - var notes_len: usize = 0; + file.zir = try AstGen.generate(gpa, file.tree); + file.zir_loaded = true; + defer file.zir.deinit(gpa); - if (token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)] == .invalid) { - const bad_off = @intCast(u32, tree.tokenSlice(parse_error.token + @boolToInt(parse_error.token_is_prev)).len); - const byte_offset = @intCast(u32, start_loc.line_start) + @intCast(u32, start_loc.column) + bad_off; - notes_buffer[notes_len] = .{ - .src = .{ - .src_path = path, - .msg = try std.fmt.allocPrint(arena, "invalid byte: '{'}'", .{ - std.zig.fmtEscapes(tree.source[byte_offset..][0..1]), - }), - .span = .{ .start = byte_offset, .end = byte_offset + 1, .main = byte_offset }, - .line = @intCast(u32, start_loc.line), - .column = @intCast(u32, start_loc.column) + bad_off, - .source_line = source_line, - }, - }; - notes_len += 1; - } - - for (parse_errors[i + 1 ..]) |note| { - if (!note.is_note) break; - - text_buf.items.len = 0; - try tree.renderError(note, writer); - const note_loc = tree.tokenLocation(0, note.token); - const byte_offset = @intCast(u32, note_loc.line_start); - notes_buffer[notes_len] = .{ - .src = .{ - .src_path = path, - .msg = try arena.dupe(u8, text_buf.items), - .span = .{ - .start = byte_offset, - .end = byte_offset + @intCast(u32, tree.tokenSlice(note.token).len), - .main = byte_offset, - }, - .line = @intCast(u32, note_loc.line), - .column = @intCast(u32, note_loc.column), - .source_line = tree.source[note_loc.line_start..note_loc.line_end], - }, - }; - i += 1; - notes_len += 1; - } - - const extra_offset = tree.errorOffset(parse_error); - const byte_offset = @intCast(u32, start_loc.line_start) + extra_offset; - const message: Compilation.AllErrors.Message = .{ - .src = .{ - .src_path = path, - .msg = text, - .span = .{ - .start = byte_offset, - .end = byte_offset + @intCast(u32, tree.tokenSlice(lok_token).len), - .main = byte_offset, - }, - .line = @intCast(u32, start_loc.line), - .column = @intCast(u32, start_loc.column) + extra_offset, - .source_line = source_line, - .notes = notes_buffer[0..notes_len], - }, - }; - - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; - - message.renderToStdErr(ttyconf); - } + try Compilation.addZirErrorMessages(gpa, error_bundle, &file); } pub const info_zen = @@ -5547,8 +5496,6 @@ pub fn cmdAstCheck( arena: Allocator, args: []const []const u8, ) !void { - const Module = @import("Module.zig"); - const AstGen = @import("AstGen.zig"); const Zir = @import("Zir.zig"); var color: Color = .auto; @@ -5638,7 +5585,7 @@ pub fn cmdAstCheck( file.tree_loaded = true; defer file.tree.deinit(gpa); - try printErrsMsgToStdErr(gpa, arena, file.tree, file.sub_file_path, color); + try printAstErrorsToStderr(gpa, file.tree, file.sub_file_path, color); if (file.tree.errors.len != 0) { process.exit(1); } @@ -5648,16 +5595,16 @@ pub fn cmdAstCheck( defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena); - try Compilation.AllErrors.addZir(arena, &errors, &file); + var errors: std.zig.ErrorBundle = undefined; + try errors.init(gpa); + defer errors.deinit(gpa); + try Compilation.addZirErrorMessages(gpa, &errors, &file); const ttyconf: std.debug.TTY.Config = switch (color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - for (errors.items) |full_err_msg| { - full_err_msg.renderToStdErr(ttyconf); - } + errors.renderToStdErr(ttyconf); process.exit(1); } @@ -5715,8 +5662,6 @@ pub fn cmdChangelist( arena: Allocator, args: []const []const u8, ) !void { - const Module = @import("Module.zig"); - const AstGen = @import("AstGen.zig"); const Zir = @import("Zir.zig"); const old_source_file = args[0]; @@ -5764,7 +5709,7 @@ pub fn cmdChangelist( file.tree_loaded = true; defer file.tree.deinit(gpa); - try printErrsMsgToStdErr(gpa, arena, file.tree, old_source_file, .auto); + try printAstErrorsToStderr(gpa, file.tree, old_source_file, .auto); if (file.tree.errors.len != 0) { process.exit(1); } @@ -5774,12 +5719,12 @@ pub fn cmdChangelist( defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena); - try Compilation.AllErrors.addZir(arena, &errors, &file); + var errors: std.zig.ErrorBundle = undefined; + try errors.init(gpa); + defer errors.deinit(gpa); + try Compilation.addZirErrorMessages(gpa, &errors, &file); const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); - for (errors.items) |full_err_msg| { - full_err_msg.renderToStdErr(ttyconf); - } + errors.renderToStdErr(ttyconf); process.exit(1); } @@ -5801,7 +5746,7 @@ pub fn cmdChangelist( var new_tree = try Ast.parse(gpa, new_source, .zig); defer new_tree.deinit(gpa); - try printErrsMsgToStdErr(gpa, arena, new_tree, new_source_file, .auto); + try printAstErrorsToStderr(gpa, new_tree, new_source_file, .auto); if (new_tree.errors.len != 0) { process.exit(1); } @@ -5813,12 +5758,12 @@ pub fn cmdChangelist( file.zir_loaded = true; if (file.zir.hasCompileErrors()) { - var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena); - try Compilation.AllErrors.addZir(arena, &errors, &file); + var errors: std.zig.ErrorBundle = undefined; + try errors.init(gpa); + defer errors.deinit(gpa); + try Compilation.addZirErrorMessages(gpa, &errors, &file); const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); - for (errors.items) |full_err_msg| { - full_err_msg.renderToStdErr(ttyconf); - } + errors.renderToStdErr(ttyconf); process.exit(1); } diff --git a/src/mingw.zig b/src/mingw.zig index 9e9e180945..d5c42c3ccc 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -41,7 +41,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { //"-D_UNICODE", //"-DWPRFLAG=1", }); - return comp.build_crt_file("crt2", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt2", .Obj, .@"mingw-w64 crt2.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "mingw", "crt", "crtexe.c", @@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-U__CRTDLL__", "-D__MSVCRT__", }); - return comp.build_crt_file("dllcrt2", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("dllcrt2", .Obj, .@"mingw-w64 dllcrt2.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "mingw", "crt", "crtdll.c", @@ -100,7 +100,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }; } - return comp.build_crt_file("mingw32", .Lib, &c_source_files); + return comp.build_crt_file("mingw32", .Lib, .@"mingw-w64 mingw32.lib", &c_source_files); }, .msvcrt_os_lib => { @@ -148,7 +148,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { }; } } - return comp.build_crt_file("msvcrt-os", .Lib, c_source_files.items); + return comp.build_crt_file("msvcrt-os", .Lib, .@"mingw-w64 msvcrt-os.lib", c_source_files.items); }, .mingwex_lib => { @@ -211,7 +211,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { } else { @panic("unsupported arch"); } - return comp.build_crt_file("mingwex", .Lib, c_source_files.items); + return comp.build_crt_file("mingwex", .Lib, .@"mingw-w64 mingwex.lib", c_source_files.items); }, .uuid_lib => { @@ -244,7 +244,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = extra_flags, }; } - return comp.build_crt_file("uuid", .Lib, &c_source_files); + return comp.build_crt_file("uuid", .Lib, .@"mingw-w64 uuid.lib", &c_source_files); }, } } diff --git a/src/musl.zig b/src/musl.zig index 18e618df8f..7dd224604f 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -33,7 +33,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { try args.appendSlice(&[_][]const u8{ "-Qunused-arguments", }); - return comp.build_crt_file("crti", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crti", .Obj, .@"musl crti.o", &[1]Compilation.CSourceFile{ .{ .src_path = try start_asm_path(comp, arena, "crti.s"), .extra_flags = args.items, @@ -46,7 +46,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { try args.appendSlice(&[_][]const u8{ "-Qunused-arguments", }); - return comp.build_crt_file("crtn", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crtn", .Obj, .@"musl crtn.o", &[1]Compilation.CSourceFile{ .{ .src_path = try start_asm_path(comp, arena, "crtn.s"), .extra_flags = args.items, @@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-fno-stack-protector", "-DCRT", }); - return comp.build_crt_file("crt1", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt1", .Obj, .@"musl crt1.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "musl", "crt", "crt1.c", @@ -77,7 +77,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-fno-stack-protector", "-DCRT", }); - return comp.build_crt_file("rcrt1", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("rcrt1", .Obj, .@"musl rcrt1.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "musl", "crt", "rcrt1.c", @@ -94,7 +94,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-fno-stack-protector", "-DCRT", }); - return comp.build_crt_file("Scrt1", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("Scrt1", .Obj, .@"musl Scrt1.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "musl", "crt", "Scrt1.c", @@ -187,7 +187,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }; } - return comp.build_crt_file("c", .Lib, c_source_files.items); + return comp.build_crt_file("c", .Lib, .@"musl libc.a", c_source_files.items); }, .libc_so => { const target = comp.getTarget(); @@ -241,7 +241,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { }); defer sub_compilation.destroy(); - try sub_compilation.updateSubCompilation(); + try comp.updateSubCompilation(sub_compilation, .@"musl libc.so"); try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); diff --git a/src/wasi_libc.zig b/src/wasi_libc.zig index fc8c81d5af..c4a4cbc4a5 100644 --- a/src/wasi_libc.zig +++ b/src/wasi_libc.zig @@ -74,7 +74,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var args = std.ArrayList([]const u8).init(arena); try addCCArgs(comp, arena, &args, false); try addLibcBottomHalfIncludes(comp, arena, &args); - return comp.build_crt_file("crt1-reactor", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt1-reactor", .Obj, .@"wasi crt1-reactor.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", try sanitize(arena, crt1_reactor_src_file), @@ -87,7 +87,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var args = std.ArrayList([]const u8).init(arena); try addCCArgs(comp, arena, &args, false); try addLibcBottomHalfIncludes(comp, arena, &args); - return comp.build_crt_file("crt1-command", .Obj, &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt1-command", .Obj, .@"wasi crt1-command.o", &[1]Compilation.CSourceFile{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", try sanitize(arena, crt1_command_src_file), @@ -145,7 +145,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { } } - try comp.build_crt_file("c", .Lib, libc_sources.items); + try comp.build_crt_file("c", .Lib, .@"wasi libc.a", libc_sources.items); }, .libwasi_emulated_process_clocks_a => { var args = std.ArrayList([]const u8).init(arena); @@ -161,7 +161,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }); } - try comp.build_crt_file("wasi-emulated-process-clocks", .Lib, emu_clocks_sources.items); + try comp.build_crt_file("wasi-emulated-process-clocks", .Lib, .@"libwasi-emulated-process-clocks.a", emu_clocks_sources.items); }, .libwasi_emulated_getpid_a => { var args = std.ArrayList([]const u8).init(arena); @@ -177,7 +177,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }); } - try comp.build_crt_file("wasi-emulated-getpid", .Lib, emu_getpid_sources.items); + try comp.build_crt_file("wasi-emulated-getpid", .Lib, .@"libwasi-emulated-getpid.a", emu_getpid_sources.items); }, .libwasi_emulated_mman_a => { var args = std.ArrayList([]const u8).init(arena); @@ -193,7 +193,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }); } - try comp.build_crt_file("wasi-emulated-mman", .Lib, emu_mman_sources.items); + try comp.build_crt_file("wasi-emulated-mman", .Lib, .@"libwasi-emulated-mman.a", emu_mman_sources.items); }, .libwasi_emulated_signal_a => { var emu_signal_sources = std.ArrayList(Compilation.CSourceFile).init(arena); @@ -228,7 +228,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { } } - try comp.build_crt_file("wasi-emulated-signal", .Lib, emu_signal_sources.items); + try comp.build_crt_file("wasi-emulated-signal", .Lib, .@"libwasi-emulated-signal.a", emu_signal_sources.items); }, } } From 6f717b18f05ba02439603e0987e9c9551fbadedb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Feb 2023 19:21:44 -0700 Subject: [PATCH 156/294] std.zig.ErrorBundle: rework binary encoding * Separate into a "WIP" struct and a "finished" struct. * Use a bit of indirection for error notes to simplify ergonomics of this data structure. --- lib/std/zig/ErrorBundle.zig | 410 ++++++++++++++++++++---------------- src/Compilation.zig | 257 +++++++++++----------- src/Package.zig | 36 ++-- src/Sema.zig | 9 +- src/main.zig | 86 ++++---- src/test.zig | 21 +- 6 files changed, 427 insertions(+), 392 deletions(-) diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index dee19818a8..1d2eca9703 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -3,24 +3,22 @@ //! is used to collect all the errors from the various places into one //! convenient place for API users to consume. -string_bytes: std.ArrayListUnmanaged(u8), -/// The first thing in this array is a ErrorMessageListIndex. -extra: std.ArrayListUnmanaged(u32), +string_bytes: []const u8, +/// The first thing in this array is an `ErrorMessageList`. +extra: []const u32, // An index into `extra` pointing at an `ErrorMessage`. pub const MessageIndex = enum(u32) { _, }; -/// After the header is: -/// * string_bytes -/// * extra (little endian) -pub const Header = struct { - string_bytes_len: u32, - extra_len: u32, +// An index into `extra` pointing at an `SourceLocation`. +pub const SourceLocationIndex = enum(u32) { + none = 0, + _, }; -/// Trailing: ErrorMessage for each len +/// There will be a MessageIndex for each len at start. pub const ErrorMessageList = struct { len: u32, start: u32, @@ -46,14 +44,13 @@ pub const SourceLocation = struct { }; /// Trailing: -/// * ErrorMessage for each notes_len. +/// * MessageIndex for each notes_len. pub const ErrorMessage = struct { /// null terminated string index msg: u32, /// Usually one, but incremented for redundant messages. count: u32 = 1, - /// 0 or the index into extra of a SourceLocation - src_loc: u32 = 0, + src_loc: SourceLocationIndex = .none, notes_len: u32 = 0, }; @@ -65,170 +62,41 @@ pub const ReferenceTrace = struct { decl_name: u32, /// Index into extra of a SourceLocation /// If this is 0, this is the sentinel ReferenceTrace element. - src_loc: u32, + src_loc: SourceLocationIndex, }; -pub fn init(eb: *ErrorBundle, gpa: Allocator) !void { - eb.* = .{ - .string_bytes = .{}, - .extra = .{}, - }; - - // So that 0 can be used to indicate a null string. - try eb.string_bytes.append(gpa, 0); - - _ = try addExtra(eb, gpa, ErrorMessageList{ - .len = 0, - .start = 0, - }); -} - pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void { - eb.string_bytes.deinit(gpa); - eb.extra.deinit(gpa); + gpa.free(eb.string_bytes); + gpa.free(eb.extra); eb.* = undefined; } -pub fn addString(eb: *ErrorBundle, gpa: Allocator, s: []const u8) !u32 { - const index = @intCast(u32, eb.string_bytes.items.len); - try eb.string_bytes.ensureUnusedCapacity(gpa, s.len + 1); - eb.string_bytes.appendSliceAssumeCapacity(s); - eb.string_bytes.appendAssumeCapacity(0); - return index; -} - -pub fn printString(eb: *ErrorBundle, gpa: Allocator, comptime fmt: []const u8, args: anytype) !u32 { - const index = @intCast(u32, eb.string_bytes.items.len); - try eb.string_bytes.writer(gpa).print(fmt, args); - try eb.string_bytes.append(gpa, 0); - return index; -} - -pub fn addErrorMessage(eb: *ErrorBundle, gpa: Allocator, em: ErrorMessage) !void { - if (eb.errorMessageCount() == 0) { - eb.setStartIndex(@intCast(u32, eb.extra.items.len)); - } - _ = try addExtra(eb, gpa, em); -} - -pub fn addSourceLocation(eb: *ErrorBundle, gpa: Allocator, sl: SourceLocation) !u32 { - return addExtra(eb, gpa, sl); -} - -pub fn addReferenceTrace(eb: *ErrorBundle, gpa: Allocator, rt: ReferenceTrace) !void { - _ = try addExtra(eb, gpa, rt); -} - -pub fn addBundle(eb: *ErrorBundle, gpa: Allocator, other: ErrorBundle) !void { - // Skip over the initial ErrorMessageList len field. - const root_fields_len = @typeInfo(ErrorMessageList).Struct.fields.len; - const other_list = other.extraData(ErrorMessageList, 0).data; - const other_extra = other.extra.items[root_fields_len..]; - - try eb.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.items.len); - try eb.extra.ensureUnusedCapacity(gpa, other_extra.len); - - const new_string_base = @intCast(u32, eb.string_bytes.items.len); - const new_data_base = @intCast(u32, eb.extra.items.len - root_fields_len); - - eb.string_bytes.appendSliceAssumeCapacity(other.string_bytes.items); - eb.extra.appendSliceAssumeCapacity(other_extra); - - // Now we must offset the string indexes and extra indexes of the newly - // added extra. - var index = new_data_base + other_list.start; - for (0..other_list.len) |_| { - index = try patchMessage(eb, index, new_string_base, new_data_base); - } -} - -fn patchMessage(eb: *ErrorBundle, msg_idx: usize, new_string_base: u32, new_data_base: u32) !u32 { - var msg = eb.extraData(ErrorMessage, msg_idx); - if (msg.data.msg != 0) msg.data.msg += new_string_base; - if (msg.data.src_loc != 0) msg.data.src_loc += new_data_base; - eb.setExtra(msg_idx, msg.data); - - try patchSrcLoc(eb, msg.data.src_loc, new_string_base, new_data_base); - - var index = @intCast(u32, msg.end); - for (0..msg.data.notes_len) |_| { - index = try patchMessage(eb, index, new_string_base, new_data_base); - } - return index; -} - -fn patchSrcLoc(eb: *ErrorBundle, idx: usize, new_string_base: u32, new_data_base: u32) !void { - if (idx == 0) return; - - var src_loc = eb.extraData(SourceLocation, idx); - if (src_loc.data.src_path != 0) src_loc.data.src_path += new_string_base; - if (src_loc.data.source_line != 0) src_loc.data.source_line += new_string_base; - eb.setExtra(idx, src_loc.data); - - var index = src_loc.end; - for (0..src_loc.data.reference_trace_len) |_| { - var ref_trace = eb.extraData(ReferenceTrace, index); - if (ref_trace.data.decl_name != 0) ref_trace.data.decl_name += new_string_base; - if (ref_trace.data.src_loc != 0) ref_trace.data.src_loc += new_data_base; - eb.setExtra(index, ref_trace.data); - try patchSrcLoc(eb, ref_trace.data.src_loc, new_string_base, new_data_base); - index = ref_trace.end; - } -} - -fn addExtra(eb: *ErrorBundle, gpa: Allocator, extra: anytype) Allocator.Error!u32 { - const fields = @typeInfo(@TypeOf(extra)).Struct.fields; - try eb.extra.ensureUnusedCapacity(gpa, fields.len); - return addExtraAssumeCapacity(eb, extra); -} - -fn addExtraAssumeCapacity(eb: *ErrorBundle, extra: anytype) u32 { - const fields = @typeInfo(@TypeOf(extra)).Struct.fields; - const result = @intCast(u32, eb.extra.items.len); - eb.extra.items.len += fields.len; - setExtra(eb, result, extra); - return result; -} - -fn setExtra(eb: *ErrorBundle, index: usize, extra: anytype) void { - const fields = @typeInfo(@TypeOf(extra)).Struct.fields; - var i = index; - inline for (fields) |field| { - eb.extra.items[i] = switch (field.type) { - u32 => @field(extra, field.name), - else => @compileError("bad field type"), - }; - i += 1; - } -} - pub fn errorMessageCount(eb: ErrorBundle) u32 { - return eb.extra.items[0]; + return eb.getErrorMessageList().len; } -pub fn setErrorMessageCount(eb: *ErrorBundle, count: u32) void { - eb.extra.items[0] = count; +pub fn getErrorMessageList(eb: ErrorBundle) ErrorMessageList { + return eb.extraData(ErrorMessageList, 0).data; } -pub fn incrementCount(eb: *ErrorBundle, delta: u32) void { - eb.extra.items[0] += delta; -} - -pub fn getStartIndex(eb: ErrorBundle) u32 { - return eb.extra.items[1]; -} - -pub fn setStartIndex(eb: *ErrorBundle, index: u32) void { - eb.extra.items[1] = index; +pub fn getMessages(eb: ErrorBundle) []const MessageIndex { + const list = eb.getErrorMessageList(); + return @ptrCast([]const MessageIndex, eb.extra[list.start..][0..list.len]); } pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage { return eb.extraData(ErrorMessage, @enumToInt(index)).data; } -pub fn getSourceLocation(eb: ErrorBundle, index: u32) SourceLocation { - assert(index != 0); - return eb.extraData(SourceLocation, index).data; +pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation { + assert(index != .none); + return eb.extraData(SourceLocation, @enumToInt(index)).data; +} + +pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex { + const notes_len = eb.getErrorMessage(index).notes_len; + const start = @enumToInt(index) + @typeInfo(ErrorMessage).Struct.fields.len; + return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]); } /// Returns the requested data, as well as the new index which is at the start of the @@ -239,7 +107,9 @@ fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, var result: T = undefined; inline for (fields) |field| { @field(result, field.name) = switch (field.type) { - u32 => eb.extra.items[i], + u32 => eb.extra[i], + MessageIndex => @intToEnum(MessageIndex, eb.extra[i]), + SourceLocationIndex => @intToEnum(SourceLocationIndex, eb.extra[i]), else => @compileError("bad field type"), }; i += 1; @@ -252,7 +122,7 @@ fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, /// Given an index into `string_bytes` returns the null-terminated string found there. pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 { - const string_bytes = eb.string_bytes.items; + const string_bytes = eb.string_bytes; var end: usize = index; while (string_bytes[end] != 0) { end += 1; @@ -272,28 +142,25 @@ pub fn renderToWriter( ttyconf: std.debug.TTY.Config, writer: anytype, ) anyerror!void { - const list = eb.extraData(ErrorMessageList, 0).data; - var index: usize = list.start; - for (0..list.len) |_| { - const err_msg = eb.extraData(ErrorMessage, index); - index = try renderErrorMessageToWriter(eb, err_msg.data, err_msg.end, ttyconf, writer, "error", .Red, 0); + for (eb.getMessages()) |err_msg| { + try renderErrorMessageToWriter(eb, err_msg, ttyconf, writer, "error", .Red, 0); } } fn renderErrorMessageToWriter( eb: ErrorBundle, - err_msg: ErrorMessage, - end_index: usize, + err_msg_index: MessageIndex, ttyconf: std.debug.TTY.Config, stderr: anytype, kind: []const u8, color: std.debug.TTY.Color, indent: usize, -) anyerror!usize { +) anyerror!void { var counting_writer = std.io.countingWriter(stderr); const counting_stderr = counting_writer.writer(); - if (err_msg.src_loc != 0) { - const src = eb.extraData(SourceLocation, err_msg.src_loc); + const err_msg = eb.getErrorMessage(err_msg_index); + if (err_msg.src_loc != .none) { + const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc)); try counting_stderr.writeByteNTimes(' ', indent); try ttyconf.setColor(stderr, .Bold); try counting_stderr.print("{s}:{d}:{d}: ", .{ @@ -337,10 +204,8 @@ fn renderErrorMessageToWriter( try stderr.writeByte('\n'); try ttyconf.setColor(stderr, .Reset); } - var index = end_index; - for (0..err_msg.notes_len) |_| { - const note = eb.extraData(ErrorMessage, index); - index = try renderErrorMessageToWriter(eb, note.data, note.end, ttyconf, stderr, "note", .Cyan, indent); + for (eb.getNotes(err_msg_index)) |note| { + try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent); } if (src.data.reference_trace_len > 0) { try ttyconf.setColor(stderr, .Reset); @@ -350,7 +215,7 @@ fn renderErrorMessageToWriter( for (0..src.data.reference_trace_len) |_| { const ref_trace = eb.extraData(ReferenceTrace, ref_index); ref_index = ref_trace.end; - if (ref_trace.data.src_loc != 0) { + if (ref_trace.data.src_loc != .none) { const ref_src = eb.getSourceLocation(ref_trace.data.src_loc); try stderr.print(" {s}: {s}:{d}:{d}\n", .{ eb.nullTerminatedString(ref_trace.data.decl_name), @@ -374,7 +239,6 @@ fn renderErrorMessageToWriter( try stderr.writeByte('\n'); try ttyconf.setColor(stderr, .Reset); } - return index; } else { try ttyconf.setColor(stderr, color); try stderr.writeByteNTimes(' ', indent); @@ -390,12 +254,9 @@ fn renderErrorMessageToWriter( try stderr.print(" ({d} times)\n", .{err_msg.count}); } try ttyconf.setColor(stderr, .Reset); - var index = end_index; - for (0..err_msg.notes_len) |_| { - const note = eb.extraData(ErrorMessage, index); - index = try renderErrorMessageToWriter(eb, note.data, note.end, ttyconf, stderr, "note", .Cyan, indent + 4); + for (eb.getNotes(err_msg_index)) |note| { + try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent + 4); } - return index; } } @@ -417,3 +278,186 @@ const std = @import("std"); const ErrorBundle = @This(); const Allocator = std.mem.Allocator; const assert = std.debug.assert; + +pub const Wip = struct { + gpa: Allocator, + string_bytes: std.ArrayListUnmanaged(u8), + /// The first thing in this array is a ErrorMessageList. + extra: std.ArrayListUnmanaged(u32), + root_list: std.ArrayListUnmanaged(MessageIndex), + + pub fn init(wip: *Wip, gpa: Allocator) !void { + wip.* = .{ + .gpa = gpa, + .string_bytes = .{}, + .extra = .{}, + .root_list = .{}, + }; + + // So that 0 can be used to indicate a null string. + try wip.string_bytes.append(gpa, 0); + + assert(0 == try addExtra(wip, ErrorMessageList{ + .len = 0, + .start = 0, + })); + } + + pub fn deinit(wip: *Wip) void { + const gpa = wip.gpa; + wip.root_list.deinit(gpa); + wip.string_bytes.deinit(gpa); + wip.extra.deinit(gpa); + wip.* = undefined; + } + + pub fn toOwnedBundle(wip: *Wip) !ErrorBundle { + const gpa = wip.gpa; + wip.setExtra(0, ErrorMessageList{ + .len = @intCast(u32, wip.root_list.items.len), + .start = @intCast(u32, wip.extra.items.len), + }); + try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items)); + wip.root_list.clearAndFree(gpa); + return .{ + .string_bytes = try wip.string_bytes.toOwnedSlice(gpa), + .extra = try wip.extra.toOwnedSlice(gpa), + }; + } + + pub fn tmpBundle(wip: Wip) ErrorBundle { + return .{ + .string_bytes = wip.string_bytes.items, + .extra = wip.extra.items, + }; + } + + pub fn addString(wip: *Wip, s: []const u8) !u32 { + const gpa = wip.gpa; + const index = @intCast(u32, wip.string_bytes.items.len); + try wip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1); + wip.string_bytes.appendSliceAssumeCapacity(s); + wip.string_bytes.appendAssumeCapacity(0); + return index; + } + + pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) !u32 { + const gpa = wip.gpa; + const index = @intCast(u32, wip.string_bytes.items.len); + try wip.string_bytes.writer(gpa).print(fmt, args); + try wip.string_bytes.append(gpa, 0); + return index; + } + + pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) !void { + try wip.root_list.ensureUnusedCapacity(wip.gpa, 1); + wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em)); + } + + pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) !MessageIndex { + return @intToEnum(MessageIndex, try addExtra(wip, em)); + } + + pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex { + return @intToEnum(MessageIndex, addExtraAssumeCapacity(wip, em)); + } + + pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) !SourceLocationIndex { + return @intToEnum(SourceLocationIndex, try addExtra(wip, sl)); + } + + pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) !void { + _ = try addExtra(wip, rt); + } + + pub fn addBundle(wip: *Wip, other: ErrorBundle) !void { + const gpa = wip.gpa; + + try wip.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.len); + try wip.extra.ensureUnusedCapacity(gpa, other.extra.len); + + const other_list = other.getMessages(); + + // The ensureUnusedCapacity call above guarantees this. + const notes_start = wip.reserveNotes(@intCast(u32, other_list.len)) catch unreachable; + for (notes_start.., other_list) |note, message| { + wip.extra.items[note] = @enumToInt(wip.addOtherMessage(other, message) catch unreachable); + } + } + + pub fn reserveNotes(wip: *Wip, notes_len: u32) !u32 { + try wip.extra.ensureUnusedCapacity(wip.gpa, notes_len + + notes_len * @typeInfo(ErrorBundle.ErrorMessage).Struct.fields.len); + wip.extra.items.len += notes_len; + return @intCast(u32, wip.extra.items.len - notes_len); + } + + fn addOtherMessage(wip: *Wip, other: ErrorBundle, msg_index: MessageIndex) !MessageIndex { + const other_msg = other.getErrorMessage(msg_index); + const src_loc = try wip.addOtherSourceLocation(other, other_msg.src_loc); + const msg = try wip.addErrorMessage(.{ + .msg = try wip.addString(other.nullTerminatedString(other_msg.msg)), + .count = other_msg.count, + .src_loc = src_loc, + .notes_len = other_msg.notes_len, + }); + const notes_start = try wip.reserveNotes(other_msg.notes_len); + for (notes_start.., other.getNotes(msg_index)) |note, other_note| { + wip.extra.items[note] = @enumToInt(try wip.addOtherMessage(other, other_note)); + } + return msg; + } + + fn addOtherSourceLocation( + wip: *Wip, + other: ErrorBundle, + index: SourceLocationIndex, + ) !SourceLocationIndex { + if (index == .none) return .none; + const other_sl = other.getSourceLocation(index); + + const src_loc = try wip.addSourceLocation(.{ + .src_path = try wip.addString(other.nullTerminatedString(other_sl.src_path)), + .line = other_sl.line, + .column = other_sl.column, + .span_start = other_sl.span_start, + .span_main = other_sl.span_main, + .span_end = other_sl.span_end, + .source_line = try wip.addString(other.nullTerminatedString(other_sl.source_line)), + .reference_trace_len = other_sl.reference_trace_len, + }); + + // TODO: also add the reference trace + + return src_loc; + } + + fn addExtra(wip: *Wip, extra: anytype) Allocator.Error!u32 { + const gpa = wip.gpa; + const fields = @typeInfo(@TypeOf(extra)).Struct.fields; + try wip.extra.ensureUnusedCapacity(gpa, fields.len); + return addExtraAssumeCapacity(wip, extra); + } + + fn addExtraAssumeCapacity(wip: *Wip, extra: anytype) u32 { + const fields = @typeInfo(@TypeOf(extra)).Struct.fields; + const result = @intCast(u32, wip.extra.items.len); + wip.extra.items.len += fields.len; + setExtra(wip, result, extra); + return result; + } + + fn setExtra(wip: *Wip, index: usize, extra: anytype) void { + const fields = @typeInfo(@TypeOf(extra)).Struct.fields; + var i = index; + inline for (fields) |field| { + wip.extra.items[i] = switch (field.type) { + u32 => @field(extra, field.name), + MessageIndex => @enumToInt(@field(extra, field.name)), + SourceLocationIndex => @enumToInt(@field(extra, field.name)), + else => @compileError("bad field type"), + }; + i += 1; + } + } +}; diff --git a/src/Compilation.zig b/src/Compilation.zig index f5a92c66ea..e19dc59445 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2546,9 +2546,9 @@ pub fn totalErrorCount(self: *Compilation) u32 { pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { const gpa = self.gpa; - var bundle: ErrorBundle = undefined; + var bundle: ErrorBundle.Wip = undefined; try bundle.init(gpa); - errdefer bundle.deinit(gpa); + defer bundle.deinit(); { var it = self.failed_c_objects.iterator(); @@ -2557,12 +2557,10 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { const err_msg = entry.value_ptr.*; // TODO these fields will need to be adjusted when we have proper // C error reporting bubbling up. - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.printString(gpa, "unable to build C object: {s}", .{ - err_msg.msg, - }), - .src_loc = try bundle.addSourceLocation(gpa, .{ - .src_path = try bundle.addString(gpa, c_object.src.src_path), + try bundle.addRootErrorMessage(.{ + .msg = try bundle.printString("unable to build C object: {s}", .{err_msg.msg}), + .src_loc = try bundle.addSourceLocation(.{ + .src_path = try bundle.addString(c_object.src.src_path), .span_start = 0, .span_main = 0, .span_end = 1, @@ -2571,49 +2569,46 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { .source_line = 0, // TODO }), }); - bundle.incrementCount(1); } } for (self.lld_errors.items) |lld_error| { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, lld_error.msg), - .notes_len = @intCast(u32, lld_error.context_lines.len), - }); - bundle.incrementCount(1); + const notes_len = @intCast(u32, lld_error.context_lines.len); - for (lld_error.context_lines) |context_line| { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, context_line), - }); + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString(lld_error.msg), + .notes_len = notes_len, + }); + const notes_start = try bundle.reserveNotes(notes_len); + for (notes_start.., lld_error.context_lines) |note, context_line| { + bundle.extra.items[note] = @enumToInt(bundle.addErrorMessageAssumeCapacity(.{ + .msg = try bundle.addString(context_line), + })); } } for (self.misc_failures.values()) |*value| { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, value.msg), + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString(value.msg), .notes_len = if (value.children) |b| b.errorMessageCount() else 0, }); - if (value.children) |b| try bundle.addBundle(gpa, b); - bundle.incrementCount(1); + if (value.children) |b| try bundle.addBundle(b); } if (self.alloc_failure_occurred) { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, "memory allocation failure"), + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString("memory allocation failure"), }); - bundle.incrementCount(1); } if (self.bin_file.options.module) |module| { { var it = module.failed_files.iterator(); while (it.next()) |entry| { if (entry.value_ptr.*) |msg| { - try addModuleErrorMsg(gpa, &bundle, msg.*); + try addModuleErrorMsg(&bundle, msg.*); } else { - // Must be ZIR errors. In order for ZIR errors to exist, the parsing - // must have completed successfully. - const tree = try entry.key_ptr.*.getTree(module.gpa); - assert(tree.errors.len == 0); - try addZirErrorMessages(gpa, &bundle, entry.key_ptr.*); + // Must be ZIR errors. Note that this may include AST errors. + // addZirErrorMessages asserts that the tree is loaded. + _ = try entry.key_ptr.*.getTree(gpa); + try addZirErrorMessages(&bundle, entry.key_ptr.*); } } } @@ -2621,7 +2616,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { var it = module.failed_embed_files.iterator(); while (it.next()) |entry| { const msg = entry.value_ptr.*; - try addModuleErrorMsg(gpa, &bundle, msg.*); + try addModuleErrorMsg(&bundle, msg.*); } } { @@ -2631,21 +2626,20 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { // Skip errors for Decls within files that had a parse failure. // We'll try again once parsing succeeds. if (decl.getFileScope().okToReportErrors()) { - try addModuleErrorMsg(gpa, &bundle, entry.value_ptr.*.*); + try addModuleErrorMsg(&bundle, entry.value_ptr.*.*); if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, std.mem.span(c_error.msg)), - .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(gpa, .{ - .src_path = try bundle.addString(gpa, std.mem.span(some)), + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString(std.mem.span(c_error.msg)), + .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(.{ + .src_path = try bundle.addString(std.mem.span(some)), .span_start = c_error.offset, .span_main = c_error.offset, .span_end = c_error.offset + 1, .line = c_error.line, .column = c_error.column, - .source_line = if (c_error.source_line) |line| try bundle.addString(gpa, std.mem.span(line)) else 0, - }) else 0, + .source_line = if (c_error.source_line) |line| try bundle.addString(std.mem.span(line)) else 0, + }) else .none, }); - bundle.incrementCount(1); }; } } @@ -2657,40 +2651,39 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { // Skip errors for Decls within files that had a parse failure. // We'll try again once parsing succeeds. if (decl.getFileScope().okToReportErrors()) { - try addModuleErrorMsg(gpa, &bundle, entry.value_ptr.*.*); + try addModuleErrorMsg(&bundle, entry.value_ptr.*.*); } } } for (module.failed_exports.values()) |value| { - try addModuleErrorMsg(gpa, &bundle, value.*); + try addModuleErrorMsg(&bundle, value.*); } } - if (bundle.errorMessageCount() == 0) { + if (bundle.root_list.items.len == 0) { if (self.link_error_flags.no_entry_point_found) { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, "no entry point found"), + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString("no entry point found"), }); - bundle.incrementCount(1); } } if (self.link_error_flags.missing_libc) { - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, "libc not available"), + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString("libc not available"), .notes_len = 2, }); - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, "run 'zig libc -h' to learn about libc installations"), - }); - try bundle.addErrorMessage(gpa, .{ - .msg = try bundle.addString(gpa, "run 'zig targets' to see the targets for which zig can always provide libc"), - }); - bundle.incrementCount(1); + const notes_start = try bundle.reserveNotes(2); + bundle.extra.items[notes_start + 0] = @enumToInt(try bundle.addErrorMessage(.{ + .msg = try bundle.addString("run 'zig libc -h' to learn about libc installations"), + })); + bundle.extra.items[notes_start + 1] = @enumToInt(try bundle.addErrorMessage(.{ + .msg = try bundle.addString("run 'zig targets' to see the targets for which zig can always provide libc"), + })); } if (self.bin_file.options.module) |module| { - if (bundle.errorMessageCount() == 0 and module.compile_log_decls.count() != 0) { + if (bundle.root_list.items.len == 0 and module.compile_log_decls.count() != 0) { const keys = module.compile_log_decls.keys(); const values = module.compile_log_decls.values(); // First one will be the error; subsequent ones will be notes. @@ -2699,9 +2692,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { const err_msg = Module.ErrorMsg{ .src_loc = src_loc, .msg = "found compile log statement", - .notes = try self.gpa.alloc(Module.ErrorMsg, module.compile_log_decls.count() - 1), + .notes = try gpa.alloc(Module.ErrorMsg, module.compile_log_decls.count() - 1), }; - defer self.gpa.free(err_msg.notes); + defer gpa.free(err_msg.notes); for (keys[1..], 0..) |key, i| { const note_decl = module.declPtr(key); @@ -2711,25 +2704,26 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { }; } - try addModuleErrorMsg(gpa, &bundle, err_msg); + try addModuleErrorMsg(&bundle, err_msg); } } - assert(self.totalErrorCount() == bundle.errorMessageCount()); + assert(self.totalErrorCount() == bundle.root_list.items.len); - return bundle; + return bundle.toOwnedBundle(); } pub const ErrorNoteHashContext = struct { - eb: *const ErrorBundle, + eb: *const ErrorBundle.Wip, pub fn hash(ctx: ErrorNoteHashContext, key: ErrorBundle.ErrorMessage) u32 { var hasher = std.hash.Wyhash.init(0); + const eb = ctx.eb.tmpBundle(); - hasher.update(ctx.eb.nullTerminatedString(key.msg)); - if (key.src_loc != 0) { - const src = ctx.eb.getSourceLocation(key.src_loc); - hasher.update(ctx.eb.nullTerminatedString(src.src_path)); + hasher.update(eb.nullTerminatedString(key.msg)); + if (key.src_loc != .none) { + const src = eb.getSourceLocation(key.src_loc); + hasher.update(eb.nullTerminatedString(src.src_path)); std.hash.autoHash(&hasher, src.line); std.hash.autoHash(&hasher, src.column); std.hash.autoHash(&hasher, src.span_main); @@ -2745,17 +2739,18 @@ pub const ErrorNoteHashContext = struct { b_index: usize, ) bool { _ = b_index; - const msg_a = ctx.eb.nullTerminatedString(a.msg); - const msg_b = ctx.eb.nullTerminatedString(b.msg); + const eb = ctx.eb.tmpBundle(); + const msg_a = eb.nullTerminatedString(a.msg); + const msg_b = eb.nullTerminatedString(b.msg); if (!std.mem.eql(u8, msg_a, msg_b)) return false; - if (a.src_loc == 0 and b.src_loc == 0) return true; - if (a.src_loc == 0 or b.src_loc == 0) return false; - const src_a = ctx.eb.getSourceLocation(a.src_loc); - const src_b = ctx.eb.getSourceLocation(b.src_loc); + if (a.src_loc == .none and b.src_loc == .none) return true; + if (a.src_loc == .none or b.src_loc == .none) return false; + const src_a = eb.getSourceLocation(a.src_loc); + const src_b = eb.getSourceLocation(b.src_loc); - const src_path_a = ctx.eb.nullTerminatedString(src_a.src_path); - const src_path_b = ctx.eb.nullTerminatedString(src_b.src_path); + const src_path_a = eb.nullTerminatedString(src_a.src_path); + const src_path_b = eb.nullTerminatedString(src_b.src_path); return std.mem.eql(u8, src_path_a, src_path_b) and src_a.line == src_b.line and @@ -2764,16 +2759,16 @@ pub const ErrorNoteHashContext = struct { } }; -pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Module.ErrorMsg) !void { +pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void { + const gpa = eb.gpa; const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| { const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa); defer gpa.free(file_path); - try eb.addErrorMessage(gpa, .{ - .msg = try eb.printString(gpa, "unable to load '{s}': {s}", .{ + try eb.addRootErrorMessage(.{ + .msg = try eb.printString("unable to load '{s}': {s}", .{ file_path, @errorName(err), }), }); - eb.incrementCount(1); return; }; const err_span = try module_err_msg.src_loc.span(gpa); @@ -2788,13 +2783,13 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul if (module_reference.hidden != 0) { try ref_traces.append(gpa, .{ .decl_name = module_reference.hidden, - .src_loc = 0, + .src_loc = .none, }); break; } else if (module_reference.decl == null) { try ref_traces.append(gpa, .{ .decl_name = 0, - .src_loc = 0, + .src_loc = .none, }); break; } @@ -2804,9 +2799,9 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul const rt_file_path = try module_reference.src_loc.file_scope.fullPath(gpa); defer gpa.free(rt_file_path); try ref_traces.append(gpa, .{ - .decl_name = try eb.addString(gpa, std.mem.sliceTo(module_reference.decl.?, 0)), - .src_loc = try eb.addSourceLocation(gpa, .{ - .src_path = try eb.addString(gpa, rt_file_path), + .decl_name = try eb.addString(std.mem.sliceTo(module_reference.decl.?, 0)), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(rt_file_path), .span_start = span.start, .span_main = span.main, .span_end = span.end, @@ -2817,8 +2812,8 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul }); } - const src_loc = try eb.addSourceLocation(gpa, .{ - .src_path = try eb.addString(gpa, file_path), + const src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(file_path), .span_start = err_span.start, .span_main = err_span.main, .span_end = err_span.end, @@ -2827,12 +2822,12 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul .source_line = if (module_err_msg.src_loc.lazy == .entire_file) 0 else - try eb.addString(gpa, err_loc.source_line), + try eb.addString(err_loc.source_line), .reference_trace_len = @intCast(u32, ref_traces.items.len), }); for (ref_traces.items) |rt| { - try eb.addReferenceTrace(gpa, rt); + try eb.addReferenceTrace(rt); } // De-duplicate error notes. The main use case in mind for this is @@ -2848,15 +2843,15 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul defer gpa.free(note_file_path); const gop = try notes.getOrPutContext(gpa, .{ - .msg = try eb.addString(gpa, module_note.msg), - .src_loc = try eb.addSourceLocation(gpa, .{ - .src_path = try eb.addString(gpa, note_file_path), + .msg = try eb.addString(module_note.msg), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(note_file_path), .span_start = span.start, .span_main = span.main, .span_end = span.end, .line = @intCast(u32, loc.line), .column = @intCast(u32, loc.column), - .source_line = if (err_loc.eql(loc)) 0 else try eb.addString(gpa, loc.source_line), + .source_line = if (err_loc.eql(loc)) 0 else try eb.addString(loc.source_line), }), }, .{ .eb = eb }); if (gop.found_existing) { @@ -2864,24 +2859,28 @@ pub fn addModuleErrorMsg(gpa: Allocator, eb: *ErrorBundle, module_err_msg: Modul } } - try eb.addErrorMessage(gpa, .{ - .msg = try eb.addString(gpa, module_err_msg.msg), - .src_loc = src_loc, - .notes_len = @intCast(u32, notes.entries.len), - }); - eb.incrementCount(1); + const notes_len = @intCast(u32, notes.entries.len); - for (notes.keys()) |note| { - try eb.addErrorMessage(gpa, note); + try eb.addRootErrorMessage(.{ + .msg = try eb.addString(module_err_msg.msg), + .src_loc = src_loc, + .notes_len = notes_len, + }); + + const notes_start = try eb.reserveNotes(notes_len); + + for (notes_start.., notes.keys()) |i, note| { + eb.extra.items[i] = @enumToInt(try eb.addErrorMessage(note)); } } -pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File) !void { +pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void { assert(file.zir_loaded); assert(file.tree_loaded); assert(file.source_loaded); const payload_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)]; assert(payload_index != 0); + const gpa = eb.gpa; const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index); const items_len = header.data.items_len; @@ -2900,14 +2899,30 @@ pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File) }; const err_loc = std.zig.findLineColumn(file.source, err_span.main); - var notes: []ErrorBundle.ErrorMessage = &.{}; - defer gpa.free(notes); + { + const msg = file.zir.nullTerminatedString(item.data.msg); + const src_path = try file.fullPath(gpa); + defer gpa.free(src_path); + try eb.addRootErrorMessage(.{ + .msg = try eb.addString(msg), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(src_path), + .span_start = err_span.start, + .span_main = err_span.main, + .span_end = err_span.end, + .line = @intCast(u32, err_loc.line), + .column = @intCast(u32, err_loc.column), + .source_line = try eb.addString(err_loc.source_line), + }), + .notes_len = item.data.notes, + }); + } if (item.data.notes != 0) { + const notes_start = try eb.reserveNotes(item.data.notes); const block = file.zir.extraData(Zir.Inst.Block, item.data.notes); const body = file.zir.extra[block.end..][0..block.data.body_len]; - notes = try gpa.alloc(ErrorBundle.ErrorMessage, body.len); - for (notes, body) |*note, body_elem| { + for (notes_start.., body) |note_i, body_elem| { const note_item = file.zir.extraData(Zir.Inst.CompileErrors.Item, body_elem); const msg = file.zir.nullTerminatedString(note_item.data.msg); const span = blk: { @@ -2923,10 +2938,10 @@ pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File) const src_path = try file.fullPath(gpa); defer gpa.free(src_path); - note.* = .{ - .msg = try eb.addString(gpa, msg), - .src_loc = try eb.addSourceLocation(gpa, .{ - .src_path = try eb.addString(gpa, src_path), + eb.extra.items[note_i] = @enumToInt(try eb.addErrorMessage(.{ + .msg = try eb.addString(msg), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(src_path), .span_start = span.start, .span_main = span.main, .span_end = span.end, @@ -2935,35 +2950,13 @@ pub fn addZirErrorMessages(gpa: Allocator, eb: *ErrorBundle, file: *Module.File) .source_line = if (loc.eql(err_loc)) 0 else - try eb.addString(gpa, loc.source_line), + try eb.addString(loc.source_line), }), .notes_len = 0, // TODO rework this function to be recursive - }; + })); } } - - const msg = file.zir.nullTerminatedString(item.data.msg); - const src_path = try file.fullPath(gpa); - defer gpa.free(src_path); - try eb.addErrorMessage(gpa, .{ - .msg = try eb.addString(gpa, msg), - .src_loc = try eb.addSourceLocation(gpa, .{ - .src_path = try eb.addString(gpa, src_path), - .span_start = err_span.start, - .span_main = err_span.main, - .span_end = err_span.end, - .line = @intCast(u32, err_loc.line), - .column = @intCast(u32, err_loc.column), - .source_line = try eb.addString(gpa, err_loc.source_line), - }), - .notes_len = @intCast(u32, notes.len), - }); - - for (notes) |note| { - try eb.addErrorMessage(gpa, note); - } } - eb.incrementCount(items_len); } pub fn getCompileLogOutput(self: *Compilation) []const u8 { diff --git a/src/Package.zig b/src/Package.zig index febcc51788..d26daf5a0c 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -225,7 +225,7 @@ pub fn fetchAndAddDependencies( dependencies_source: *std.ArrayList(u8), build_roots_source: *std.ArrayList(u8), name_prefix: []const u8, - error_bundle: *std.zig.ErrorBundle, + error_bundle: *std.zig.ErrorBundle.Wip, all_modules: *AllModules, ) !void { const max_bytes = 10 * 1024 * 1024; @@ -260,13 +260,12 @@ pub fn fetchAndAddDependencies( if (manifest.errors.len > 0) { const file_path = try directory.join(arena, &.{Manifest.basename}); for (manifest.errors) |msg| { - try Report.addErrorMessage(gpa, ast, file_path, error_bundle, 0, msg); + try Report.addErrorMessage(ast, file_path, error_bundle, 0, msg); } return error.PackageFetchFailed; } const report: Report = .{ - .gpa = gpa, .ast = &ast, .directory = directory, .error_bundle = error_bundle, @@ -343,10 +342,9 @@ pub fn createFilePkg( } const Report = struct { - gpa: Allocator, ast: *const std.zig.Ast, directory: Compilation.Directory, - error_bundle: *std.zig.ErrorBundle, + error_bundle: *std.zig.ErrorBundle.Wip, fn fail( report: Report, @@ -354,7 +352,7 @@ const Report = struct { comptime fmt_string: []const u8, fmt_args: anytype, ) error{ PackageFetchFailed, OutOfMemory } { - const gpa = report.gpa; + const gpa = report.error_bundle.gpa; const file_path = try report.directory.join(gpa, &.{Manifest.basename}); defer gpa.free(file_path); @@ -362,7 +360,7 @@ const Report = struct { const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args); defer gpa.free(msg); - try addErrorMessage(report.gpa, report.ast.*, file_path, report.error_bundle, 0, .{ + try addErrorMessage(report.ast.*, file_path, report.error_bundle, 0, .{ .tok = tok, .off = 0, .msg = msg, @@ -372,30 +370,28 @@ const Report = struct { } fn addErrorMessage( - gpa: Allocator, ast: std.zig.Ast, file_path: []const u8, - eb: *std.zig.ErrorBundle, + eb: *std.zig.ErrorBundle.Wip, notes_len: u32, msg: Manifest.ErrorMessage, ) error{OutOfMemory}!void { const token_starts = ast.tokens.items(.start); const start_loc = ast.tokenLocation(0, msg.tok); - try eb.addErrorMessage(gpa, .{ - .msg = try eb.addString(gpa, msg.msg), - .src_loc = try eb.addSourceLocation(gpa, .{ - .src_path = try eb.addString(gpa, file_path), + try eb.addRootErrorMessage(.{ + .msg = try eb.addString(msg.msg), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(file_path), .span_start = token_starts[msg.tok], .span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len), .span_main = token_starts[msg.tok] + msg.off, .line = @intCast(u32, start_loc.line), .column = @intCast(u32, start_loc.column), - .source_line = try eb.addString(gpa, ast.source[start_loc.line_start..start_loc.line_end]), + .source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]), }), .notes_len = notes_len, }); - eb.incrementCount(1); } }; @@ -526,14 +522,16 @@ fn fetchAndUnpack( defer gpa.free(file_path); const eb = report.error_bundle; - try Report.addErrorMessage(gpa, report.ast.*, file_path, eb, 1, .{ + const notes_len = 1; + try Report.addErrorMessage(report.ast.*, file_path, eb, notes_len, .{ .tok = dep.url_tok, .off = 0, .msg = "url field is missing corresponding hash field", }); - try eb.addErrorMessage(gpa, .{ - .msg = try eb.printString(gpa, "expected .hash = \"{s}\",", .{&actual_hex}), - }); + const notes_start = try eb.reserveNotes(notes_len); + eb.extra.items[notes_start] = @enumToInt(try eb.addErrorMessage(.{ + .msg = try eb.printString("expected .hash = \"{s}\",", .{&actual_hex}), + })); return error.PackageFetchFailed; } diff --git a/src/Sema.zig b/src/Sema.zig index 237936547e..03ebbbdbac 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2215,11 +2215,12 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) { if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation; - var errors: std.zig.ErrorBundle = undefined; - errors.init(gpa) catch unreachable; - Compilation.addModuleErrorMsg(gpa, &errors, err_msg.*) catch unreachable; + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + wip_errors.init(gpa) catch unreachable; + Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable; std.debug.print("compile error during Sema:\n", .{}); - errors.renderToStdErr(.no_color); + var error_bundle = wip_errors.toOwnedBundle() catch unreachable; + error_bundle.renderToStdErr(.no_color); crash_report.compilerPanic("unexpected compile error occurred", null, null); } diff --git a/src/main.zig b/src/main.zig index 14b63017e5..0a16aa5a46 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4436,9 +4436,9 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi var all_modules: Package.AllModules = .{}; defer all_modules.deinit(gpa); - var errors: std.zig.ErrorBundle = undefined; - try errors.init(gpa); - defer errors.deinit(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); // Here we borrow main package's table and will replace it with a fresh // one after this process completes. @@ -4453,15 +4453,17 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi &dependencies_source, &build_roots_source, "", - &errors, + &wip_errors, &all_modules, ); - if (errors.errorMessageCount() > 0) { + if (wip_errors.root_list.items.len > 0) { const ttyconf: std.debug.TTY.Config = switch (color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; + var errors = try wip_errors.toOwnedBundle(); + defer errors.deinit(gpa); errors.renderToStdErr(ttyconf); process.exit(1); } @@ -4721,16 +4723,18 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var errors: std.zig.ErrorBundle = undefined; - try errors.init(gpa); - defer errors.deinit(gpa); - try Compilation.addZirErrorMessages(gpa, &errors, &file); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try Compilation.addZirErrorMessages(&wip_errors, &file); const ttyconf: std.debug.TTY.Config = switch (color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - errors.renderToStdErr(ttyconf); + var error_bundle = try wip_errors.toOwnedBundle(); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(ttyconf); has_ast_error = true; } } @@ -4930,16 +4934,18 @@ fn fmtPathFile( defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var errors: std.zig.ErrorBundle = undefined; - try errors.init(gpa); - defer errors.deinit(gpa); - try Compilation.addZirErrorMessages(gpa, &errors, &file); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try Compilation.addZirErrorMessages(&wip_errors, &file); const ttyconf: std.debug.TTY.Config = switch (fmt.color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - errors.renderToStdErr(ttyconf); + var error_bundle = try wip_errors.toOwnedBundle(); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(ttyconf); fmt.any_error = true; } } @@ -4968,17 +4974,19 @@ fn fmtPathFile( } fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void { - var error_bundle: std.zig.ErrorBundle = undefined; - try error_bundle.init(gpa); - defer error_bundle.deinit(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); - try putAstErrorsIntoBundle(gpa, tree, path, &error_bundle); + try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors); const ttyconf: std.debug.TTY.Config = switch (color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; + var error_bundle = try wip_errors.toOwnedBundle(); + defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(ttyconf); } @@ -4986,7 +4994,7 @@ pub fn putAstErrorsIntoBundle( gpa: Allocator, tree: Ast, path: []const u8, - error_bundle: *std.zig.ErrorBundle, + wip_errors: *std.zig.ErrorBundle.Wip, ) !void { var file: Module.File = .{ .status = .never_loaded, @@ -5013,7 +5021,7 @@ pub fn putAstErrorsIntoBundle( file.zir_loaded = true; defer file.zir.deinit(gpa); - try Compilation.addZirErrorMessages(gpa, error_bundle, &file); + try Compilation.addZirErrorMessages(wip_errors, &file); } pub const info_zen = @@ -5595,16 +5603,18 @@ pub fn cmdAstCheck( defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var errors: std.zig.ErrorBundle = undefined; - try errors.init(gpa); - defer errors.deinit(gpa); - try Compilation.addZirErrorMessages(gpa, &errors, &file); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try Compilation.addZirErrorMessages(&wip_errors, &file); const ttyconf: std.debug.TTY.Config = switch (color) { .auto => std.debug.detectTTYConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; - errors.renderToStdErr(ttyconf); + var error_bundle = try wip_errors.toOwnedBundle(); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(ttyconf); process.exit(1); } @@ -5719,12 +5729,14 @@ pub fn cmdChangelist( defer file.zir.deinit(gpa); if (file.zir.hasCompileErrors()) { - var errors: std.zig.ErrorBundle = undefined; - try errors.init(gpa); - defer errors.deinit(gpa); - try Compilation.addZirErrorMessages(gpa, &errors, &file); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try Compilation.addZirErrorMessages(&wip_errors, &file); const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); - errors.renderToStdErr(ttyconf); + var error_bundle = try wip_errors.toOwnedBundle(); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(ttyconf); process.exit(1); } @@ -5758,12 +5770,14 @@ pub fn cmdChangelist( file.zir_loaded = true; if (file.zir.hasCompileErrors()) { - var errors: std.zig.ErrorBundle = undefined; - try errors.init(gpa); - defer errors.deinit(gpa); - try Compilation.addZirErrorMessages(gpa, &errors, &file); + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try Compilation.addZirErrorMessages(&wip_errors, &file); const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); - errors.renderToStdErr(ttyconf); + var error_bundle = try wip_errors.toOwnedBundle(); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(ttyconf); process.exit(1); } diff --git a/src/test.zig b/src/test.zig index ce87742606..663c4f1aff 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1242,7 +1242,7 @@ pub const TestContext = struct { defer self.gpa.free(zig_lib_directory.path.?); var aux_thread_pool: ThreadPool = undefined; - try aux_thread_pool.init(self.gpa); + try aux_thread_pool.init(.{ .allocator = self.gpa }); defer aux_thread_pool.deinit(); // Use the same global cache dir for all the tests, such that we for example don't have to @@ -1614,23 +1614,8 @@ pub const TestContext = struct { if (update.case != .Error) { var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); - if (all_errors.list.len != 0) { - print( - "\nCase '{s}': unexpected errors at update_index={d}:\n{s}\n", - .{ case.name, update_index, hr }, - ); - for (all_errors.list) |err_msg| { - switch (err_msg) { - .src => |src| { - print("{s}:{d}:{d}: error: {s}\n{s}\n", .{ - src.src_path, src.line + 1, src.column + 1, src.msg, hr, - }); - }, - .plain => |plain| { - print("error: {s}\n{s}\n", .{ plain.msg, hr }); - }, - } - } + if (all_errors.errorMessageCount() > 0) { + all_errors.renderToStdErr(std.debug.detectTTYConfig(std.io.getStdErr())); // TODO print generated C code return error.UnexpectedCompileErrors; } From 6d88c3e935357e73493a0d10cd85dd2676f6b85b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 16:27:49 -0700 Subject: [PATCH 157/294] add builtin.zig_version_string sometimes this is more useful than SemanticVersion --- src/Compilation.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index e19dc59445..3a75b44861 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5056,7 +5056,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca \\const std = @import("std"); \\/// Zig version. When writing code that supports multiple versions of Zig, prefer \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks. - \\pub const zig_version = std.SemanticVersion.parse("{s}") catch unreachable; + \\pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable; + \\pub const zig_version_string = "{s}"; \\pub const zig_backend = std.builtin.CompilerBackend.{}; \\ \\pub const output_mode = std.builtin.OutputMode.{}; From a42888e145ced152e6437c3241b799fd39c0da6a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 16:36:09 -0700 Subject: [PATCH 158/294] std.zig.ErrorBundle: add special representation for empty This allows the common case of no compilation errors to be represented without any allocations. --- lib/std/zig/ErrorBundle.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 1d2eca9703..94f80797a8 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -2,11 +2,21 @@ //! so that they can be created and destroyed appropriately. This structure //! is used to collect all the errors from the various places into one //! convenient place for API users to consume. +//! +//! There is one special encoding for this data structure. If both arrays are +//! empty, it means there are no errors. This special encoding exists so that +//! heap allocation is not needed in the common case of no errors. string_bytes: []const u8, /// The first thing in this array is an `ErrorMessageList`. extra: []const u32, +/// Special encoding when there are no errors. +pub const empty: ErrorBundle = .{ + .string_bytes = &.{}, + .extra = &.{}, +}; + // An index into `extra` pointing at an `ErrorMessage`. pub const MessageIndex = enum(u32) { _, @@ -72,6 +82,7 @@ pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void { } pub fn errorMessageCount(eb: ErrorBundle) u32 { + if (eb.extra.len == 0) return 0; return eb.getErrorMessageList().len; } @@ -313,6 +324,17 @@ pub const Wip = struct { pub fn toOwnedBundle(wip: *Wip) !ErrorBundle { const gpa = wip.gpa; + if (wip.root_list.items.len == 0) { + // Special encoding when there are no errors. + wip.deinit(); + wip.* = .{ + .gpa = gpa, + .string_bytes = .{}, + .extra = .{}, + .root_list = .{}, + }; + return empty; + } wip.setExtra(0, ErrorMessageList{ .len = @intCast(u32, wip.root_list.items.len), .start = @intCast(u32, wip.extra.items.len), From c583d140135fe5a57d055d9c0b8bdf59698f29e1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 16:49:44 -0700 Subject: [PATCH 159/294] std.fifo: add toOwnedSlice method --- lib/std/fifo.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index b7c8f761d3..ad929cde8a 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -383,6 +383,21 @@ pub fn LinearFifo( self.discard(try dest_writer.write(self.readableSlice(0))); } } + + pub fn toOwnedSlice(self: *Self) Allocator.Error![]T { + assert(self.head == 0); + assert(self.count <= self.buf.len); + const allocator = self.allocator; + if (allocator.resize(self.buf, self.count)) { + const result = self.buf[0..self.count]; + self.* = Self.init(allocator); + return result; + } + const new_memory = try allocator.dupe(T, self.buf[0..self.count]); + allocator.free(self.buf); + self.* = Self.init(allocator); + return new_memory; + } }; } From 986a30e373f6b2f0da2de64570013c83cacc17b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 16:52:35 -0700 Subject: [PATCH 160/294] integrate the build runner and the compiler server The compiler now provides a server protocol for an interactive session with another process. The build runner uses this protocol to communicate compilation errors semantically from zig compiler subprocesses to the build runner. The protocol is exposed via stdin/stdout, or on a network socket, depending on whether the CLI flag `--listen=-` or e.g. `--listen=127.0.0.1:1337` is used. Additionally: * add the zig version string to the build runner cache prefix * remove --prominent-compile-errors CLI flag because it no longer does anything. Compilation errors are now unconditionally displayed at the bottom of the build summary output when using the terminal-based build runner. * Remove the color field from std.Build. The build steps are no longer supposed to interact with stderr directly. Instead they communicate semantically back to the build runner, which has its own logic about TTY configuration. * Use the cleanExit() pattern in the build runner. * Build steps can now use error.MakeFailed when they have already properly reported an error, or they can fail with any other error code in which case the build runner will create a simple message based on this error code. --- lib/build_runner.zig | 113 +++++++--- lib/std/Build.zig | 147 ++++++++++--- lib/std/Build/CompileStep.zig | 6 +- lib/std/Build/RunStep.zig | 8 +- lib/std/Build/Step.zig | 29 ++- lib/std/zig.zig | 2 + lib/std/zig/Client.zig | 32 +++ lib/std/zig/Server.zig | 28 +++ src/main.zig | 380 ++++++++++++++++++++++------------ 9 files changed, 524 insertions(+), 221 deletions(-) create mode 100644 lib/std/zig/Client.zig create mode 100644 lib/std/zig/Server.zig diff --git a/lib/build_runner.zig b/lib/build_runner.zig index e12d46f03d..b95c36a196 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -1,6 +1,7 @@ const root = @import("@build"); const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; const io = std.io; const fmt = std.fmt; const mem = std.mem; @@ -71,8 +72,7 @@ pub fn main() !void { cache.addPrefix(build_root_directory); cache.addPrefix(local_cache_directory); cache.addPrefix(global_cache_directory); - - //cache.hash.addBytes(builtin.zig_version); + cache.hash.addBytes(builtin.zig_version_string); const builder = try std.Build.create( allocator, @@ -95,10 +95,8 @@ pub fn main() !void { var install_prefix: ?[]const u8 = null; var dir_list = std.Build.DirList{}; - // before arg parsing, check for the NO_COLOR environment variable - // if it exists, default the color setting to .off - // explicit --color arguments will still override this setting. - builder.color = if (process.hasEnvVarConstant("NO_COLOR")) .off else .auto; + const Color = enum { auto, off, on }; + var color: Color = .auto; while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-D")) { @@ -166,7 +164,7 @@ pub fn main() !void { std.debug.print("expected [auto|on|off] after --color", .{}); usageAndErr(builder, false, stderr_stream); }; - builder.color = std.meta.stringToEnum(@TypeOf(builder.color), next_arg) orelse { + color = std.meta.stringToEnum(Color, next_arg) orelse { std.debug.print("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); usageAndErr(builder, false, stderr_stream); }; @@ -200,8 +198,6 @@ pub fn main() !void { builder.verbose_cc = true; } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) { builder.verbose_llvm_cpu_features = true; - } else if (mem.eql(u8, arg, "--prominent-compile-errors")) { - builder.prominent_compile_errors = true; } else if (mem.eql(u8, arg, "-fwine")) { builder.enable_wine = true; } else if (mem.eql(u8, arg, "-fno-wine")) { @@ -257,6 +253,12 @@ pub fn main() !void { } } + const ttyconf: std.debug.TTY.Config = switch (color) { + .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .on => .escape_codes, + .off => .no_color, + }; + var progress: std.Progress = .{}; const main_progress_node = progress.start("", 0); defer main_progress_node.end(); @@ -272,11 +274,15 @@ pub fn main() !void { if (builder.validateUserInputDidItFail()) usageAndErr(builder, true, stderr_stream); - runStepNames(builder, targets.items, main_progress_node, thread_pool_options) catch |err| { - switch (err) { - error.UncleanExit => process.exit(1), - else => return err, - } + runStepNames( + builder, + targets.items, + main_progress_node, + thread_pool_options, + ttyconf, + ) catch |err| switch (err) { + error.UncleanExit => process.exit(1), + else => return err, }; } @@ -285,6 +291,7 @@ fn runStepNames( step_names: []const []const u8, parent_prog_node: *std.Progress.Node, thread_pool_options: std.Thread.Pool.Options, + ttyconf: std.debug.TTY.Config, ) !void { var step_stack = ArrayList(*Step).init(b.allocator); defer step_stack.deinit(); @@ -332,12 +339,14 @@ fn runStepNames( wait_group.start(); thread_pool.spawn(workerMakeOneStep, .{ - &wait_group, &thread_pool, b, step, &step_prog, + &wait_group, &thread_pool, b, step, &step_prog, ttyconf, }) catch @panic("OOM"); } } - var any_failed = false; + var success_count: usize = 0; + var failure_count: usize = 0; + var pending_count: usize = 0; for (step_stack.items) |s| { switch (s.state) { @@ -349,20 +358,42 @@ fn runStepNames( // A -> B -> C (failure) // B will be marked as dependency_failure, while A may never be queued, and thus // remain in the initial state of precheck_done. - .dependency_failure, .precheck_done => continue, - .success => continue, - .failure => { - any_failed = true; - std.debug.print("{s}: {s}\n", .{ - s.name, @errorName(s.result.err_code), - }); - }, + .dependency_failure, .precheck_done => pending_count += 1, + .success => success_count += 1, + .failure => failure_count += 1, } } - if (any_failed) { - process.exit(1); - } + const stderr = std.io.getStdErr(); + + const total_count = success_count + failure_count + pending_count; + stderr.writer().print("build summary: {d}/{d} steps succeeded; {d} failed\n", .{ + success_count, total_count, failure_count, + }) catch {}; + if (failure_count == 0) return cleanExit(); + + for (step_stack.items) |s| switch (s.state) { + .failure => { + // TODO print the dep prefix too + ttyconf.setColor(stderr, .Bold) catch break; + stderr.writeAll(s.name) catch break; + ttyconf.setColor(stderr, .Reset) catch break; + + if (s.result_error_bundle.errorMessageCount() > 0) { + stderr.writer().print(": {d} compilation errors:\n", .{ + s.result_error_bundle.errorMessageCount(), + }) catch break; + s.result_error_bundle.renderToStdErr(ttyconf); + } else { + stderr.writer().print(": {d} error messages (printed above)\n", .{ + s.result_error_msgs.items.len, + }) catch break; + } + }, + else => continue, + }; + + process.exit(1); } fn checkForDependencyLoop( @@ -407,6 +438,7 @@ fn workerMakeOneStep( b: *std.Build, s: *Step, prog_node: *std.Progress.Node, + ttyconf: std.debug.TTY.Config, ) void { defer wg.finish(); @@ -446,17 +478,26 @@ fn workerMakeOneStep( const make_result = s.make(); // No matter the result, we want to display error/warning messages. - if (s.result.error_msgs.items.len > 0) { + if (s.result_error_msgs.items.len > 0) { sub_prog_node.context.lock_stderr(); defer sub_prog_node.context.unlock_stderr(); - for (s.result.error_msgs.items) |msg| { - std.io.getStdErr().writeAll(msg) catch break; + const stderr = std.io.getStdErr(); + + for (s.result_error_msgs.items) |msg| { + // TODO print the dep prefix too + ttyconf.setColor(stderr, .Bold) catch break; + stderr.writeAll(s.name) catch break; + stderr.writeAll(": ") catch break; + ttyconf.setColor(stderr, .Red) catch break; + stderr.writeAll("error: ") catch break; + ttyconf.setColor(stderr, .Reset) catch break; + stderr.writeAll(msg) catch break; } } make_result catch |err| { - s.result.err_code = err; + assert(err == error.MakeFailed); @atomicStore(Step.State, &s.state, .failure, .SeqCst); return; }; @@ -467,7 +508,7 @@ fn workerMakeOneStep( for (s.dependants.items) |dep| { wg.start(); thread_pool.spawn(workerMakeOneStep, .{ - wg, thread_pool, b, dep, prog_node, + wg, thread_pool, b, dep, prog_node, ttyconf, }) catch @panic("OOM"); } } @@ -601,3 +642,11 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 { if (idx >= args.len) return null; return args[idx..]; } + +fn cleanExit() void { + if (builtin.mode == .Debug) { + return; + } else { + process.exit(0); + } +} diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 92ace4e60b..fc8ec26723 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -59,9 +59,6 @@ verbose_air: bool, verbose_llvm_ir: bool, verbose_cimport: bool, verbose_llvm_cpu_features: bool, -/// The purpose of executing the command is for a human to read compile errors from the terminal -prominent_compile_errors: bool, -color: enum { auto, on, off } = .auto, reference_trace: ?u32 = null, invalid_user_input: bool, zig_exe: []const u8, @@ -211,7 +208,6 @@ pub fn create( .verbose_llvm_ir = false, .verbose_cimport = false, .verbose_llvm_cpu_features = false, - .prominent_compile_errors = false, .invalid_user_input = false, .allocator = allocator, .user_input_options = UserInputOptionsMap.init(allocator), @@ -295,8 +291,6 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc .verbose_llvm_ir = parent.verbose_llvm_ir, .verbose_cimport = parent.verbose_cimport, .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features, - .prominent_compile_errors = parent.prominent_compile_errors, - .color = parent.color, .reference_trace = parent.reference_trace, .invalid_user_input = false, .zig_exe = parent.zig_exe, @@ -1409,54 +1403,149 @@ pub fn execAllowFail( } } -pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]u8 { +/// This function is used exclusively for spawning and communicating with the zig compiler. +/// TODO: move to build_runner.zig +pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]const u8 { assert(argv.len != 0); if (b.verbose) { - printCmd(b.allocator, null, argv); + const text = try allocPrintCmd(b.allocator, null, argv); + try s.result_error_msgs.append(b.allocator, text); } if (!process.can_spawn) { - try s.result.error_msgs.append(b.allocator, b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ + try s.result_error_msgs.append(b.allocator, b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ try allocPrintCmd(b.allocator, null, argv), })); - return error.CannotSpawnProcesses; + return error.MakeFailed; } - const result = std.ChildProcess.exec(.{ - .allocator = b.allocator, - .argv = argv, - .env_map = b.env_map, - .max_output_bytes = 10 * 1024 * 1024, - }) catch |err| { - try s.result.error_msgs.append(b.allocator, b.fmt("unable to spawn the following command: {s}\n{s}", .{ - @errorName(err), try allocPrintCmd(b.allocator, null, argv), - })); - return error.ExecFailed; - }; + var child = std.ChildProcess.init(argv, b.allocator); + child.env_map = b.env_map; + child.stdin_behavior = .Pipe; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; - if (result.stderr.len != 0) { - try s.result.error_msgs.append(b.allocator, result.stderr); + try child.spawn(); + + var poller = std.io.poll(b.allocator, enum { stdout, stderr }, .{ + .stdout = child.stdout.?, + .stderr = child.stderr.?, + }); + defer poller.deinit(); + + try sendMessage(child.stdin.?, .update); + try sendMessage(child.stdin.?, .exit); + + const Header = std.zig.Server.Message.Header; + var result: ?[]const u8 = null; + + while (try poller.poll()) { + const stdout = poller.fifo(.stdout); + const buf = stdout.readableSlice(0); + assert(stdout.readableLength() == buf.len); + if (buf.len >= @sizeOf(Header)) { + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + const header_and_msg_len = header.bytes_len + @sizeOf(Header); + if (buf.len >= header_and_msg_len) { + const body = buf[@sizeOf(Header)..]; + switch (header.tag) { + .zig_version => { + if (!mem.eql(u8, builtin.zig_version_string, body)) { + try s.result_error_msgs.append( + b.allocator, + b.fmt("zig version mismatch build runner vs compiler: '{s}' vs '{s}'", .{ + builtin.zig_version_string, body, + }), + ); + return error.MakeFailed; + } + }, + .error_bundle => { + const EbHdr = std.zig.Server.Message.ErrorBundle; + const eb_hdr = @ptrCast(*align(1) const EbHdr, body); + const extra_bytes = + body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + // TODO: use @ptrCast when the compiler supports it + const unaligned_extra = mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try b.allocator.alloc(u32, unaligned_extra.len); + // TODO: use @memcpy when it supports slices + for (extra_array, unaligned_extra) |*dst, src| dst.* = src; + s.result_error_bundle = .{ + .string_bytes = try b.allocator.dupe(u8, string_bytes), + .extra = extra_array, + }; + }, + .progress => { + @panic("TODO handle progress message"); + }, + .emit_bin_path => { + @panic("TODO handle emit_bin_path message"); + }, + _ => { + // Unrecognized message. + }, + } + stdout.discard(header_and_msg_len); + } + } } - switch (result.term) { + const stderr = poller.fifo(.stderr); + if (stderr.readableLength() > 0) { + try s.result_error_msgs.append(b.allocator, try stderr.toOwnedSlice()); + } + + // Send EOF to stdin. + child.stdin.?.close(); + child.stdin = null; + + const term = try child.wait(); + switch (term) { .Exited => |code| { if (code != 0) { - try s.result.error_msgs.append(b.allocator, b.fmt("the following command exited with error code {d}:\n{s}", .{ + try s.result_error_msgs.append(b.allocator, b.fmt("the following command exited with error code {d}:\n{s}", .{ code, try allocPrintCmd(b.allocator, null, argv), })); - return error.ExitCodeFailure; + return error.MakeFailed; } - return result.stdout; }, .Signal, .Stopped, .Unknown => |code| { _ = code; - try s.result.error_msgs.append(b.allocator, b.fmt("the following command terminated unexpectedly:\n{s}", .{ + try s.result_error_msgs.append(b.allocator, b.fmt("the following command terminated unexpectedly:\n{s}", .{ try allocPrintCmd(b.allocator, null, argv), })); - return error.ProcessTerminated; + return error.MakeFailed; }, } + + if (s.result_error_bundle.errorMessageCount() > 0) { + try s.result_error_msgs.append( + b.allocator, + b.fmt("the following command failed with {d} compilation errors:\n{s}", .{ + s.result_error_bundle.errorMessageCount(), + try allocPrintCmd(b.allocator, null, argv), + }), + ); + return error.MakeFailed; + } + + return result orelse { + try s.result_error_msgs.append(b.allocator, b.fmt("the following command failed to communicate the compilation result:\n{s}", .{ + try allocPrintCmd(b.allocator, null, argv), + })); + return error.MakeFailed; + }; +} + +fn sendMessage(file: fs.File, tag: std.zig.Client.Message.Tag) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(std.mem.asBytes(&header)); } /// This is a helper function to be called from build.zig scripts, *not* from diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index ce0ede9510..c5c2b2a440 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1177,11 +1177,6 @@ fn make(step: *Step) !void { }; try zig_args.append(cmd); - if (builder.color != .auto) { - try zig_args.append("--color"); - try zig_args.append(@tagName(builder.color)); - } - if (builder.reference_trace) |some| { try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-freference-trace={d}", .{some})); } @@ -1834,6 +1829,7 @@ fn make(step: *Step) !void { } try zig_args.append("--enable-cache"); + try zig_args.append("--listen=-"); // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 815916f380..904ef0935f 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -419,12 +419,8 @@ pub fn runCommand( }; if (!termMatches(expected_term, term)) { - if (builder.prominent_compile_errors) { - std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) }); - } else { - std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) }); - printCmd(cwd, argv); - } + std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) }); + printCmd(cwd, argv); return error.UnexpectedExit; } diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 42698f2190..4edece8038 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -6,15 +6,13 @@ dependencies: std.ArrayList(*Step), /// then populated during dependency loop checking in the build runner. dependants: std.ArrayListUnmanaged(*Step), state: State, -/// Populated only if state is success. -result: struct { - err_code: anyerror, - error_msgs: std.ArrayListUnmanaged([]const u8), -}, /// The return addresss associated with creation of this step that can be useful /// to print along with debugging messages. debug_stack_trace: [n_debug_stack_frames]usize, +result_error_msgs: std.ArrayListUnmanaged([]const u8), +result_error_bundle: std.zig.ErrorBundle, + const n_debug_stack_frames = 4; pub const State = enum { @@ -94,16 +92,25 @@ pub fn init(allocator: Allocator, options: Options) Step { .dependencies = std.ArrayList(*Step).init(allocator), .dependants = .{}, .state = .precheck_unstarted, - .result = .{ - .err_code = undefined, - .error_msgs = .{}, - }, .debug_stack_trace = addresses, + .result_error_msgs = .{}, + .result_error_bundle = std.zig.ErrorBundle.empty, }; } -pub fn make(self: *Step) !void { - try self.makeFn(self); +/// If the Step's `make` function reports `error.MakeFailed`, it indicates they +/// have already reported the error. Otherwise, we add a simple error report +/// here. +pub fn make(s: *Step) error{MakeFailed}!void { + return s.makeFn(s) catch |err| { + if (err != error.MakeFailed) { + const gpa = s.dependencies.allocator; + s.result_error_msgs.append(gpa, std.fmt.allocPrint(gpa, "{s} failed: {s}", .{ + s.name, @errorName(err), + }) catch @panic("OOM")) catch @panic("OOM"); + } + return error.MakeFailed; + }; } pub fn dependOn(self: *Step, other: *Step) void { diff --git a/lib/std/zig.zig b/lib/std/zig.zig index ecff8e99bd..98edeabd10 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -4,6 +4,8 @@ const fmt = @import("zig/fmt.zig"); const assert = std.debug.assert; pub const ErrorBundle = @import("zig/ErrorBundle.zig"); +pub const Server = @import("zig/Server.zig"); +pub const Client = @import("zig/Client.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const fmtId = fmt.fmtId; diff --git a/lib/std/zig/Client.zig b/lib/std/zig/Client.zig new file mode 100644 index 0000000000..a68c189e57 --- /dev/null +++ b/lib/std/zig/Client.zig @@ -0,0 +1,32 @@ +pub const Message = struct { + pub const Header = extern struct { + tag: Tag, + /// Size of the body only; does not include this Header. + bytes_len: u32, + }; + + pub const Tag = enum(u32) { + /// Tells the compiler to shut down cleanly. + /// No body. + exit, + /// Tells the compiler to detect changes in source files and update the + /// affected output compilation artifacts. + /// If one of the compilation artifacts is an executable that is + /// running as a child process, the compiler will wait for it to exit + /// before performing the update. + /// No body. + update, + /// Tells the compiler to execute the executable as a child process. + /// No body. + run, + /// Tells the compiler to detect changes in source files and update the + /// affected output compilation artifacts. + /// If one of the compilation artifacts is an executable that is + /// running as a child process, the compiler will perform a hot code + /// swap. + /// No body. + hot_update, + + _, + }; +}; diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig new file mode 100644 index 0000000000..76f2303f6b --- /dev/null +++ b/lib/std/zig/Server.zig @@ -0,0 +1,28 @@ +pub const Message = struct { + pub const Header = extern struct { + tag: Tag, + /// Size of the body only; does not include this Header. + bytes_len: u32, + }; + + pub const Tag = enum(u32) { + /// Body is a UTF-8 string. + zig_version, + /// Body is an ErrorBundle. + error_bundle, + /// Body is a UTF-8 string. + progress, + /// Body is a UTF-8 string. + emit_bin_path, + _, + }; + + /// Trailing: + /// * extra: [extra_len]u32, + /// * string_bytes: [string_bytes_len]u8, + /// See `std.zig.ErrorBundle`. + pub const ErrorBundle = extern struct { + extra_len: u32, + string_bytes_len: u32, + }; +}; diff --git a/src/main.zig b/src/main.zig index 0a16aa5a46..d574681bcd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -668,6 +668,12 @@ const ArgMode = union(enum) { run, }; +const Listen = union(enum) { + none, + ip4: std.net.Ip4Address, + stdio, +}; + fn buildOutputType( gpa: Allocator, arena: Allocator, @@ -689,7 +695,7 @@ fn buildOutputType( var function_sections = false; var no_builtin = false; var watch = false; - var listen_addr: ?std.net.Ip4Address = null; + var listen: Listen = .none; var debug_compile_errors = false; var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK"); var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC"); @@ -1149,14 +1155,22 @@ fn buildOutputType( } } else if (mem.eql(u8, arg, "--listen")) { const next_arg = args_iter.nextOrFatal(); - // example: --listen 127.0.0.1:9000 - var it = std.mem.split(u8, next_arg, ":"); - const host = it.next().?; - const port_text = it.next() orelse "14735"; - const port = std.fmt.parseInt(u16, port_text, 10) catch |err| - fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) }); - listen_addr = std.net.Ip4Address.parse(host, port) catch |err| - fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }); + if (mem.eql(u8, next_arg, "-")) { + listen = .stdio; + watch = true; + } else { + // example: --listen 127.0.0.1:9000 + var it = std.mem.split(u8, next_arg, ":"); + const host = it.next().?; + const port_text = it.next() orelse "14735"; + const port = std.fmt.parseInt(u16, port_text, 10) catch |err| + fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) }); + listen = .{ .ip4 = std.net.Ip4Address.parse(host, port) catch |err| + fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }) }; + watch = true; + } + } else if (mem.eql(u8, arg, "--listen=-")) { + listen = .stdio; watch = true; } else if (mem.eql(u8, arg, "--debug-link-snapshot")) { if (!build_options.enable_link_snapshots) { @@ -3277,6 +3291,47 @@ fn buildOutputType( return cmdTranslateC(comp, arena, have_enable_cache); } + switch (listen) { + .none => {}, + .stdio => { + try serve( + comp, + std.io.getStdIn(), + std.io.getStdOut(), + test_exec_args.items, + self_exe_path, + arg_mode, + all_args, + runtime_args_start, + ); + return cleanExit(); + }, + .ip4 => |ip4_addr| { + var server = std.net.StreamServer.init(.{ + .reuse_address = true, + }); + defer server.deinit(); + + try server.listen(.{ .in = ip4_addr }); + + while (true) { + const conn = try server.accept(); + defer conn.stream.close(); + + try serve( + comp, + .{ .handle = conn.stream.handle }, + .{ .handle = conn.stream.handle }, + test_exec_args.items, + self_exe_path, + arg_mode, + all_args, + runtime_args_start, + ); + } + }, + } + const hook: AfterUpdateHook = blk: { if (!have_enable_cache) break :blk .none; @@ -3354,6 +3409,12 @@ fn buildOutputType( ); } + // TODO move this REPL implementation to the standard library / build + // system and have it be a CLI abstraction layer on top of the real, actual + // binary protocol of the compiler. Make it actually interface through the + // server protocol. This way the REPL does not have any special powers that + // an IDE couldn't also have. + const stdin = std.io.getStdIn().reader(); const stderr = std.io.getStdErr().writer(); var repl_buf: [1024]u8 = undefined; @@ -3367,123 +3428,6 @@ fn buildOutputType( var last_cmd: ReplCmd = .help; - if (listen_addr) |ip4_addr| { - var server = std.net.StreamServer.init(.{ - .reuse_address = true, - }); - defer server.deinit(); - - try server.listen(.{ .in = ip4_addr }); - - while (true) { - const conn = try server.accept(); - defer conn.stream.close(); - - var buf: [100]u8 = undefined; - var child_pid: ?i32 = null; - - while (true) { - try comp.makeBinFileExecutable(); - - const amt = try conn.stream.read(&buf); - const line = buf[0..amt]; - const actual_line = mem.trimRight(u8, line, "\r\n "); - - const cmd: ReplCmd = blk: { - if (mem.eql(u8, actual_line, "update")) { - break :blk .update; - } else if (mem.eql(u8, actual_line, "exit")) { - break; - } else if (mem.eql(u8, actual_line, "help")) { - break :blk .help; - } else if (mem.eql(u8, actual_line, "run")) { - break :blk .run; - } else if (mem.eql(u8, actual_line, "update-and-run")) { - break :blk .update_and_run; - } else if (actual_line.len == 0) { - break :blk last_cmd; - } else { - try stderr.print("unknown command: {s}\n", .{actual_line}); - continue; - } - }; - last_cmd = cmd; - switch (cmd) { - .update => { - tracy.frameMark(); - if (output_mode == .Exe) { - try comp.makeBinFileWritable(); - } - updateModule(gpa, comp, hook) catch |err| switch (err) { - error.SemanticAnalyzeFail => continue, - else => |e| return e, - }; - }, - .help => { - try stderr.writeAll(repl_help); - }, - .run => { - tracy.frameMark(); - try runOrTest( - comp, - gpa, - arena, - test_exec_args.items, - self_exe_path.?, - arg_mode, - target_info, - watch, - &comp_destroyed, - all_args, - runtime_args_start, - link_libc, - ); - }, - .update_and_run => { - tracy.frameMark(); - if (child_pid) |pid| { - try conn.stream.writer().print("hot code swap requested for pid {d}", .{pid}); - try comp.hotCodeSwap(pid); - - var errors = try comp.getAllErrorsAlloc(); - defer errors.deinit(comp.gpa); - - if (errors.errorMessageCount() > 0) { - const ttyconf: std.debug.TTY.Config = switch (comp.color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; - try errors.renderToWriter(ttyconf, conn.stream.writer()); - continue; - } - } else { - if (output_mode == .Exe) { - try comp.makeBinFileWritable(); - } - updateModule(gpa, comp, hook) catch |err| switch (err) { - error.SemanticAnalyzeFail => continue, - else => |e| return e, - }; - try comp.makeBinFileExecutable(); - - child_pid = try runOrTestHotSwap( - comp, - gpa, - arena, - test_exec_args.items, - self_exe_path.?, - arg_mode, - all_args, - runtime_args_start, - ); - } - }, - } - } - } - } - while (watch) { try stderr.print("(zig) ", .{}); try comp.makeBinFileExecutable(); @@ -3576,6 +3520,173 @@ fn buildOutputType( return cleanExit(); } +fn serve( + comp: *Compilation, + in: fs.File, + out: fs.File, + test_exec_args: []const ?[]const u8, + self_exe_path: ?[]const u8, + arg_mode: ArgMode, + all_args: []const []const u8, + runtime_args_start: ?usize, +) !void { + const gpa = comp.gpa; + + try serveMessage(out, .{ + .tag = .zig_version, + .bytes_len = build_options.version.len, + }, &.{ + build_options.version, + }); + + var child_pid: ?i32 = null; + var receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(gpa); + defer receive_fifo.deinit(); + + while (true) { + const hdr = try receiveMessage(in, &receive_fifo); + + switch (hdr.tag) { + .exit => { + return cleanExit(); + }, + .update => { + tracy.frameMark(); + if (comp.bin_file.options.output_mode == .Exe) { + try comp.makeBinFileWritable(); + } + try comp.update(); + try comp.makeBinFileExecutable(); + try serveUpdateResults(out, comp); + }, + .run => { + if (child_pid != null) { + @panic("TODO block until the child exits"); + } + @panic("TODO call runOrTest"); + //try runOrTest( + // comp, + // gpa, + // arena, + // test_exec_args, + // self_exe_path.?, + // arg_mode, + // target_info, + // true, + // &comp_destroyed, + // all_args, + // runtime_args_start, + // link_libc, + //); + }, + .hot_update => { + tracy.frameMark(); + if (child_pid) |pid| { + try comp.hotCodeSwap(pid); + try serveUpdateResults(out, comp); + } else { + if (comp.bin_file.options.output_mode == .Exe) { + try comp.makeBinFileWritable(); + } + try comp.update(); + try comp.makeBinFileExecutable(); + try serveUpdateResults(out, comp); + + child_pid = try runOrTestHotSwap( + comp, + gpa, + test_exec_args, + self_exe_path.?, + arg_mode, + all_args, + runtime_args_start, + ); + } + }, + _ => { + @panic("TODO unrecognized message from client"); + }, + } + } +} + +fn serveMessage( + out: fs.File, + header: std.zig.Server.Message.Header, + bufs: []const []const u8, +) !void { + var iovecs: [10]std.os.iovec_const = undefined; + iovecs[0] = .{ + .iov_base = @ptrCast([*]const u8, &header), + .iov_len = @sizeOf(std.zig.Server.Message.Header), + }; + for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { + iovec.* = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + } + try out.writevAll(iovecs[0 .. bufs.len + 1]); +} + +fn serveErrorBundle(out: fs.File, error_bundle: std.zig.ErrorBundle) !void { + const eb_hdr: std.zig.Server.Message.ErrorBundle = .{ + .extra_len = @intCast(u32, error_bundle.extra.len), + .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), + }; + const bytes_len = @sizeOf(std.zig.Server.Message.ErrorBundle) + + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; + try serveMessage(out, .{ + .tag = .error_bundle, + .bytes_len = @intCast(u32, bytes_len), + }, &.{ + std.mem.asBytes(&eb_hdr), + // TODO: implement @ptrCast between slices changing the length + std.mem.sliceAsBytes(error_bundle.extra), + error_bundle.string_bytes, + }); +} + +fn serveUpdateResults(out: fs.File, comp: *Compilation) !void { + const gpa = comp.gpa; + var error_bundle = try comp.getAllErrorsAlloc(); + defer error_bundle.deinit(gpa); + if (error_bundle.errorMessageCount() > 0) { + try serveErrorBundle(out, error_bundle); + } else if (comp.bin_file.options.emit) |emit| { + const full_path = try emit.directory.join(gpa, &.{emit.sub_path}); + defer gpa.free(full_path); + + try serveMessage(out, .{ + .tag = .emit_bin_path, + .bytes_len = @intCast(u32, full_path.len), + }, &.{ + full_path, + }); + } +} + +fn receiveMessage(in: fs.File, fifo: *std.fifo.LinearFifo(u8, .Dynamic)) !std.zig.Client.Message.Header { + const Header = std.zig.Client.Message.Header; + + while (true) { + const buf = fifo.readableSlice(0); + assert(fifo.readableLength() == buf.len); + if (buf.len >= @sizeOf(Header)) { + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + if (header.bytes_len != 0) + return error.InvalidClientMessage; + const result = header.*; + fifo.discard(@sizeOf(Header)); + return result; + } + + const write_buffer = try fifo.writableWithSize(256); + const amt = try in.read(write_buffer); + fifo.update(amt); + } +} + const ModuleDepIterator = struct { split: mem.SplitIterator(u8), @@ -3765,7 +3876,6 @@ fn runOrTest( fn runOrTestHotSwap( comp: *Compilation, gpa: Allocator, - arena: Allocator, test_exec_args: []const ?[]const u8, self_exe_path: []const u8, arg_mode: ArgMode, @@ -3775,9 +3885,10 @@ fn runOrTestHotSwap( const exe_emit = comp.bin_file.options.emit.?; // A naive `directory.join` here will indeed get the correct path to the binary, // however, in the case of cwd, we actually want `./foo` so that the path can be executed. - const exe_path = try fs.path.join(arena, &[_][]const u8{ + const exe_path = try fs.path.join(gpa, &[_][]const u8{ exe_emit.directory.path orelse ".", exe_emit.sub_path, }); + defer gpa.free(exe_path); var argv = std.ArrayList([]const u8).init(gpa); defer argv.deinit(); @@ -3807,7 +3918,7 @@ fn runOrTestHotSwap( if (runtime_args_start) |i| { try argv.appendSlice(all_args[i..]); } - var child = std.ChildProcess.init(argv.items, arena); + var child = std.ChildProcess.init(argv.items, gpa); child.stdin_behavior = .Inherit; child.stdout_behavior = .Inherit; @@ -4206,7 +4317,6 @@ pub const usage_build = pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { var color: Color = .auto; - var prominent_compile_errors: bool = false; // We want to release all the locks before executing the child process, so we make a nice // big block here to ensure the cleanup gets run when we extract out our argv. @@ -4267,8 +4377,6 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi i += 1; override_global_cache_dir = args[i]; continue; - } else if (mem.eql(u8, arg, "--prominent-compile-errors")) { - prominent_compile_errors = true; } else if (mem.eql(u8, arg, "-freference-trace")) { try child_argv.append(arg); reference_trace = 256; @@ -4535,12 +4643,8 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi .Exited => |code| { if (code == 0) return cleanExit(); - if (prominent_compile_errors) { - fatal("the build command failed with exit code {d}", .{code}); - } else { - const cmd = try std.mem.join(arena, " ", child_argv); - fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); - } + const cmd = try std.mem.join(arena, " ", child_argv); + fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); }, else => { const cmd = try std.mem.join(arena, " ", child_argv); From 7a3dabdc4738c2816bede92571ccdf481d400997 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 16:55:51 -0700 Subject: [PATCH 161/294] build runner: account for debug builds in cleanExit build runner is always compiled in debug mode, so the switch on optimization here was silly. --- lib/build_runner.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index b95c36a196..bb3fb42f0e 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -644,9 +644,8 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 { } fn cleanExit() void { - if (builtin.mode == .Debug) { - return; - } else { - process.exit(0); - } + // Perhaps in the future there could be an Advanced Options flag such as + // --debug-build-runner-leaks which would make this function return instead + // of calling exit. + process.exit(0); } From 79440d2b470b3906bd87334ecd90b2a0f2cea05b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 17:15:06 -0700 Subject: [PATCH 162/294] std.Build.CompileStep: obtain the build output dir from protocol Now building successfully works again. --- lib/build_runner.zig | 6 ++++++ lib/std/Build.zig | 9 ++++----- lib/std/Build/CompileStep.zig | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index bb3fb42f0e..603ff770cc 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -364,12 +364,17 @@ fn runStepNames( } } + // A proper command line application defaults to silently succeeding. + // The user may request verbose mode if they have a different preference. + if (failure_count == 0 and !b.verbose) return cleanExit(); + const stderr = std.io.getStdErr(); const total_count = success_count + failure_count + pending_count; stderr.writer().print("build summary: {d}/{d} steps succeeded; {d} failed\n", .{ success_count, total_count, failure_count, }) catch {}; + if (failure_count == 0) return cleanExit(); for (step_stack.items) |s| switch (s.state) { @@ -493,6 +498,7 @@ fn workerMakeOneStep( stderr.writeAll("error: ") catch break; ttyconf.setColor(stderr, .Reset) catch break; stderr.writeAll(msg) catch break; + stderr.writeAll("\n") catch break; } } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index fc8ec26723..a9d7c31733 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1154,13 +1154,12 @@ fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8 for (argv) |arg| { try buf.writer().print("{s} ", .{arg}); } - try buf.append('\n'); return buf.toOwnedSlice(); } fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void { const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM"); - std.debug.print("{s}", .{text}); + std.debug.print("{s}\n", .{text}); } pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void { @@ -1482,7 +1481,7 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]const u8 { @panic("TODO handle progress message"); }, .emit_bin_path => { - @panic("TODO handle emit_bin_path message"); + result = try b.allocator.dupe(u8, body); }, _ => { // Unrecognized message. @@ -1553,7 +1552,7 @@ fn sendMessage(file: fs.File, tag: std.zig.Client.Message.Tag) !void { /// a helpful message. pub fn exec(b: *Build, argv: []const []const u8) []u8 { if (!process.can_spawn) { - std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}", .{ + std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{ try allocPrintCmd(b.allocator, null, argv), }); process.exit(1); @@ -1562,7 +1561,7 @@ pub fn exec(b: *Build, argv: []const []const u8) []u8 { var code: u8 = undefined; return b.execAllowFail(argv, &code, .Inherit) catch |err| { const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM"); - std.debug.print("unable to spawn the following command: {s}\n{s}", .{ + std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{ @errorName(err), printed_cmd, }); process.exit(1); diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index c5c2b2a440..a7d2926770 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1888,8 +1888,8 @@ fn make(step: *Step) !void { try zig_args.append(resolved_args_file); } - const output_dir_nl = try builder.execFromStep(zig_args.items, &self.step); - const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n"); + const output_bin_path = try builder.execFromStep(zig_args.items, &self.step); + const build_output_dir = fs.path.dirname(output_bin_path).?; if (self.output_dir) |output_dir| { var src_dir = try std.fs.cwd().openIterableDir(build_output_dir, .{}); From 27f136e8282e4d5a64420d633e96f2a3ee6da08e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 21:12:10 -0700 Subject: [PATCH 163/294] build.zig: remove redundant dependency of install step on zig exe --- build.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/build.zig b/build.zig index 797c980739..189f50407d 100644 --- a/build.zig +++ b/build.zig @@ -175,7 +175,6 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&exe.step); } - b.default_step.dependOn(&exe.step); exe.single_threaded = single_threaded; if (target.isWindows() and target.getAbi() == .gnu) { From 7da34bd9e86fe32dec56e6e89a6d4615e2b50794 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 21:13:23 -0700 Subject: [PATCH 164/294] build runner: print a fancy tree with build results on failure --- lib/build_runner.zig | 145 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 27 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 603ff770cc..d70dbf2919 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -261,7 +261,6 @@ pub fn main() !void { var progress: std.Progress = .{}; const main_progress_node = progress.start("", 0); - defer main_progress_node.end(); builder.debug_log_scopes = debug_log_scopes.items; builder.resolveInstallPrefix(install_prefix, dir_list); @@ -323,6 +322,8 @@ fn runStepNames( defer thread_pool.deinit(); { + defer parent_prog_node.end(); + var step_prog = parent_prog_node.start("run steps", step_stack.items.len); defer step_prog.end(); @@ -347,20 +348,28 @@ fn runStepNames( var success_count: usize = 0; var failure_count: usize = 0; var pending_count: usize = 0; + var total_compile_errors: usize = 0; for (step_stack.items) |s| { switch (s.state) { .precheck_unstarted => unreachable, .precheck_started => unreachable, .running => unreachable, - // precheck_done is equivalent to dependency_failure in the case of - // transitive dependencies. For example: - // A -> B -> C (failure) - // B will be marked as dependency_failure, while A may never be queued, and thus - // remain in the initial state of precheck_done. - .dependency_failure, .precheck_done => pending_count += 1, + .precheck_done => { + // precheck_done is equivalent to dependency_failure in the case of + // transitive dependencies. For example: + // A -> B -> C (failure) + // B will be marked as dependency_failure, while A may never be queued, and thus + // remain in the initial state of precheck_done. + s.state = .dependency_failure; + pending_count += 1; + }, + .dependency_failure => pending_count += 1, .success => success_count += 1, - .failure => failure_count += 1, + .failure => { + failure_count += 1; + total_compile_errors += s.result_error_bundle.errorMessageCount(); + }, } } @@ -371,36 +380,118 @@ fn runStepNames( const stderr = std.io.getStdErr(); const total_count = success_count + failure_count + pending_count; - stderr.writer().print("build summary: {d}/{d} steps succeeded; {d} failed\n", .{ - success_count, total_count, failure_count, + ttyconf.setColor(stderr, .Cyan) catch {}; + stderr.writeAll("Build Summary: ") catch {}; + ttyconf.setColor(stderr, .Reset) catch {}; + stderr.writer().print("{d}/{d} steps succeeded; {d} failed; {d} total compile errors\n", .{ + success_count, total_count, failure_count, total_compile_errors, }) catch {}; + // Print a fancy tree with build results. + var print_node: PrintNode = .{ .parent = null }; + if (step_names.len == 0) { + print_node.last = true; + printTreeStep(b, b.default_step, stderr, ttyconf, &print_node) catch {}; + } else { + for (step_names, 0..) |step_name, i| { + const tls = b.top_level_steps.get(step_name).?; + print_node.last = i + 1 == b.top_level_steps.count(); + printTreeStep(b, &tls.step, stderr, ttyconf, &print_node) catch {}; + } + } + if (failure_count == 0) return cleanExit(); - for (step_stack.items) |s| switch (s.state) { - .failure => { - // TODO print the dep prefix too - ttyconf.setColor(stderr, .Bold) catch break; - stderr.writeAll(s.name) catch break; - ttyconf.setColor(stderr, .Reset) catch break; - + // Finally, render compile errors at the bottom of the terminal. + if (total_compile_errors > 0) { + for (step_stack.items) |s| { if (s.result_error_bundle.errorMessageCount() > 0) { - stderr.writer().print(": {d} compilation errors:\n", .{ - s.result_error_bundle.errorMessageCount(), - }) catch break; s.result_error_bundle.renderToStdErr(ttyconf); - } else { - stderr.writer().print(": {d} error messages (printed above)\n", .{ - s.result_error_msgs.items.len, - }) catch break; } - }, - else => continue, - }; + } + + // Signal to parent process that we have printed compile errors. The + // parent process may choose to omit the "following command failed" + // line in this case. + process.exit(2); + } process.exit(1); } +const PrintNode = struct { + parent: ?*PrintNode, + last: bool = false, +}; + +fn printTreeStep( + b: *std.Build, + s: *Step, + stderr: std.fs.File, + ttyconf: std.debug.TTY.Config, + parent_node: *PrintNode, +) !void { + var opt_node: ?*PrintNode = parent_node.parent; + while (opt_node) |n| : (opt_node = n.parent) { + if (n.parent == null) break; + if (n.last) { + try stderr.writeAll(" "); + } else { + try stderr.writeAll("│ "); + } + } + + if (parent_node.parent != null) { + if (parent_node.last) { + try stderr.writeAll("└─ "); + } else { + try stderr.writeAll("├─ "); + } + } + + // TODO print the dep prefix too? + try stderr.writeAll(s.name); + + switch (s.state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + .running => unreachable, + + .dependency_failure => { + try ttyconf.setColor(stderr, .Dim); + try stderr.writeAll(" transitive failure\n"); + try ttyconf.setColor(stderr, .Reset); + }, + + .success => { + try ttyconf.setColor(stderr, .Green); + try stderr.writeAll(" success\n"); + try ttyconf.setColor(stderr, .Reset); + }, + + .failure => { + try ttyconf.setColor(stderr, .Red); + if (s.result_error_bundle.errorMessageCount() > 0) { + try stderr.writer().print(" {d} errors\n", .{ + s.result_error_bundle.errorMessageCount(), + }); + } else { + try stderr.writeAll(" failure\n"); + } + try ttyconf.setColor(stderr, .Reset); + }, + } + + for (s.dependencies.items, 0..) |dep, i| { + var print_node: PrintNode = .{ + .parent = parent_node, + .last = i == s.dependencies.items.len - 1, + }; + try printTreeStep(b, dep, stderr, ttyconf, &print_node); + } +} + fn checkForDependencyLoop( b: *std.Build, s: *Step, From 7efeedcd896b340a95cae50cd8c175a178465d8b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 21:13:54 -0700 Subject: [PATCH 165/294] std.Build.CompileStep: better default step name --- lib/std/Build/CompileStep.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index a7d2926770..491c8ac8e4 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -312,6 +312,12 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep { panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); } + const step_name = builder.fmt("compile {s} {s} {s}", .{ + name, + @tagName(options.optimize), + options.target.zigTriple(builder.allocator) catch @panic("OOM"), + }); + const self = builder.allocator.create(CompileStep) catch @panic("OOM"); self.* = CompileStep{ .strip = null, @@ -328,7 +334,7 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep { .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator), .step = Step.init(builder.allocator, .{ .id = base_id, - .name = name, + .name = step_name, .makeFn = make, }), .version = options.version, From 85b4b6e3b35dd6b70092ac006b6bd621332e258f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 21:14:09 -0700 Subject: [PATCH 166/294] std.Build.InstallArtifactStep: better default step name --- lib/std/Build/InstallArtifactStep.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index 43dcffede8..b652ade38f 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -21,7 +21,7 @@ pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep .builder = builder, .step = Step.init(builder.allocator, .{ .id = base_id, - .name = builder.fmt("install {s}", .{artifact.step.name}), + .name = builder.fmt("install {s}", .{artifact.name}), .makeFn = make, }), .artifact = artifact, From 8acbfafefb635390f6b4da102966b5c4a2f3332e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 21:14:32 -0700 Subject: [PATCH 167/294] compiler: update function accepts a std.Progress.Node This makes progress be exposed to the top-level caller of update(). I tossed in a bonus change: when the `zig build` subcommand sees exit code 2, it omits the "following command failed" line, and the build runner uses exit code 2 when there are compile errors. This tidies up the output on build failure by a little bit. --- src/Compilation.zig | 85 +++++++++++++++++++++++---------------------- src/glibc.zig | 19 +++++----- src/libcxx.zig | 8 ++--- src/libtsan.zig | 4 +-- src/libunwind.zig | 4 +-- src/main.zig | 48 ++++++++++++++++++++++--- src/mingw.zig | 14 ++++---- src/musl.zig | 16 ++++----- src/wasi_libc.zig | 16 ++++----- 9 files changed, 130 insertions(+), 84 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 3a75b44861..478f931718 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1847,8 +1847,15 @@ fn cleanupTmpArtifactDirectory( } } +pub fn hotCodeSwap(comp: *Compilation, prog_node: *std.Progress.Node, pid: std.os.pid_t) !void { + comp.bin_file.child_pid = pid; + try comp.makeBinFileWritable(); + try comp.update(prog_node); + try comp.makeBinFileExecutable(); +} + /// Detect changes to source files, perform semantic analysis, and update the output files. -pub fn update(comp: *Compilation) !void { +pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void { const tracy_trace = trace(@src()); defer tracy_trace.end(); @@ -1995,21 +2002,6 @@ pub fn update(comp: *Compilation) !void { } } - // If the terminal is dumb, we dont want to show the user all the output. - var progress: std.Progress = .{ .dont_print_on_dumb = true }; - const main_progress_node = progress.start("", 0); - defer main_progress_node.end(); - switch (comp.color) { - .off => { - progress.terminal = null; - }, - .on => { - progress.terminal = std.io.getStdErr(); - progress.supports_ansi_escape_codes = true; - }, - .auto => {}, - } - try comp.performAllTheWork(main_progress_node); if (comp.bin_file.options.module) |module| { @@ -3057,11 +3049,11 @@ pub fn performAllTheWork( // backend, preventing anonymous Decls from being prematurely destroyed. while (true) { if (comp.work_queue.readItem()) |work_item| { - try processOneJob(comp, work_item); + try processOneJob(comp, work_item, main_progress_node); continue; } if (comp.anon_work_queue.readItem()) |work_item| { - try processOneJob(comp, work_item); + try processOneJob(comp, work_item, main_progress_node); continue; } break; @@ -3069,16 +3061,16 @@ pub fn performAllTheWork( if (comp.job_queued_compiler_rt_lib) { comp.job_queued_compiler_rt_lib = false; - buildCompilerRtOneShot(comp, .Lib, &comp.compiler_rt_lib); + buildCompilerRtOneShot(comp, .Lib, &comp.compiler_rt_lib, main_progress_node); } if (comp.job_queued_compiler_rt_obj) { comp.job_queued_compiler_rt_obj = false; - buildCompilerRtOneShot(comp, .Obj, &comp.compiler_rt_obj); + buildCompilerRtOneShot(comp, .Obj, &comp.compiler_rt_obj, main_progress_node); } } -fn processOneJob(comp: *Compilation, job: Job) !void { +fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !void { switch (job) { .codegen_decl => |decl_index| { const module = comp.bin_file.options.module.?; @@ -3230,7 +3222,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("glibc_crt_file"); defer named_frame.end(); - glibc.buildCRTFile(comp, crt_file) catch |err| { + glibc.buildCRTFile(comp, crt_file, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{ @errorName(err), @@ -3241,7 +3233,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("glibc_shared_objects"); defer named_frame.end(); - glibc.buildSharedObjects(comp) catch |err| { + glibc.buildSharedObjects(comp, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .glibc_shared_objects, @@ -3254,7 +3246,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("musl_crt_file"); defer named_frame.end(); - musl.buildCRTFile(comp, crt_file) catch |err| { + musl.buildCRTFile(comp, crt_file, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .musl_crt_file, @@ -3267,7 +3259,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("mingw_crt_file"); defer named_frame.end(); - mingw.buildCRTFile(comp, crt_file) catch |err| { + mingw.buildCRTFile(comp, crt_file, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .mingw_crt_file, @@ -3294,7 +3286,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("libunwind"); defer named_frame.end(); - libunwind.buildStaticLib(comp) catch |err| { + libunwind.buildStaticLib(comp, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .libunwind, @@ -3307,7 +3299,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("libcxx"); defer named_frame.end(); - libcxx.buildLibCXX(comp) catch |err| { + libcxx.buildLibCXX(comp, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .libcxx, @@ -3320,7 +3312,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("libcxxabi"); defer named_frame.end(); - libcxx.buildLibCXXABI(comp) catch |err| { + libcxx.buildLibCXXABI(comp, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .libcxxabi, @@ -3333,7 +3325,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("libtsan"); defer named_frame.end(); - libtsan.buildTsan(comp) catch |err| { + libtsan.buildTsan(comp, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .libtsan, @@ -3346,7 +3338,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const named_frame = tracy.namedFrame("wasi_libc_crt_file"); defer named_frame.end(); - wasi_libc.buildCRTFile(comp, crt_file) catch |err| { + wasi_libc.buildCRTFile(comp, crt_file, prog_node) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( .wasi_libc_crt_file, @@ -3364,6 +3356,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { .Lib, &comp.libssp_static_lib, .libssp, + prog_node, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.SubCompilationFailed => return, // error reported already @@ -3383,6 +3376,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { .Lib, &comp.libc_static_lib, .zig_libc, + prog_node, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.SubCompilationFailed => return, // error reported already @@ -3723,8 +3717,15 @@ fn buildCompilerRtOneShot( comp: *Compilation, output_mode: std.builtin.OutputMode, out: *?CRTFile, + prog_node: *std.Progress.Node, ) void { - comp.buildOutputFromZig("compiler_rt.zig", output_mode, out, .compiler_rt) catch |err| switch (err) { + comp.buildOutputFromZig( + "compiler_rt.zig", + output_mode, + out, + .compiler_rt, + prog_node, + ) catch |err| switch (err) { error.SubCompilationFailed => return, // error reported already else => comp.lockAndSetMiscFailure( .compiler_rt, @@ -5248,8 +5249,15 @@ pub fn updateSubCompilation( parent_comp: *Compilation, sub_comp: *Compilation, misc_task: MiscTask, + prog_node: *std.Progress.Node, ) !void { - try sub_comp.update(); + { + var sub_node = prog_node.start(@tagName(misc_task), 0); + sub_node.activate(); + defer sub_node.end(); + + try sub_comp.update(prog_node); + } // Look for compilation errors in this sub compilation const gpa = parent_comp.gpa; @@ -5276,6 +5284,7 @@ fn buildOutputFromZig( output_mode: std.builtin.OutputMode, out: *?CRTFile, misc_task_tag: MiscTask, + prog_node: *std.Progress.Node, ) !void { const tracy_trace = trace(@src()); defer tracy_trace.end(); @@ -5342,7 +5351,7 @@ fn buildOutputFromZig( }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, misc_task_tag); + try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); assert(out.* == null); out.* = Compilation.CRTFile{ @@ -5358,6 +5367,7 @@ pub fn build_crt_file( root_name: []const u8, output_mode: std.builtin.OutputMode, misc_task_tag: MiscTask, + prog_node: *std.Progress.Node, c_source_files: []const Compilation.CSourceFile, ) !void { const tracy_trace = trace(@src()); @@ -5418,7 +5428,7 @@ pub fn build_crt_file( }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, misc_task_tag); + try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); @@ -5470,10 +5480,3 @@ pub fn compilerRtStrip(comp: Compilation) bool { return true; } } - -pub fn hotCodeSwap(comp: *Compilation, pid: std.os.pid_t) !void { - comp.bin_file.child_pid = pid; - try comp.makeBinFileWritable(); - try comp.update(); - try comp.makeBinFileExecutable(); -} diff --git a/src/glibc.zig b/src/glibc.zig index 530f35531a..b37398bffd 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -161,7 +161,7 @@ pub const CRTFile = enum { libc_nonshared_a, }; -pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { +pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -196,7 +196,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-DASSEMBLER", "-Wa,--noexecstack", }); - return comp.build_crt_file("crti", .Obj, .@"glibc crti.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crti", .Obj, .@"glibc crti.o", prog_node, &.{ .{ .src_path = try start_asm_path(comp, arena, "crti.S"), .cache_exempt_flags = args.items, @@ -215,7 +215,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-DASSEMBLER", "-Wa,--noexecstack", }); - return comp.build_crt_file("crtn", .Obj, .@"glibc crtn.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crtn", .Obj, .@"glibc crtn.o", prog_node, &.{ .{ .src_path = try start_asm_path(comp, arena, "crtn.S"), .cache_exempt_flags = args.items, @@ -265,7 +265,9 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .cache_exempt_flags = args.items, }; }; - return comp.build_crt_file("Scrt1", .Obj, .@"glibc Scrt1.o", &[_]Compilation.CSourceFile{ start_o, abi_note_o }); + return comp.build_crt_file("Scrt1", .Obj, .@"glibc Scrt1.o", prog_node, &.{ + start_o, abi_note_o, + }); }, .libc_nonshared_a => { const s = path.sep_str; @@ -366,7 +368,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { files_index += 1; } const files = files_buf[0..files_index]; - return comp.build_crt_file("c_nonshared", .Lib, .@"glibc libc_nonshared.a", files); + return comp.build_crt_file("c_nonshared", .Lib, .@"glibc libc_nonshared.a", prog_node, files); }, } } @@ -639,7 +641,7 @@ pub const BuiltSharedObjects = struct { const all_map_basename = "all.map"; -pub fn buildSharedObjects(comp: *Compilation) !void { +pub fn buildSharedObjects(comp: *Compilation, prog_node: *std.Progress.Node) !void { const tracy = trace(@src()); defer tracy.end(); @@ -1023,7 +1025,7 @@ pub fn buildSharedObjects(comp: *Compilation) !void { const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable; try o_directory.handle.writeFile(asm_file_basename, stubs_asm.items); - try buildSharedLib(comp, arena, comp.global_cache_directory, o_directory, asm_file_basename, lib); + try buildSharedLib(comp, arena, comp.global_cache_directory, o_directory, asm_file_basename, lib, prog_node); } man.writeManifest() catch |err| { @@ -1046,6 +1048,7 @@ fn buildSharedLib( bin_directory: Compilation.Directory, asm_file_basename: []const u8, lib: Lib, + prog_node: *std.Progress.Node, ) !void { const tracy = trace(@src()); defer tracy.end(); @@ -1105,7 +1108,7 @@ fn buildSharedLib( }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, .@"glibc shared object"); + try comp.updateSubCompilation(sub_compilation, .@"glibc shared object", prog_node); } // Return true if glibc has crti/crtn sources for that architecture. diff --git a/src/libcxx.zig b/src/libcxx.zig index c17352c562..9c5dc9426f 100644 --- a/src/libcxx.zig +++ b/src/libcxx.zig @@ -96,7 +96,7 @@ const libcxx_files = [_][]const u8{ "src/verbose_abort.cpp", }; -pub fn buildLibCXX(comp: *Compilation) !void { +pub fn buildLibCXX(comp: *Compilation, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -258,7 +258,7 @@ pub fn buildLibCXX(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, .libcxx); + try comp.updateSubCompilation(sub_compilation, .libcxx, prog_node); assert(comp.libcxx_static_lib == null); comp.libcxx_static_lib = Compilation.CRTFile{ @@ -269,7 +269,7 @@ pub fn buildLibCXX(comp: *Compilation) !void { }; } -pub fn buildLibCXXABI(comp: *Compilation) !void { +pub fn buildLibCXXABI(comp: *Compilation, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -418,7 +418,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, .libcxxabi); + try comp.updateSubCompilation(sub_compilation, .libcxxabi, prog_node); assert(comp.libcxxabi_static_lib == null); comp.libcxxabi_static_lib = Compilation.CRTFile{ diff --git a/src/libtsan.zig b/src/libtsan.zig index 1399b6b76c..54bf00e4b6 100644 --- a/src/libtsan.zig +++ b/src/libtsan.zig @@ -5,7 +5,7 @@ const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); const trace = @import("tracy.zig").trace; -pub fn buildTsan(comp: *Compilation) !void { +pub fn buildTsan(comp: *Compilation, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -235,7 +235,7 @@ pub fn buildTsan(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, .libtsan); + try comp.updateSubCompilation(sub_compilation, .libtsan, prog_node); assert(comp.tsan_static_lib == null); comp.tsan_static_lib = Compilation.CRTFile{ diff --git a/src/libunwind.zig b/src/libunwind.zig index 667195a369..aefbfb457d 100644 --- a/src/libunwind.zig +++ b/src/libunwind.zig @@ -7,7 +7,7 @@ const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); const trace = @import("tracy.zig").trace; -pub fn buildStaticLib(comp: *Compilation) !void { +pub fn buildStaticLib(comp: *Compilation, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -130,7 +130,7 @@ pub fn buildStaticLib(comp: *Compilation) !void { }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, .libunwind); + try comp.updateSubCompilation(sub_compilation, .libunwind, prog_node); assert(comp.libunwind_static_lib == null); diff --git a/src/main.zig b/src/main.zig index d574681bcd..495cc40ed5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3543,6 +3543,23 @@ fn serve( var receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(gpa); defer receive_fifo.deinit(); + var progress: std.Progress = .{ + .terminal = null, + .root = .{ + .context = undefined, + .parent = null, + .name = "", + .unprotected_estimated_total_items = 0, + .unprotected_completed_items = 0, + }, + .columns_written = 0, + .prev_refresh_timestamp = 0, + .timer = null, + .done = false, + }; + const main_progress_node = &progress.root; + main_progress_node.context = &progress; + while (true) { const hdr = try receiveMessage(in, &receive_fifo); @@ -3551,11 +3568,12 @@ fn serve( return cleanExit(); }, .update => { + assert(main_progress_node.recently_updated_child == null); tracy.frameMark(); if (comp.bin_file.options.output_mode == .Exe) { try comp.makeBinFileWritable(); } - try comp.update(); + try comp.update(main_progress_node); try comp.makeBinFileExecutable(); try serveUpdateResults(out, comp); }, @@ -3581,14 +3599,15 @@ fn serve( }, .hot_update => { tracy.frameMark(); + assert(main_progress_node.recently_updated_child == null); if (child_pid) |pid| { - try comp.hotCodeSwap(pid); + try comp.hotCodeSwap(main_progress_node, pid); try serveUpdateResults(out, comp); } else { if (comp.bin_file.options.output_mode == .Exe) { try comp.makeBinFileWritable(); } - try comp.update(); + try comp.update(main_progress_node); try comp.makeBinFileExecutable(); try serveUpdateResults(out, comp); @@ -3936,7 +3955,24 @@ const AfterUpdateHook = union(enum) { }; fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void { - try comp.update(); + { + // If the terminal is dumb, we dont want to show the user all the output. + var progress: std.Progress = .{ .dont_print_on_dumb = true }; + const main_progress_node = progress.start("", 0); + defer main_progress_node.end(); + switch (comp.color) { + .off => { + progress.terminal = null; + }, + .on => { + progress.terminal = std.io.getStdErr(); + progress.supports_ansi_escape_codes = true; + }, + .auto => {}, + } + + try comp.update(main_progress_node); + } var errors = try comp.getAllErrorsAlloc(); defer errors.deinit(comp.gpa); @@ -4642,6 +4678,10 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi switch (term) { .Exited => |code| { if (code == 0) return cleanExit(); + // Indicates that the build runner has reported compile errors + // and this parent process does not need to report any further + // diagnostics. + if (code == 2) process.exit(2); const cmd = try std.mem.join(arena, " ", child_argv); fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); diff --git a/src/mingw.zig b/src/mingw.zig index d5c42c3ccc..a85645e80b 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -19,7 +19,7 @@ pub const CRTFile = enum { uuid_lib, }; -pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { +pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -41,7 +41,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { //"-D_UNICODE", //"-DWPRFLAG=1", }); - return comp.build_crt_file("crt2", .Obj, .@"mingw-w64 crt2.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt2", .Obj, .@"mingw-w64 crt2.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "mingw", "crt", "crtexe.c", @@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-U__CRTDLL__", "-D__MSVCRT__", }); - return comp.build_crt_file("dllcrt2", .Obj, .@"mingw-w64 dllcrt2.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("dllcrt2", .Obj, .@"mingw-w64 dllcrt2.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "mingw", "crt", "crtdll.c", @@ -100,7 +100,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }; } - return comp.build_crt_file("mingw32", .Lib, .@"mingw-w64 mingw32.lib", &c_source_files); + return comp.build_crt_file("mingw32", .Lib, .@"mingw-w64 mingw32.lib", prog_node, &c_source_files); }, .msvcrt_os_lib => { @@ -148,7 +148,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { }; } } - return comp.build_crt_file("msvcrt-os", .Lib, .@"mingw-w64 msvcrt-os.lib", c_source_files.items); + return comp.build_crt_file("msvcrt-os", .Lib, .@"mingw-w64 msvcrt-os.lib", prog_node, c_source_files.items); }, .mingwex_lib => { @@ -211,7 +211,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { } else { @panic("unsupported arch"); } - return comp.build_crt_file("mingwex", .Lib, .@"mingw-w64 mingwex.lib", c_source_files.items); + return comp.build_crt_file("mingwex", .Lib, .@"mingw-w64 mingwex.lib", prog_node, c_source_files.items); }, .uuid_lib => { @@ -244,7 +244,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = extra_flags, }; } - return comp.build_crt_file("uuid", .Lib, .@"mingw-w64 uuid.lib", &c_source_files); + return comp.build_crt_file("uuid", .Lib, .@"mingw-w64 uuid.lib", prog_node, &c_source_files); }, } } diff --git a/src/musl.zig b/src/musl.zig index 7dd224604f..4a3f1e6dde 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -17,7 +17,7 @@ pub const CRTFile = enum { libc_so, }; -pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { +pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -33,7 +33,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { try args.appendSlice(&[_][]const u8{ "-Qunused-arguments", }); - return comp.build_crt_file("crti", .Obj, .@"musl crti.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crti", .Obj, .@"musl crti.o", prog_node, &.{ .{ .src_path = try start_asm_path(comp, arena, "crti.s"), .extra_flags = args.items, @@ -46,7 +46,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { try args.appendSlice(&[_][]const u8{ "-Qunused-arguments", }); - return comp.build_crt_file("crtn", .Obj, .@"musl crtn.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crtn", .Obj, .@"musl crtn.o", prog_node, &.{ .{ .src_path = try start_asm_path(comp, arena, "crtn.s"), .extra_flags = args.items, @@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-fno-stack-protector", "-DCRT", }); - return comp.build_crt_file("crt1", .Obj, .@"musl crt1.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt1", .Obj, .@"musl crt1.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "musl", "crt", "crt1.c", @@ -77,7 +77,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-fno-stack-protector", "-DCRT", }); - return comp.build_crt_file("rcrt1", .Obj, .@"musl rcrt1.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("rcrt1", .Obj, .@"musl rcrt1.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "musl", "crt", "rcrt1.c", @@ -94,7 +94,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { "-fno-stack-protector", "-DCRT", }); - return comp.build_crt_file("Scrt1", .Obj, .@"musl Scrt1.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("Scrt1", .Obj, .@"musl Scrt1.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", "musl", "crt", "Scrt1.c", @@ -187,7 +187,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }; } - return comp.build_crt_file("c", .Lib, .@"musl libc.a", c_source_files.items); + return comp.build_crt_file("c", .Lib, .@"musl libc.a", prog_node, c_source_files.items); }, .libc_so => { const target = comp.getTarget(); @@ -241,7 +241,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { }); defer sub_compilation.destroy(); - try comp.updateSubCompilation(sub_compilation, .@"musl libc.so"); + try comp.updateSubCompilation(sub_compilation, .@"musl libc.so", prog_node); try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); diff --git a/src/wasi_libc.zig b/src/wasi_libc.zig index c4a4cbc4a5..38a4f17190 100644 --- a/src/wasi_libc.zig +++ b/src/wasi_libc.zig @@ -59,7 +59,7 @@ pub fn execModelCrtFileFullName(wasi_exec_model: std.builtin.WasiExecModel) []co }; } -pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { +pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } @@ -74,7 +74,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var args = std.ArrayList([]const u8).init(arena); try addCCArgs(comp, arena, &args, false); try addLibcBottomHalfIncludes(comp, arena, &args); - return comp.build_crt_file("crt1-reactor", .Obj, .@"wasi crt1-reactor.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt1-reactor", .Obj, .@"wasi crt1-reactor.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", try sanitize(arena, crt1_reactor_src_file), @@ -87,7 +87,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var args = std.ArrayList([]const u8).init(arena); try addCCArgs(comp, arena, &args, false); try addLibcBottomHalfIncludes(comp, arena, &args); - return comp.build_crt_file("crt1-command", .Obj, .@"wasi crt1-command.o", &[1]Compilation.CSourceFile{ + return comp.build_crt_file("crt1-command", .Obj, .@"wasi crt1-command.o", prog_node, &.{ .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", try sanitize(arena, crt1_command_src_file), @@ -145,7 +145,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { } } - try comp.build_crt_file("c", .Lib, .@"wasi libc.a", libc_sources.items); + try comp.build_crt_file("c", .Lib, .@"wasi libc.a", prog_node, libc_sources.items); }, .libwasi_emulated_process_clocks_a => { var args = std.ArrayList([]const u8).init(arena); @@ -161,7 +161,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }); } - try comp.build_crt_file("wasi-emulated-process-clocks", .Lib, .@"libwasi-emulated-process-clocks.a", emu_clocks_sources.items); + try comp.build_crt_file("wasi-emulated-process-clocks", .Lib, .@"libwasi-emulated-process-clocks.a", prog_node, emu_clocks_sources.items); }, .libwasi_emulated_getpid_a => { var args = std.ArrayList([]const u8).init(arena); @@ -177,7 +177,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }); } - try comp.build_crt_file("wasi-emulated-getpid", .Lib, .@"libwasi-emulated-getpid.a", emu_getpid_sources.items); + try comp.build_crt_file("wasi-emulated-getpid", .Lib, .@"libwasi-emulated-getpid.a", prog_node, emu_getpid_sources.items); }, .libwasi_emulated_mman_a => { var args = std.ArrayList([]const u8).init(arena); @@ -193,7 +193,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .extra_flags = args.items, }); } - try comp.build_crt_file("wasi-emulated-mman", .Lib, .@"libwasi-emulated-mman.a", emu_mman_sources.items); + try comp.build_crt_file("wasi-emulated-mman", .Lib, .@"libwasi-emulated-mman.a", prog_node, emu_mman_sources.items); }, .libwasi_emulated_signal_a => { var emu_signal_sources = std.ArrayList(Compilation.CSourceFile).init(arena); @@ -228,7 +228,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { } } - try comp.build_crt_file("wasi-emulated-signal", .Lib, .@"libwasi-emulated-signal.a", emu_signal_sources.items); + try comp.build_crt_file("wasi-emulated-signal", .Lib, .@"libwasi-emulated-signal.a", prog_node, emu_signal_sources.items); }, } } From 81376e72057026464e1c17330666d4fb3c5a7ce0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 21:57:59 -0700 Subject: [PATCH 168/294] fix UAF in build runner --- lib/build_runner.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index d70dbf2919..9a8f138979 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -309,7 +309,7 @@ fn runStepNames( } } - const starting_steps = step_stack.items; + const starting_steps = try b.allocator.dupe(*Step, step_stack.items); for (starting_steps) |s| { checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) { error.DependencyLoopDetected => return error.UncleanExit, From 0e078790feaf49964d7a0da3042117ebd10de13b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Feb 2023 23:58:13 -0700 Subject: [PATCH 169/294] multiplex compiler progress messages into the build runner --- lib/build_runner.zig | 2 +- lib/std/Build.zig | 16 ++++-- lib/std/Build/CheckFileStep.zig | 3 +- lib/std/Build/CheckObjectStep.zig | 3 +- lib/std/Build/CompileStep.zig | 6 +-- lib/std/Build/ConfigHeaderStep.zig | 3 +- lib/std/Build/EmulatableRunStep.zig | 3 +- lib/std/Build/FmtStep.zig | 3 +- lib/std/Build/InstallArtifactStep.zig | 3 +- lib/std/Build/InstallDirStep.zig | 3 +- lib/std/Build/InstallFileStep.zig | 3 +- lib/std/Build/LogStep.zig | 3 +- lib/std/Build/ObjCopyStep.zig | 3 +- lib/std/Build/OptionsStep.zig | 3 +- lib/std/Build/RemoveDirStep.zig | 3 +- lib/std/Build/RunStep.zig | 3 +- lib/std/Build/Step.zig | 13 +++-- lib/std/Build/TranslateCStep.zig | 4 +- lib/std/Build/WriteFileStep.zig | 3 +- src/main.zig | 73 ++++++++++++++++++++++++++- test/tests.zig | 6 ++- 21 files changed, 131 insertions(+), 31 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 9a8f138979..ea4703bfbb 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -571,7 +571,7 @@ fn workerMakeOneStep( // For example, CompileStep does some sus things with modifying the saved // *Build object in install header steps that might be able to be removed // by passing the *Build object through the make() functions. - const make_result = s.make(); + const make_result = s.make(&sub_prog_node); // No matter the result, we want to display error/warning messages. if (s.result_error_msgs.items.len > 0) { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index a9d7c31733..ca7ddb591c 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -718,7 +718,8 @@ pub fn getUninstallStep(self: *Build) *Step { return &self.uninstall_tls.step; } -fn makeUninstall(uninstall_step: *Step) anyerror!void { +fn makeUninstall(uninstall_step: *Step, prog_node: *std.Progress.Node) anyerror!void { + _ = prog_node; const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step); const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls); @@ -1404,7 +1405,7 @@ pub fn execAllowFail( /// This function is used exclusively for spawning and communicating with the zig compiler. /// TODO: move to build_runner.zig -pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]const u8 { +pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step, prog_node: *std.Progress.Node) ![]const u8 { assert(argv.len != 0); if (b.verbose) { @@ -1439,6 +1440,11 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]const u8 { const Header = std.zig.Server.Message.Header; var result: ?[]const u8 = null; + var node_name: std.ArrayListUnmanaged(u8) = .{}; + defer node_name.deinit(b.allocator); + var sub_prog_node: ?std.Progress.Node = null; + defer if (sub_prog_node) |*n| n.end(); + while (try poller.poll()) { const stdout = poller.fifo(.stdout); const buf = stdout.readableSlice(0); @@ -1478,7 +1484,11 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step) ![]const u8 { }; }, .progress => { - @panic("TODO handle progress message"); + if (sub_prog_node) |*n| n.end(); + node_name.clearRetainingCapacity(); + try node_name.appendSlice(b.allocator, body); + sub_prog_node = prog_node.start(node_name.items, 0); + sub_prog_node.?.activate(); }, .emit_bin_path => { result = try b.allocator.dupe(u8, body); diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index 5b2e5b4e5b..9fee947386 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -33,7 +33,8 @@ pub fn create( return self; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(CheckFileStep, "step", step); const src_path = self.source.getPath(self.builder); diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig index 1775a5d39a..eccbbd1696 100644 --- a/lib/std/Build/CheckObjectStep.zig +++ b/lib/std/Build/CheckObjectStep.zig @@ -300,7 +300,8 @@ pub fn checkComputeCompare( self.checks.append(new_check) catch @panic("OOM"); } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(CheckObjectStep, "step", step); const gpa = self.builder.allocator; diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 491c8ac8e4..711092c762 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1160,7 +1160,7 @@ fn constructDepString( } } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { const self = @fieldParentPtr(CompileStep, "step", step); const builder = self.builder; @@ -1718,7 +1718,7 @@ fn make(step: *Step) !void { } if (other.installed_headers.items.len > 0) { for (other.installed_headers.items) |install_step| { - try install_step.make(); + try install_step.make(prog_node); } try zig_args.append("-I"); try zig_args.append(builder.pathJoin(&.{ @@ -1894,7 +1894,7 @@ fn make(step: *Step) !void { try zig_args.append(resolved_args_file); } - const output_bin_path = try builder.execFromStep(zig_args.items, &self.step); + const output_bin_path = try builder.execFromStep(zig_args.items, &self.step, prog_node); const build_output_dir = fs.path.dirname(output_bin_path).?; if (self.output_dir) |output_dir| { diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index fb8d92e4e2..c74c03718e 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -152,7 +152,8 @@ fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v } } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(ConfigHeaderStep, "step", step); const gpa = self.builder.allocator; diff --git a/lib/std/Build/EmulatableRunStep.zig b/lib/std/Build/EmulatableRunStep.zig index 6556f4b8c0..44387c36f6 100644 --- a/lib/std/Build/EmulatableRunStep.zig +++ b/lib/std/Build/EmulatableRunStep.zig @@ -71,7 +71,8 @@ pub fn create(builder: *std.Build, name: []const u8, artifact: *CompileStep) *Em return self; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(EmulatableRunStep, "step", step); const host_info = self.builder.host; diff --git a/lib/std/Build/FmtStep.zig b/lib/std/Build/FmtStep.zig index 04b8ccb3e4..5efada7507 100644 --- a/lib/std/Build/FmtStep.zig +++ b/lib/std/Build/FmtStep.zig @@ -29,7 +29,8 @@ pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep { return self; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(FmtStep, "step", step); return self.builder.spawnChild(self.argv); diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index b652ade38f..d8907eb59f 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -64,7 +64,8 @@ pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep return self; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(InstallArtifactStep, "step", step); const builder = self.builder; diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig index 54a37af1d4..bf89d9e7c7 100644 --- a/lib/std/Build/InstallDirStep.zig +++ b/lib/std/Build/InstallDirStep.zig @@ -56,7 +56,8 @@ pub fn init( }; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(InstallDirStep, "step", step); const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir); const src_builder = self.override_source_builder orelse self.builder; diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig index e6c57a8050..f77b22c112 100644 --- a/lib/std/Build/InstallFileStep.zig +++ b/lib/std/Build/InstallFileStep.zig @@ -35,7 +35,8 @@ pub fn init( }; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(InstallFileStep, "step", step); const src_builder = self.override_source_builder orelse self.builder; const full_src_path = self.source.getPath2(src_builder, step); diff --git a/lib/std/Build/LogStep.zig b/lib/std/Build/LogStep.zig index 008ed6e8bb..25bba747bf 100644 --- a/lib/std/Build/LogStep.zig +++ b/lib/std/Build/LogStep.zig @@ -21,7 +21,8 @@ pub fn init(builder: *std.Build, data: []const u8) LogStep { }; } -fn make(step: *Step) anyerror!void { +fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void { + _ = prog_node; const self = @fieldParentPtr(LogStep, "step", step); log.info("{s}", .{self.data}); } diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index 549bb1ed00..839d95903c 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -66,7 +66,8 @@ pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource { return .{ .generated = &self.output_file }; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(ObjCopyStep, "step", step); const b = self.builder; diff --git a/lib/std/Build/OptionsStep.zig b/lib/std/Build/OptionsStep.zig index 8e86578e30..2cb3bb13be 100644 --- a/lib/std/Build/OptionsStep.zig +++ b/lib/std/Build/OptionsStep.zig @@ -219,7 +219,8 @@ pub fn getSource(self: *OptionsStep) FileSource { return .{ .generated = &self.generated_file }; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(OptionsStep, "step", step); for (self.artifact_args.items) |item| { diff --git a/lib/std/Build/RemoveDirStep.zig b/lib/std/Build/RemoveDirStep.zig index acde60a745..4fc8e6d338 100644 --- a/lib/std/Build/RemoveDirStep.zig +++ b/lib/std/Build/RemoveDirStep.zig @@ -22,7 +22,8 @@ pub fn init(builder: *std.Build, dir_path: []const u8) RemoveDirStep { }; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(RemoveDirStep, "step", step); const full_path = self.builder.pathFromRoot(self.dir_path); diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 904ef0935f..7ec60b6d22 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -206,7 +206,8 @@ fn needOutputCheck(self: RunStep) bool { return false; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(RunStep, "step", step); const need_output_check = self.needOutputCheck(); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 4edece8038..53861683ac 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -1,6 +1,6 @@ id: Id, name: []const u8, -makeFn: *const fn (self: *Step) anyerror!void, +makeFn: MakeFn, dependencies: std.ArrayList(*Step), /// This field is empty during execution of the user's build script, and /// then populated during dependency loop checking in the build runner. @@ -13,6 +13,8 @@ debug_stack_trace: [n_debug_stack_frames]usize, result_error_msgs: std.ArrayListUnmanaged([]const u8), result_error_bundle: std.zig.ErrorBundle, +pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; + const n_debug_stack_frames = 4; pub const State = enum { @@ -72,7 +74,7 @@ pub const Id = enum { pub const Options = struct { id: Id, name: []const u8, - makeFn: *const fn (self: *Step) anyerror!void = makeNoOp, + makeFn: MakeFn = makeNoOp, first_ret_addr: ?usize = null, }; @@ -101,8 +103,8 @@ pub fn init(allocator: Allocator, options: Options) Step { /// If the Step's `make` function reports `error.MakeFailed`, it indicates they /// have already reported the error. Otherwise, we add a simple error report /// here. -pub fn make(s: *Step) error{MakeFailed}!void { - return s.makeFn(s) catch |err| { +pub fn make(s: *Step, prog_node: *std.Progress.Node) error{MakeFailed}!void { + return s.makeFn(s, prog_node) catch |err| { if (err != error.MakeFailed) { const gpa = s.dependencies.allocator; s.result_error_msgs.append(gpa, std.fmt.allocPrint(gpa, "{s} failed: {s}", .{ @@ -129,8 +131,9 @@ pub fn getStackTrace(s: *Step) std.builtin.StackTrace { }; } -fn makeNoOp(self: *Step) anyerror!void { +fn makeNoOp(self: *Step, prog_node: *std.Progress.Node) anyerror!void { _ = self; + _ = prog_node; } pub fn cast(step: *Step, comptime T: type) ?*T { diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig index 8c3a254d6a..d19e598d93 100644 --- a/lib/std/Build/TranslateCStep.zig +++ b/lib/std/Build/TranslateCStep.zig @@ -88,7 +88,7 @@ pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void { self.c_macros.append(self.builder.dupe(name_and_value)) catch @panic("OOM"); } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { const self = @fieldParentPtr(TranslateCStep, "step", step); var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); @@ -120,7 +120,7 @@ fn make(step: *Step) !void { try argv_list.append(self.source.getPath(self.builder)); - const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step); + const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step, prog_node); const output_path = mem.trimRight(u8, output_path_nl, "\r\n"); self.out_basename = fs.path.basename(output_path); diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 4f5e768237..62acd6e8ee 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -99,7 +99,8 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo return null; } -fn make(step: *Step) !void { +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const wf = @fieldParentPtr(WriteFileStep, "step", step); // Writing to source files is kind of an extra capability of this diff --git a/src/main.zig b/src/main.zig index 495cc40ed5..1d5cf57388 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3573,7 +3573,21 @@ fn serve( if (comp.bin_file.options.output_mode == .Exe) { try comp.makeBinFileWritable(); } - try comp.update(main_progress_node); + + { + var reset: std.Thread.ResetEvent = .{}; + + var progress_thread = try std.Thread.spawn(.{}, progressThread, .{ + &progress, out, &reset, + }); + defer { + reset.set(); + progress_thread.join(); + } + + try comp.update(main_progress_node); + } + try comp.makeBinFileExecutable(); try serveUpdateResults(out, comp); }, @@ -3629,6 +3643,63 @@ fn serve( } } +fn progressThread(progress: *std.Progress, out: fs.File, reset: *std.Thread.ResetEvent) void { + while (true) { + if (reset.timedWait(500 * std.time.ns_per_ms)) |_| { + // The Compilation update has completed. + return; + } else |err| switch (err) { + error.Timeout => {}, + } + + var buf: std.BoundedArray(u8, 160) = .{}; + + { + progress.update_mutex.lock(); + defer progress.update_mutex.unlock(); + + var need_ellipse = false; + var maybe_node: ?*std.Progress.Node = &progress.root; + while (maybe_node) |node| { + if (need_ellipse) { + buf.appendSlice("... ") catch {}; + } + need_ellipse = false; + const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic); + const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic); + const current_item = completed_items + 1; + if (node.name.len != 0 or eti > 0) { + if (node.name.len != 0) { + buf.appendSlice(node.name) catch {}; + need_ellipse = true; + } + if (eti > 0) { + if (need_ellipse) buf.appendSlice(" ") catch {}; + buf.writer().print("[{d}/{d}] ", .{ current_item, eti }) catch {}; + need_ellipse = false; + } else if (completed_items != 0) { + if (need_ellipse) buf.appendSlice(" ") catch {}; + buf.writer().print("[{d}] ", .{current_item}) catch {}; + need_ellipse = false; + } + } + maybe_node = @atomicLoad(?*std.Progress.Node, &node.recently_updated_child, .Acquire); + } + } + + const progress_string = buf.slice(); + + serveMessage(out, .{ + .tag = .progress, + .bytes_len = @intCast(u32, progress_string.len), + }, &.{ + progress_string, + }) catch |err| { + fatal("unable to write to client: {s}", .{@errorName(err)}); + }; + } +} + fn serveMessage( out: fs.File, header: std.zig.Server.Message.Header, diff --git a/test/tests.zig b/test/tests.zig index fe2efbc06e..fceaa173d1 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -875,7 +875,8 @@ pub const StackTracesContext = struct { return ptr; } - fn make(step: *Step) !void { + fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(RunAndCompareStep, "step", step); const b = self.context.b; @@ -1218,7 +1219,8 @@ pub const GenHContext = struct { return ptr; } - fn make(step: *Step) !void { + fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); const b = self.context.b; From 8b2d872020bf8139404d0655e5ab70792cc67873 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 00:34:30 -0700 Subject: [PATCH 170/294] fix std.Build.TranslateCStep --- lib/std/Build.zig | 2 +- lib/std/Build/TranslateCStep.zig | 4 +-- src/main.zig | 51 ++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index ca7ddb591c..05decec36f 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1453,7 +1453,7 @@ pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step, prog_node: *s const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); const header_and_msg_len = header.bytes_len + @sizeOf(Header); if (buf.len >= header_and_msg_len) { - const body = buf[@sizeOf(Header)..]; + const body = buf[@sizeOf(Header)..][0..header.bytes_len]; switch (header.tag) { .zig_version => { if (!mem.eql(u8, builtin.zig_version_string, body)) { diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig index d19e598d93..fef644f03c 100644 --- a/lib/std/Build/TranslateCStep.zig +++ b/lib/std/Build/TranslateCStep.zig @@ -97,6 +97,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try argv_list.append("-lc"); try argv_list.append("--enable-cache"); + try argv_list.append("--listen=-"); if (!self.target.isNative()) { try argv_list.append("-target"); @@ -120,8 +121,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try argv_list.append(self.source.getPath(self.builder)); - const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step, prog_node); - const output_path = mem.trimRight(u8, output_path_nl, "\r\n"); + const output_path = try self.builder.execFromStep(argv_list.items, &self.step, prog_node); self.out_basename = fs.path.basename(output_path); const output_dir = fs.path.dirname(output_path).?; diff --git a/src/main.zig b/src/main.zig index 1d5cf57388..630f454272 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3287,10 +3287,6 @@ fn buildOutputType( if (show_builtin) { return std.io.getStdOut().writeAll(try comp.generateBuiltinZigSource(arena)); } - if (arg_mode == .translate_c) { - return cmdTranslateC(comp, arena, have_enable_cache); - } - switch (listen) { .none => {}, .stdio => { @@ -3332,6 +3328,10 @@ fn buildOutputType( }, } + if (arg_mode == .translate_c) { + return cmdTranslateC(comp, arena, null); + } + const hook: AfterUpdateHook = blk: { if (!have_enable_cache) break :blk .none; @@ -3532,12 +3532,7 @@ fn serve( ) !void { const gpa = comp.gpa; - try serveMessage(out, .{ - .tag = .zig_version, - .bytes_len = build_options.version.len, - }, &.{ - build_options.version, - }); + try serveStringMessage(out, .zig_version, build_options.version); var child_pid: ?i32 = null; var receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(gpa); @@ -3570,6 +3565,17 @@ fn serve( .update => { assert(main_progress_node.recently_updated_child == null); tracy.frameMark(); + + if (arg_mode == .translate_c) { + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + var output_path: []const u8 = undefined; + try cmdTranslateC(comp, arena, &output_path); + try serveStringMessage(out, .emit_bin_path, output_path); + continue; + } + if (comp.bin_file.options.output_mode == .Exe) { try comp.makeBinFileWritable(); } @@ -3746,16 +3752,17 @@ fn serveUpdateResults(out: fs.File, comp: *Compilation) !void { } else if (comp.bin_file.options.emit) |emit| { const full_path = try emit.directory.join(gpa, &.{emit.sub_path}); defer gpa.free(full_path); - - try serveMessage(out, .{ - .tag = .emit_bin_path, - .bytes_len = @intCast(u32, full_path.len), - }, &.{ - full_path, - }); + try serveStringMessage(out, .emit_bin_path, full_path); } } +fn serveStringMessage(out: fs.File, tag: std.zig.Server.Message.Tag, s: []const u8) !void { + try serveMessage(out, .{ + .tag = tag, + .bytes_len = @intCast(u32, s.len), + }, &.{s}); +} + fn receiveMessage(in: fs.File, fifo: *std.fifo.LinearFifo(u8, .Dynamic)) !std.zig.Client.Message.Header { const Header = std.zig.Client.Message.Header; @@ -4100,7 +4107,7 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void } } -fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void { +fn cmdTranslateC(comp: *Compilation, arena: Allocator, output_path: ?*[]const u8) !void { if (!build_options.have_llvm) fatal("cannot translate-c: compiler built without LLVM extensions", .{}); @@ -4111,7 +4118,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void var man: Cache.Manifest = comp.obtainCObjectCacheManifest(); man.want_shared_lock = false; - defer if (enable_cache) man.deinit(); + defer if (output_path != null) man.deinit(); man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| { @@ -4169,6 +4176,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void error.OutOfMemory => return error.OutOfMemory, error.ASTUnitFailure => fatal("clang API returned errors but due to a clang bug, it is not exposing the errors for zig to see. For more details: https://github.com/ziglang/zig/issues/4455", .{}), error.SemanticAnalyzeFail => { + // TODO convert these to zig errors for (clang_errors) |clang_err| { std.debug.print("{s}:{d}:{d}: {s}\n", .{ if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", @@ -4213,12 +4221,11 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void break :digest digest; }; - if (enable_cache) { + if (output_path) |out_path| { const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename, }); - try io.getStdOut().writer().print("{s}\n", .{full_zig_path}); - return cleanExit(); + out_path.* = full_zig_path; } else { const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename }); const zig_file = comp.local_cache_directory.handle.openFile(out_zig_path, .{}) catch |err| { From 01299864e2fc87aa597815743a4d8552e8a0129e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 13:00:04 -0700 Subject: [PATCH 171/294] build runner: fix unicode tree printing --- lib/build_runner.zig | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index ea4703bfbb..c2509d0d22 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -424,6 +424,17 @@ const PrintNode = struct { last: bool = false, }; +fn printPrefix(node: *PrintNode, stderr: std.fs.File) !void { + const parent = node.parent orelse return; + if (parent.parent == null) return; + try printPrefix(parent, stderr); + if (parent.last) { + try stderr.writeAll(" "); + } else { + try stderr.writeAll("│ "); + } +} + fn printTreeStep( b: *std.Build, s: *Step, @@ -431,15 +442,7 @@ fn printTreeStep( ttyconf: std.debug.TTY.Config, parent_node: *PrintNode, ) !void { - var opt_node: ?*PrintNode = parent_node.parent; - while (opt_node) |n| : (opt_node = n.parent) { - if (n.parent == null) break; - if (n.last) { - try stderr.writeAll(" "); - } else { - try stderr.writeAll("│ "); - } - } + try printPrefix(parent_node, stderr); if (parent_node.parent != null) { if (parent_node.last) { From e895d582143f74c9e3a2d06083017e74ad67b770 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 13:08:34 -0700 Subject: [PATCH 172/294] build system: give RunStep a better default step name --- lib/std/Build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 05decec36f..3e765be4ba 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -618,7 +618,7 @@ pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep { // It doesn't have to be native. We catch that if you actually try to run it. // Consider that this is declarative; the run step may not be run unless a user // option is supplied. - const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.step.name})); + const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name})); run_step.addArtifactArg(exe); if (exe.kind == .test_exe) { From a7754d219a60fdc15d3bff0d6d2ac7fee9f7c4db Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 13:08:53 -0700 Subject: [PATCH 173/294] build system: better default name for ConfigHeaderStep --- lib/std/Build/ConfigHeaderStep.zig | 39 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index c74c03718e..994b71da32 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -51,10 +51,30 @@ pub const Options = struct { pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep { const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM"); + + var include_path: []const u8 = "config.h"; + + if (options.style.getFileSource()) |s| switch (s) { + .path => |p| { + const basename = std.fs.path.basename(p); + if (std.mem.endsWith(u8, basename, ".h.in")) { + include_path = basename[0 .. basename.len - 3]; + } + }, + else => {}, + }; + + if (options.include_path) |p| { + include_path = p; + } + const name = if (options.style.getFileSource()) |s| - builder.fmt("configure {s} header {s}", .{ @tagName(options.style), s.getDisplayName() }) + builder.fmt("configure {s} header {s} to {s}", .{ + @tagName(options.style), s.getDisplayName(), include_path, + }) else - builder.fmt("configure {s} header", .{@tagName(options.style)}); + builder.fmt("configure {s} header to {s}", .{@tagName(options.style), include_path}); + self.* = .{ .builder = builder, .step = Step.init(builder.allocator, .{ @@ -67,23 +87,10 @@ pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep { .values = std.StringArrayHashMap(Value).init(builder.allocator), .max_bytes = options.max_bytes, - .include_path = "config.h", + .include_path = include_path, .output_file = .{ .step = &self.step }, }; - if (options.style.getFileSource()) |s| switch (s) { - .path => |p| { - const basename = std.fs.path.basename(p); - if (std.mem.endsWith(u8, basename, ".h.in")) { - self.include_path = basename[0 .. basename.len - 3]; - } - }, - else => {}, - }; - - if (options.include_path) |include_path| { - self.include_path = include_path; - } return self; } From b5baa41077dce5ef61667e76d80835c0792a2fc4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 13:31:02 -0700 Subject: [PATCH 174/294] fix zig fmt crash --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 630f454272..fcdb52c342 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5120,8 +5120,8 @@ fn fmtPathFile( var tree = try Ast.parse(gpa, source_code, .zig); defer tree.deinit(gpa); - try printAstErrorsToStderr(gpa, tree, file_path, fmt.color); if (tree.errors.len != 0) { + try printAstErrorsToStderr(gpa, tree, file_path, fmt.color); fmt.any_error = true; return; } From 1bcf674a4390ee238bc63477d3db545dbf7f66dd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 13:44:05 -0700 Subject: [PATCH 175/294] build runner: make step_stack a map to remove redundant steps This prevents compilation errors from being emitted twice. --- lib/build_runner.zig | 52 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index c2509d0d22..35aa487484 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -22,10 +22,9 @@ pub fn main() !void { var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator(), }; - const allocator = thread_safe_arena.allocator(); + const arena = thread_safe_arena.allocator(); - var args = try process.argsAlloc(allocator); - defer process.argsFree(allocator, args); + var args = try process.argsAlloc(arena); // skip my own exe name var arg_idx: usize = 1; @@ -65,7 +64,7 @@ pub fn main() !void { }; var cache: std.Build.Cache = .{ - .gpa = allocator, + .gpa = arena, .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}), }; cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); @@ -75,7 +74,7 @@ pub fn main() !void { cache.hash.addBytes(builtin.zig_version_string); const builder = try std.Build.create( - allocator, + arena, zig_exe, build_root_directory, local_cache_directory, @@ -85,9 +84,9 @@ pub fn main() !void { ); defer builder.destroy(); - var targets = ArrayList([]const u8).init(allocator); - var debug_log_scopes = ArrayList([]const u8).init(allocator); - var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = allocator }; + var targets = ArrayList([]const u8).init(arena); + var debug_log_scopes = ArrayList([]const u8).init(arena); + var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena }; const stderr_stream = io.getStdErr().writer(); const stdout_stream = io.getStdOut().writer(); @@ -274,6 +273,7 @@ pub fn main() !void { usageAndErr(builder, true, stderr_stream); runStepNames( + arena, builder, targets.items, main_progress_node, @@ -286,30 +286,32 @@ pub fn main() !void { } fn runStepNames( + arena: std.mem.Allocator, b: *std.Build, step_names: []const []const u8, parent_prog_node: *std.Progress.Node, thread_pool_options: std.Thread.Pool.Options, ttyconf: std.debug.TTY.Config, ) !void { - var step_stack = ArrayList(*Step).init(b.allocator); - defer step_stack.deinit(); + const gpa = b.allocator; + var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{}; + defer step_stack.deinit(gpa); if (step_names.len == 0) { - try step_stack.append(b.default_step); + try step_stack.put(gpa, b.default_step, {}); } else { - try step_stack.resize(step_names.len); - - for (step_names, 0..) |step_name, i| { + try step_stack.ensureUnusedCapacity(gpa, step_names.len); + for (0..step_names.len) |i| { + const step_name = step_names[step_names.len - i - 1]; const s = b.top_level_steps.get(step_name) orelse { std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name}); process.exit(1); }; - step_stack.items[step_names.len - i - 1] = &s.step; + step_stack.putAssumeCapacity(&s.step, {}); } } - const starting_steps = try b.allocator.dupe(*Step, step_stack.items); + const starting_steps = try arena.dupe(*Step, step_stack.keys()); for (starting_steps) |s| { checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) { error.DependencyLoopDetected => return error.UncleanExit, @@ -324,7 +326,7 @@ fn runStepNames( { defer parent_prog_node.end(); - var step_prog = parent_prog_node.start("run steps", step_stack.items.len); + var step_prog = parent_prog_node.start("run steps", step_stack.count()); defer step_prog.end(); var wait_group: std.Thread.WaitGroup = .{}; @@ -333,10 +335,9 @@ fn runStepNames( // Here we spawn the initial set of tasks with a nice heuristic - // dependency order. Each worker when it finishes a step will then // check whether it should run any dependants. - var i = step_stack.items.len; - while (i > 0) { - i -= 1; - const step = step_stack.items[i]; + const steps_slice = step_stack.keys(); + for (0..steps_slice.len) |i| { + const step = steps_slice[steps_slice.len - i - 1]; wait_group.start(); thread_pool.spawn(workerMakeOneStep, .{ @@ -350,7 +351,7 @@ fn runStepNames( var pending_count: usize = 0; var total_compile_errors: usize = 0; - for (step_stack.items) |s| { + for (step_stack.keys()) |s| { switch (s.state) { .precheck_unstarted => unreachable, .precheck_started => unreachable, @@ -404,7 +405,7 @@ fn runStepNames( // Finally, render compile errors at the bottom of the terminal. if (total_compile_errors > 0) { - for (step_stack.items) |s| { + for (step_stack.keys()) |s| { if (s.result_error_bundle.errorMessageCount() > 0) { s.result_error_bundle.renderToStdErr(ttyconf); } @@ -498,7 +499,7 @@ fn printTreeStep( fn checkForDependencyLoop( b: *std.Build, s: *Step, - step_stack: *ArrayList(*Step), + step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { switch (s.state) { .precheck_started => { @@ -508,8 +509,9 @@ fn checkForDependencyLoop( .precheck_unstarted => { s.state = .precheck_started; + try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len); for (s.dependencies.items) |dep| { - try step_stack.append(dep); + try step_stack.put(b.allocator, dep, {}); try dep.dependants.append(b.allocator, s); checkForDependencyLoop(b, dep, step_stack) catch |err| { if (err == error.DependencyLoopDetected) { From 8c250828a2dbcf2428e06fbc9b2d34b17f42ed66 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 14:34:11 -0700 Subject: [PATCH 176/294] std.Build.Step: avoid redundancy in default error message --- lib/std/Build/Step.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 53861683ac..29cf38b55b 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -107,9 +107,7 @@ pub fn make(s: *Step, prog_node: *std.Progress.Node) error{MakeFailed}!void { return s.makeFn(s, prog_node) catch |err| { if (err != error.MakeFailed) { const gpa = s.dependencies.allocator; - s.result_error_msgs.append(gpa, std.fmt.allocPrint(gpa, "{s} failed: {s}", .{ - s.name, @errorName(err), - }) catch @panic("OOM")) catch @panic("OOM"); + s.result_error_msgs.append(gpa, @errorName(err)) catch @panic("OOM"); } return error.MakeFailed; }; From b4997d08902ed312d7504765014c34f4ea862534 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 14:34:40 -0700 Subject: [PATCH 177/294] std.Build.RunStep: better default step name Now it renames itself when an output argument is added. --- lib/std/Build/RunStep.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 7ec60b6d22..84fd7f975d 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -48,6 +48,11 @@ condition: enum { output_outdated, always } = .output_outdated, /// that the RunStep should be re-executed. extra_file_dependencies: []const []const u8 = &.{}, +/// After adding an output argument, this step will by default rename itself +/// for a better display name in the build summary. +/// This can be disabled by setting this to false. +rename_step_with_output_arg: bool, + pub const StdIoAction = union(enum) { inherit, ignore, @@ -80,6 +85,7 @@ pub fn create(builder: *std.Build, name: []const u8) *RunStep { .cwd = null, .env_map = null, .print = builder.verbose, + .rename_step_with_output_arg = true, }; return self; } @@ -100,6 +106,11 @@ pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource .basename = rs.builder.dupe(basename), } }) catch @panic("OOM"); + if (rs.rename_step_with_output_arg) { + rs.rename_step_with_output_arg = false; + rs.step.name = rs.builder.fmt("{s} ({s})", .{ rs.step.name, basename }); + } + return .{ .generated = generated_file }; } @@ -207,7 +218,11 @@ fn needOutputCheck(self: RunStep) bool { } fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // Unfortunately we have no way to collect progress from arbitrary programs. + // Perhaps in the future Zig could offer some kind of opt-in IPC mechanism that + // processes could use to supply progress updates. _ = prog_node; + const self = @fieldParentPtr(RunStep, "step", step); const need_output_check = self.needOutputCheck(); From 533c7b56f2624f1df6684a834f5780a38052bb00 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 14:40:14 -0700 Subject: [PATCH 178/294] build runner: hide repeated steps in the build summary --- lib/build_runner.zig | 90 ++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 35aa487484..b78ef075ab 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -350,6 +350,8 @@ fn runStepNames( var failure_count: usize = 0; var pending_count: usize = 0; var total_compile_errors: usize = 0; + var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{}; + defer compile_error_steps.deinit(gpa); for (step_stack.keys()) |s| { switch (s.state) { @@ -369,7 +371,11 @@ fn runStepNames( .success => success_count += 1, .failure => { failure_count += 1; - total_compile_errors += s.result_error_bundle.errorMessageCount(); + const compile_errors_len = s.result_error_bundle.errorMessageCount(); + if (compile_errors_len > 0) { + total_compile_errors += compile_errors_len; + try compile_error_steps.append(gpa, s); + } }, } } @@ -392,20 +398,22 @@ fn runStepNames( var print_node: PrintNode = .{ .parent = null }; if (step_names.len == 0) { print_node.last = true; - printTreeStep(b, b.default_step, stderr, ttyconf, &print_node) catch {}; + printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {}; } else { for (step_names, 0..) |step_name, i| { const tls = b.top_level_steps.get(step_name).?; print_node.last = i + 1 == b.top_level_steps.count(); - printTreeStep(b, &tls.step, stderr, ttyconf, &print_node) catch {}; + printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {}; } } if (failure_count == 0) return cleanExit(); // Finally, render compile errors at the bottom of the terminal. + // We use a separate compile_error_steps array list because step_stack is destructively + // mutated in printTreeStep above. if (total_compile_errors > 0) { - for (step_stack.keys()) |s| { + for (compile_error_steps.items) |s| { if (s.result_error_bundle.errorMessageCount() > 0) { s.result_error_bundle.renderToStdErr(ttyconf); } @@ -442,7 +450,10 @@ fn printTreeStep( stderr: std.fs.File, ttyconf: std.debug.TTY.Config, parent_node: *PrintNode, + step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { + const first = step_stack.swapRemove(s); + if (!first) try ttyconf.setColor(stderr, .Dim); try printPrefix(parent_node, stderr); if (parent_node.parent != null) { @@ -456,43 +467,48 @@ fn printTreeStep( // TODO print the dep prefix too? try stderr.writeAll(s.name); - switch (s.state) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - .running => unreachable, + if (first) { + switch (s.state) { + .precheck_unstarted => unreachable, + .precheck_started => unreachable, + .precheck_done => unreachable, + .running => unreachable, - .dependency_failure => { - try ttyconf.setColor(stderr, .Dim); - try stderr.writeAll(" transitive failure\n"); - try ttyconf.setColor(stderr, .Reset); - }, + .dependency_failure => { + try ttyconf.setColor(stderr, .Dim); + try stderr.writeAll(" transitive failure\n"); + try ttyconf.setColor(stderr, .Reset); + }, - .success => { - try ttyconf.setColor(stderr, .Green); - try stderr.writeAll(" success\n"); - try ttyconf.setColor(stderr, .Reset); - }, + .success => { + try ttyconf.setColor(stderr, .Green); + try stderr.writeAll(" success\n"); + try ttyconf.setColor(stderr, .Reset); + }, - .failure => { - try ttyconf.setColor(stderr, .Red); - if (s.result_error_bundle.errorMessageCount() > 0) { - try stderr.writer().print(" {d} errors\n", .{ - s.result_error_bundle.errorMessageCount(), - }); - } else { - try stderr.writeAll(" failure\n"); - } - try ttyconf.setColor(stderr, .Reset); - }, - } + .failure => { + try ttyconf.setColor(stderr, .Red); + if (s.result_error_bundle.errorMessageCount() > 0) { + try stderr.writer().print(" {d} errors\n", .{ + s.result_error_bundle.errorMessageCount(), + }); + } else { + try stderr.writeAll(" failure\n"); + } + try ttyconf.setColor(stderr, .Reset); + }, + } - for (s.dependencies.items, 0..) |dep, i| { - var print_node: PrintNode = .{ - .parent = parent_node, - .last = i == s.dependencies.items.len - 1, - }; - try printTreeStep(b, dep, stderr, ttyconf, &print_node); + for (s.dependencies.items, 0..) |dep, i| { + var print_node: PrintNode = .{ + .parent = parent_node, + .last = i == s.dependencies.items.len - 1, + }; + try printTreeStep(b, dep, stderr, ttyconf, &print_node, step_stack); + } + } else { + try stderr.writer().print(" ({d} repeated dependencies)\n", .{s.dependencies.items.len}); + try ttyconf.setColor(stderr, .Reset); } } From b465dc12349dd24fe060e69ad6eb290ddbb3739e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 15:13:26 -0700 Subject: [PATCH 179/294] build runner: slight rewording in build summary --- lib/build_runner.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index b78ef075ab..2c5bcdd617 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -507,7 +507,13 @@ fn printTreeStep( try printTreeStep(b, dep, stderr, ttyconf, &print_node, step_stack); } } else { - try stderr.writer().print(" ({d} repeated dependencies)\n", .{s.dependencies.items.len}); + if (s.dependencies.items.len == 0) { + try stderr.writeAll(" (reused)\n"); + } else { + try stderr.writer().print(" (+{d} more reused dependencies)\n", .{ + s.dependencies.items.len, + }); + } try ttyconf.setColor(stderr, .Reset); } } From d0f675827c28b1d50e8aea6a7d29cb45ad8d4e67 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 22:55:47 -0700 Subject: [PATCH 180/294] link: only write manifest if we have the exclusive lock Fixes assertion tripping when racing multiple processes that will create the same static archive. --- src/link.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/link.zig b/src/link.zig index a2f40eed65..96931dd79e 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1078,9 +1078,11 @@ pub const File = struct { log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)}); }; - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)}); - }; + if (man.have_exclusive_lock) { + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)}); + }; + } base.lock = man.toOwnedLock(); } From 58edefc6d1716c0731ee2fe672ec8d073651aafb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 22:56:37 -0700 Subject: [PATCH 181/294] zig build: many enhancements related to parallel building Rework std.Build.Step to have an `owner: *Build` field. This simplified the implementation of installation steps, as well as provided some much-needed common API for the new parallelized build system. --verbose is now defined very concretely: it prints to stderr just before spawning a child process. Child process execution is updated to conform to the new parallel-friendly make() function semantics. DRY up the failWithCacheError handling code. It now integrates properly with the step graph instead of incorrectly dumping to stderr and calling process exit. In the main CLI, fix `zig fmt` crash when there are no errors and stdin is used. Deleted steps: * EmulatableRunStep - this entire thing can be removed in favor of a flag added to std.Build.RunStep called `skip_foreign_checks`. * LogStep - this doesn't really fit with a multi-threaded build runner and is effectively superseded by the new build summary output. build runner: * add -fsummary and -fno-summary to override the default behavior, which is to print a summary if any of the build steps fail. * print the dep prefix when emitting error messages for steps. std.Build.FmtStep: * This step now supports exclude paths as well as a check flag. * The check flag decides between two modes, modify mode, and check mode. These can be used to update source files in place, or to fail the build, respectively. Zig's own build.zig: * The `test-fmt` step will do all the `zig fmt` checking that we expect to be done. Since the `test` step depends on this one, we can simply remove the explicit call to `zig fmt` in the CI. * The new `fmt` step will actually perform `zig fmt` and update source files in place. std.Build.RunStep: * expose max_stdio_size is a field (previously an unchangeable hard-coded value). * rework the API. Instead of configuring each stream independently, there is a `stdio` field where you can choose between `infer_from_args`, `inherit`, or `check`. These determine whether the RunStep is considered to have side-effects or not. The previous field, `condition` is gone. * when stdio mode is set to `check` there is a slice of any number of checks to make, which include things like exit code, stderr matching, or stdout matching. * remove the ill-defined `print` field. * when adding an output arg, it takes the opportunity to give itself a better name. * The flag `skip_foreign_checks` is added. If this is true, a RunStep which is configured to check the output of the executed binary will not fail the build if the binary cannot be executed due to being for a foreign binary to the host system which is running the build graph. Command-line arguments such as -fqemu and -fwasmtime may affect whether a binary is detected as foreign, as well as system configuration such as Rosetta (macOS) and binfmt_misc (Linux). - This makes EmulatableRunStep no longer needed. * Fix the child process handling to properly integrate with the new bulid API and to avoid deadlocks in stdout/stderr streams by polling if necessary. std.Build.RemoveDirStep now uses the open build_root directory handle instead of an absolute path. --- build.zig | 24 +- lib/build_runner.zig | 67 ++-- lib/std/Build.zig | 226 +----------- lib/std/Build/CheckFileStep.zig | 18 +- lib/std/Build/CheckObjectStep.zig | 37 +- lib/std/Build/CompileStep.zig | 456 ++++++++++++----------- lib/std/Build/ConfigHeaderStep.zig | 27 +- lib/std/Build/EmulatableRunStep.zig | 218 ----------- lib/std/Build/FmtStep.zig | 78 ++-- lib/std/Build/InstallArtifactStep.zig | 49 +-- lib/std/Build/InstallDirStep.zig | 34 +- lib/std/Build/InstallFileStep.zig | 29 +- lib/std/Build/LogStep.zig | 28 -- lib/std/Build/ObjCopyStep.zig | 33 +- lib/std/Build/OptionsStep.zig | 34 +- lib/std/Build/RemoveDirStep.zig | 29 +- lib/std/Build/RunStep.zig | 509 +++++++++++++++----------- lib/std/Build/Step.zig | 241 +++++++++++- lib/std/Build/TranslateCStep.zig | 40 +- lib/std/Build/WriteFileStep.zig | 63 ++-- src/main.zig | 12 +- test/src/compare_output.zig | 4 +- test/tests.zig | 16 +- 23 files changed, 1109 insertions(+), 1163 deletions(-) delete mode 100644 lib/std/Build/EmulatableRunStep.zig delete mode 100644 lib/std/Build/LogStep.zig diff --git a/build.zig b/build.zig index 189f50407d..5f7e214d35 100644 --- a/build.zig +++ b/build.zig @@ -61,8 +61,6 @@ pub fn build(b: *std.Build) !void { test_cases.stack_size = stack_size; test_cases.single_threaded = single_threaded; - const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"}); - const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false; const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; @@ -386,10 +384,24 @@ pub fn build(b: *std.Build) !void { } const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index]; - // run stage1 `zig fmt` on this build.zig file just to make sure it works - test_step.dependOn(&fmt_build_zig.step); - const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works"); - fmt_step.dependOn(&fmt_build_zig.step); + const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" }; + const fmt_exclude_paths = &.{ "test/cases" }; + const check_fmt = b.addFmt(.{ + .paths = fmt_include_paths, + .exclude_paths = fmt_exclude_paths, + .check = true, + }); + const do_fmt = b.addFmt(.{ + .paths = fmt_include_paths, + .exclude_paths = fmt_exclude_paths, + }); + + const test_fmt_step = b.step("test-fmt", "Check whether source files have conforming formatting"); + test_fmt_step.dependOn(&check_fmt.step); + + const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting"); + do_fmt_step.dependOn(&do_fmt.step); + test_step.dependOn(tests.addPkgTests( b, diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 2c5bcdd617..40f45d9ac8 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -93,6 +93,7 @@ pub fn main() !void { var install_prefix: ?[]const u8 = null; var dir_list = std.Build.DirList{}; + var enable_summary: ?bool = null; const Color = enum { auto, off, on }; var color: Color = .auto; @@ -217,6 +218,10 @@ pub fn main() !void { builder.enable_darling = true; } else if (mem.eql(u8, arg, "-fno-darling")) { builder.enable_darling = false; + } else if (mem.eql(u8, arg, "-fsummary")) { + enable_summary = true; + } else if (mem.eql(u8, arg, "-fno-summary")) { + enable_summary = false; } else if (mem.eql(u8, arg, "-freference-trace")) { builder.reference_trace = 256; } else if (mem.startsWith(u8, arg, "-freference-trace=")) { @@ -252,8 +257,9 @@ pub fn main() !void { } } + const stderr = std.io.getStdErr(); const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .auto => std.debug.detectTTYConfig(stderr), .on => .escape_codes, .off => .no_color, }; @@ -279,6 +285,8 @@ pub fn main() !void { main_progress_node, thread_pool_options, ttyconf, + stderr, + enable_summary, ) catch |err| switch (err) { error.UncleanExit => process.exit(1), else => return err, @@ -292,6 +300,8 @@ fn runStepNames( parent_prog_node: *std.Progress.Node, thread_pool_options: std.Thread.Pool.Options, ttyconf: std.debug.TTY.Config, + stderr: std.fs.File, + enable_summary: ?bool, ) !void { const gpa = b.allocator; var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{}; @@ -382,28 +392,35 @@ fn runStepNames( // A proper command line application defaults to silently succeeding. // The user may request verbose mode if they have a different preference. - if (failure_count == 0 and !b.verbose) return cleanExit(); + if (failure_count == 0 and enable_summary != true) return cleanExit(); - const stderr = std.io.getStdErr(); + if (enable_summary != false) { + const total_count = success_count + failure_count + pending_count; + ttyconf.setColor(stderr, .Cyan) catch {}; + stderr.writeAll("Build Summary:") catch {}; + ttyconf.setColor(stderr, .Reset) catch {}; + stderr.writer().print(" {d}/{d} steps succeeded; {d} failed", .{ + success_count, total_count, failure_count, + }) catch {}; - const total_count = success_count + failure_count + pending_count; - ttyconf.setColor(stderr, .Cyan) catch {}; - stderr.writeAll("Build Summary: ") catch {}; - ttyconf.setColor(stderr, .Reset) catch {}; - stderr.writer().print("{d}/{d} steps succeeded; {d} failed; {d} total compile errors\n", .{ - success_count, total_count, failure_count, total_compile_errors, - }) catch {}; + if (enable_summary == null) { + ttyconf.setColor(stderr, .Dim) catch {}; + stderr.writeAll(" (disable with -fno-summary)") catch {}; + ttyconf.setColor(stderr, .Reset) catch {}; + } + stderr.writeAll("\n") catch {}; - // Print a fancy tree with build results. - var print_node: PrintNode = .{ .parent = null }; - if (step_names.len == 0) { - print_node.last = true; - printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {}; - } else { - for (step_names, 0..) |step_name, i| { - const tls = b.top_level_steps.get(step_name).?; - print_node.last = i + 1 == b.top_level_steps.count(); - printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {}; + // Print a fancy tree with build results. + var print_node: PrintNode = .{ .parent = null }; + if (step_names.len == 0) { + print_node.last = true; + printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {}; + } else { + for (step_names, 0..) |step_name, i| { + const tls = b.top_level_steps.get(step_name).?; + print_node.last = i + 1 == b.top_level_steps.count(); + printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {}; + } } } @@ -453,9 +470,9 @@ fn printTreeStep( step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { const first = step_stack.swapRemove(s); - if (!first) try ttyconf.setColor(stderr, .Dim); try printPrefix(parent_node, stderr); + if (!first) try ttyconf.setColor(stderr, .Dim); if (parent_node.parent != null) { if (parent_node.last) { try stderr.writeAll("└─ "); @@ -464,7 +481,7 @@ fn printTreeStep( } } - // TODO print the dep prefix too? + // dep_prefix omitted here because it is redundant with the tree. try stderr.writeAll(s.name); if (first) { @@ -608,8 +625,10 @@ fn workerMakeOneStep( const stderr = std.io.getStdErr(); for (s.result_error_msgs.items) |msg| { - // TODO print the dep prefix too + // Sometimes it feels like you just can't catch a break. Finally, + // with Zig, you can. ttyconf.setColor(stderr, .Bold) catch break; + stderr.writeAll(s.owner.dep_prefix) catch break; stderr.writeAll(s.name) catch break; stderr.writeAll(": ") catch break; ttyconf.setColor(stderr, .Red) catch break; @@ -735,6 +754,8 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\Advanced Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error \\ -fno-reference-trace Disable reference trace + \\ -fsummary Print the build summary, even on success + \\ -fno-summary Omit the build summary, even on failure \\ --build-file [file] Override path to build.zig \\ --cache-dir [path] Override path to local Zig cache directory \\ --global-cache-dir [path] Override path to global Zig cache directory diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 3e765be4ba..1d87ea961f 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -32,14 +32,12 @@ pub const Step = @import("Build/Step.zig"); pub const CheckFileStep = @import("Build/CheckFileStep.zig"); pub const CheckObjectStep = @import("Build/CheckObjectStep.zig"); pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig"); -pub const EmulatableRunStep = @import("Build/EmulatableRunStep.zig"); pub const FmtStep = @import("Build/FmtStep.zig"); pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig"); pub const InstallDirStep = @import("Build/InstallDirStep.zig"); pub const InstallFileStep = @import("Build/InstallFileStep.zig"); pub const ObjCopyStep = @import("Build/ObjCopyStep.zig"); pub const CompileStep = @import("Build/CompileStep.zig"); -pub const LogStep = @import("Build/LogStep.zig"); pub const OptionsStep = @import("Build/OptionsStep.zig"); pub const RemoveDirStep = @import("Build/RemoveDirStep.zig"); pub const RunStep = @import("Build/RunStep.zig"); @@ -195,7 +193,7 @@ pub fn create( env_map.* = try process.getEnvMap(allocator); const self = try allocator.create(Build); - self.* = Build{ + self.* = .{ .zig_exe = zig_exe, .build_root = build_root, .cache_root = cache_root, @@ -224,16 +222,18 @@ pub fn create( .dest_dir = env_map.get("DESTDIR"), .installed_files = ArrayList(InstalledFile).init(allocator), .install_tls = .{ - .step = Step.init(allocator, .{ + .step = Step.init(.{ .id = .top_level, .name = "install", + .owner = self, }), .description = "Copy build artifacts to prefix path", }, .uninstall_tls = .{ - .step = Step.init(allocator, .{ + .step = Step.init(.{ .id = .top_level, .name = "uninstall", + .owner = self, .makeFn = makeUninstall, }), .description = "Remove build artifacts from prefix path", @@ -267,16 +267,18 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc child.* = .{ .allocator = allocator, .install_tls = .{ - .step = Step.init(allocator, .{ + .step = Step.init(.{ .id = .top_level, .name = "install", + .owner = child, }), .description = "Copy build artifacts to prefix path", }, .uninstall_tls = .{ - .step = Step.init(allocator, .{ + .step = Step.init(.{ .id = .top_level, .name = "uninstall", + .owner = child, .makeFn = makeUninstall, }), .description = "Remove build artifacts from prefix path", @@ -689,21 +691,14 @@ pub fn addWriteFiles(self: *Build) *WriteFileStep { return write_file_step; } -pub fn addLog(self: *Build, comptime format: []const u8, args: anytype) *LogStep { - const data = self.fmt(format, args); - const log_step = self.allocator.create(LogStep) catch @panic("OOM"); - log_step.* = LogStep.init(self, data); - return log_step; -} - pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep { const remove_dir_step = self.allocator.create(RemoveDirStep) catch @panic("OOM"); remove_dir_step.* = RemoveDirStep.init(self, dir_path); return remove_dir_step; } -pub fn addFmt(self: *Build, paths: []const []const u8) *FmtStep { - return FmtStep.create(self, paths); +pub fn addFmt(b: *Build, options: FmtStep.Options) *FmtStep { + return FmtStep.create(b, options); } pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep { @@ -870,10 +865,11 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ pub fn step(self: *Build, name: []const u8, description: []const u8) *Step { const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM"); - step_info.* = TopLevelStep{ - .step = Step.init(self.allocator, .{ + step_info.* = .{ + .step = Step.init(.{ .id = .top_level, .name = name, + .owner = self, }), .description = self.dupe(description), }; @@ -1145,10 +1141,6 @@ pub fn validateUserInputDidItFail(self: *Build) bool { return self.invalid_user_input; } -pub fn spawnChild(self: *Build, argv: []const []const u8) !void { - return self.spawnChildEnvMap(null, self.env_map, argv); -} - fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 { var buf = ArrayList(u8).init(ally); if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd}); @@ -1163,40 +1155,6 @@ fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void { std.debug.print("{s}\n", .{text}); } -pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void { - if (self.verbose) { - printCmd(self.allocator, cwd, argv); - } - - if (!process.can_spawn) - return error.ExecNotSupported; - - var child = std.ChildProcess.init(argv, self.allocator); - child.cwd = cwd; - child.env_map = env_map; - - const term = child.spawnAndWait() catch |err| { - log.err("Unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); - return err; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - log.err("The following command exited with error code {}:", .{code}); - printCmd(self.allocator, cwd, argv); - return error.UncleanExit; - } - }, - else => { - log.err("The following command terminated unexpectedly:", .{}); - printCmd(self.allocator, cwd, argv); - - return error.UncleanExit; - }, - } -} - pub fn installArtifact(self: *Build, artifact: *CompileStep) void { self.getInstallStep().dependOn(&self.addInstallArtifact(artifact).step); } @@ -1403,160 +1361,6 @@ pub fn execAllowFail( } } -/// This function is used exclusively for spawning and communicating with the zig compiler. -/// TODO: move to build_runner.zig -pub fn execFromStep(b: *Build, argv: []const []const u8, s: *Step, prog_node: *std.Progress.Node) ![]const u8 { - assert(argv.len != 0); - - if (b.verbose) { - const text = try allocPrintCmd(b.allocator, null, argv); - try s.result_error_msgs.append(b.allocator, text); - } - - if (!process.can_spawn) { - try s.result_error_msgs.append(b.allocator, b.fmt("Unable to spawn the following command: cannot spawn child processes\n{s}", .{ - try allocPrintCmd(b.allocator, null, argv), - })); - return error.MakeFailed; - } - - var child = std.ChildProcess.init(argv, b.allocator); - child.env_map = b.env_map; - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - var poller = std.io.poll(b.allocator, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); - - try sendMessage(child.stdin.?, .update); - try sendMessage(child.stdin.?, .exit); - - const Header = std.zig.Server.Message.Header; - var result: ?[]const u8 = null; - - var node_name: std.ArrayListUnmanaged(u8) = .{}; - defer node_name.deinit(b.allocator); - var sub_prog_node: ?std.Progress.Node = null; - defer if (sub_prog_node) |*n| n.end(); - - while (try poller.poll()) { - const stdout = poller.fifo(.stdout); - const buf = stdout.readableSlice(0); - assert(stdout.readableLength() == buf.len); - if (buf.len >= @sizeOf(Header)) { - const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - const header_and_msg_len = header.bytes_len + @sizeOf(Header); - if (buf.len >= header_and_msg_len) { - const body = buf[@sizeOf(Header)..][0..header.bytes_len]; - switch (header.tag) { - .zig_version => { - if (!mem.eql(u8, builtin.zig_version_string, body)) { - try s.result_error_msgs.append( - b.allocator, - b.fmt("zig version mismatch build runner vs compiler: '{s}' vs '{s}'", .{ - builtin.zig_version_string, body, - }), - ); - return error.MakeFailed; - } - }, - .error_bundle => { - const EbHdr = std.zig.Server.Message.ErrorBundle; - const eb_hdr = @ptrCast(*align(1) const EbHdr, body); - const extra_bytes = - body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; - const string_bytes = - body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; - // TODO: use @ptrCast when the compiler supports it - const unaligned_extra = mem.bytesAsSlice(u32, extra_bytes); - const extra_array = try b.allocator.alloc(u32, unaligned_extra.len); - // TODO: use @memcpy when it supports slices - for (extra_array, unaligned_extra) |*dst, src| dst.* = src; - s.result_error_bundle = .{ - .string_bytes = try b.allocator.dupe(u8, string_bytes), - .extra = extra_array, - }; - }, - .progress => { - if (sub_prog_node) |*n| n.end(); - node_name.clearRetainingCapacity(); - try node_name.appendSlice(b.allocator, body); - sub_prog_node = prog_node.start(node_name.items, 0); - sub_prog_node.?.activate(); - }, - .emit_bin_path => { - result = try b.allocator.dupe(u8, body); - }, - _ => { - // Unrecognized message. - }, - } - stdout.discard(header_and_msg_len); - } - } - } - - const stderr = poller.fifo(.stderr); - if (stderr.readableLength() > 0) { - try s.result_error_msgs.append(b.allocator, try stderr.toOwnedSlice()); - } - - // Send EOF to stdin. - child.stdin.?.close(); - child.stdin = null; - - const term = try child.wait(); - switch (term) { - .Exited => |code| { - if (code != 0) { - try s.result_error_msgs.append(b.allocator, b.fmt("the following command exited with error code {d}:\n{s}", .{ - code, try allocPrintCmd(b.allocator, null, argv), - })); - return error.MakeFailed; - } - }, - .Signal, .Stopped, .Unknown => |code| { - _ = code; - try s.result_error_msgs.append(b.allocator, b.fmt("the following command terminated unexpectedly:\n{s}", .{ - try allocPrintCmd(b.allocator, null, argv), - })); - return error.MakeFailed; - }, - } - - if (s.result_error_bundle.errorMessageCount() > 0) { - try s.result_error_msgs.append( - b.allocator, - b.fmt("the following command failed with {d} compilation errors:\n{s}", .{ - s.result_error_bundle.errorMessageCount(), - try allocPrintCmd(b.allocator, null, argv), - }), - ); - return error.MakeFailed; - } - - return result orelse { - try s.result_error_msgs.append(b.allocator, b.fmt("the following command failed to communicate the compilation result:\n{s}", .{ - try allocPrintCmd(b.allocator, null, argv), - })); - return error.MakeFailed; - }; -} - -fn sendMessage(file: fs.File, tag: std.zig.Client.Message.Tag) !void { - const header: std.zig.Client.Message.Header = .{ - .tag = tag, - .bytes_len = 0, - }; - try file.writeAll(std.mem.asBytes(&header)); -} - /// This is a helper function to be called from build.zig scripts, *not* from /// inside step make() functions. If any errors occur, it fails the build with /// a helpful message. @@ -1910,14 +1714,12 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 { test { _ = CheckFileStep; _ = CheckObjectStep; - _ = EmulatableRunStep; _ = FmtStep; _ = InstallArtifactStep; _ = InstallDirStep; _ = InstallFileStep; _ = ObjCopyStep; _ = CompileStep; - _ = LogStep; _ = OptionsStep; _ = RemoveDirStep; _ = RunStep; diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index 9fee947386..f70a29840e 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -8,26 +8,25 @@ const CheckFileStep = @This(); pub const base_id = .check_file; step: Step, -builder: *std.Build, expected_matches: []const []const u8, source: std.Build.FileSource, max_bytes: usize = 20 * 1024 * 1024, pub fn create( - builder: *std.Build, + owner: *std.Build, source: std.Build.FileSource, expected_matches: []const []const u8, ) *CheckFileStep { - const self = builder.allocator.create(CheckFileStep) catch @panic("OOM"); + const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); self.* = CheckFileStep{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = .check_file, .name = "CheckFile", + .owner = owner, .makeFn = make, }), - .source = source.dupe(builder), - .expected_matches = builder.dupeStrings(expected_matches), + .source = source.dupe(owner), + .expected_matches = owner.dupeStrings(expected_matches), }; self.source.addStepDependencies(&self.step); return self; @@ -35,10 +34,11 @@ pub fn create( fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; + const b = step.owner; const self = @fieldParentPtr(CheckFileStep, "step", step); - const src_path = self.source.getPath(self.builder); - const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes); + const src_path = self.source.getPath(b); + const contents = try fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes); for (self.expected_matches) |expected_match| { if (mem.indexOf(u8, contents, expected_match) == null) { diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig index eccbbd1696..57d280da0e 100644 --- a/lib/std/Build/CheckObjectStep.zig +++ b/lib/std/Build/CheckObjectStep.zig @@ -10,29 +10,31 @@ const CheckObjectStep = @This(); const Allocator = mem.Allocator; const Step = std.Build.Step; -const EmulatableRunStep = std.Build.EmulatableRunStep; pub const base_id = .check_object; step: Step, -builder: *std.Build, source: std.Build.FileSource, max_bytes: usize = 20 * 1024 * 1024, checks: std.ArrayList(Check), dump_symtab: bool = false, obj_format: std.Target.ObjectFormat, -pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - const gpa = builder.allocator; +pub fn create( + owner: *std.Build, + source: std.Build.FileSource, + obj_format: std.Target.ObjectFormat, +) *CheckObjectStep { + const gpa = owner.allocator; const self = gpa.create(CheckObjectStep) catch @panic("OOM"); self.* = .{ - .builder = builder, - .step = Step.init(gpa, .{ + .step = Step.init(.{ .id = .check_file, .name = "CheckObject", + .owner = owner, .makeFn = make, }), - .source = source.dupe(builder), + .source = source.dupe(owner), .checks = std.ArrayList(Check).init(gpa), .obj_format = obj_format, }; @@ -42,14 +44,18 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std /// Runs and (optionally) compares the output of a binary. /// Asserts `self` was generated from an executable step. -pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep { +/// TODO this doesn't actually compare, and there's no apparent reason for it +/// to depend on the check object step. I don't see why this function should exist, +/// the caller could just add the run step directly. +pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep { const dependencies_len = self.step.dependencies.items.len; assert(dependencies_len > 0); const exe_step = self.step.dependencies.items[dependencies_len - 1]; const exe = exe_step.cast(std.Build.CompileStep).?; - const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe); - emulatable_step.step.dependOn(&self.step); - return emulatable_step; + const run = self.step.owner.addRunArtifact(exe); + run.skip_foreign_checks = true; + run.step.dependOn(&self.step); + return run; } /// There two types of actions currently suported: @@ -253,7 +259,7 @@ const Check = struct { /// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { - var new_check = Check.create(self.builder); + var new_check = Check.create(self.step.owner); new_check.match(phrase); self.checks.append(new_check) catch @panic("OOM"); } @@ -295,17 +301,18 @@ pub fn checkComputeCompare( program: []const u8, expected: ComputeCompareExpected, ) void { - var new_check = Check.create(self.builder); + var new_check = Check.create(self.step.owner); new_check.computeCmp(program, expected); self.checks.append(new_check) catch @panic("OOM"); } fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; + const b = step.owner; + const gpa = b.allocator; const self = @fieldParentPtr(CheckObjectStep, "step", step); - const gpa = self.builder.allocator; - const src_path = self.source.getPath(self.builder); + const src_path = self.source.getPath(b); const contents = try fs.cwd().readFileAllocOptions( gpa, src_path, diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 711092c762..f1a8f71334 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -22,7 +22,6 @@ const InstallDir = std.Build.InstallDir; const InstallArtifactStep = std.Build.InstallArtifactStep; const GeneratedFile = std.Build.GeneratedFile; const ObjCopyStep = std.Build.ObjCopyStep; -const EmulatableRunStep = std.Build.EmulatableRunStep; const CheckObjectStep = std.Build.CheckObjectStep; const RunStep = std.Build.RunStep; const OptionsStep = std.Build.OptionsStep; @@ -32,7 +31,6 @@ const CompileStep = @This(); pub const base_id: Step.Id = .compile; step: Step, -builder: *std.Build, name: []const u8, target: CrossTarget, target_info: NativeTargetInfo, @@ -305,24 +303,23 @@ pub const EmitOption = union(enum) { } }; -pub fn create(builder: *std.Build, options: Options) *CompileStep { - const name = builder.dupe(options.name); - const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(builder) else null; +pub fn create(owner: *std.Build, options: Options) *CompileStep { + const name = owner.dupe(options.name); + const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null; if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); } - const step_name = builder.fmt("compile {s} {s} {s}", .{ + const step_name = owner.fmt("compile {s} {s} {s}", .{ name, @tagName(options.optimize), - options.target.zigTriple(builder.allocator) catch @panic("OOM"), + options.target.zigTriple(owner.allocator) catch @panic("OOM"), }); - const self = builder.allocator.create(CompileStep) catch @panic("OOM"); + const self = owner.allocator.create(CompileStep) catch @panic("OOM"); self.* = CompileStep{ .strip = null, .unwind_tables = null, - .builder = builder, .verbose_link = false, .verbose_cc = false, .optimize = options.optimize, @@ -331,27 +328,28 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep { .kind = options.kind, .root_src = root_src, .name = name, - .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator), - .step = Step.init(builder.allocator, .{ + .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator), + .step = Step.init(.{ .id = base_id, .name = step_name, + .owner = owner, .makeFn = make, }), .version = options.version, .out_filename = undefined, - .out_h_filename = builder.fmt("{s}.h", .{name}), + .out_h_filename = owner.fmt("{s}.h", .{name}), .out_lib_filename = undefined, - .out_pdb_filename = builder.fmt("{s}.pdb", .{name}), + .out_pdb_filename = owner.fmt("{s}.pdb", .{name}), .major_only_filename = null, .name_only_filename = null, - .modules = std.StringArrayHashMap(*Module).init(builder.allocator), - .include_dirs = ArrayList(IncludeDir).init(builder.allocator), - .link_objects = ArrayList(LinkObject).init(builder.allocator), - .c_macros = ArrayList([]const u8).init(builder.allocator), - .lib_paths = ArrayList([]const u8).init(builder.allocator), - .rpaths = ArrayList([]const u8).init(builder.allocator), - .framework_dirs = ArrayList([]const u8).init(builder.allocator), - .installed_headers = ArrayList(*Step).init(builder.allocator), + .modules = std.StringArrayHashMap(*Module).init(owner.allocator), + .include_dirs = ArrayList(IncludeDir).init(owner.allocator), + .link_objects = ArrayList(LinkObject).init(owner.allocator), + .c_macros = ArrayList([]const u8).init(owner.allocator), + .lib_paths = ArrayList([]const u8).init(owner.allocator), + .rpaths = ArrayList([]const u8).init(owner.allocator), + .framework_dirs = ArrayList([]const u8).init(owner.allocator), + .installed_headers = ArrayList(*Step).init(owner.allocator), .object_src = undefined, .c_std = std.Build.CStd.C99, .zig_lib_dir = null, @@ -382,9 +380,10 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep { } fn computeOutFileNames(self: *CompileStep) void { + const b = self.step.owner; const target = self.target_info.target; - self.out_filename = std.zig.binNameAlloc(self.builder.allocator, .{ + self.out_filename = std.zig.binNameAlloc(b.allocator, .{ .root_name = self.name, .target = target, .output_mode = switch (self.kind) { @@ -404,30 +403,30 @@ fn computeOutFileNames(self: *CompileStep) void { self.out_lib_filename = self.out_filename; } else if (self.version) |version| { if (target.isDarwin()) { - self.major_only_filename = self.builder.fmt("lib{s}.{d}.dylib", .{ + self.major_only_filename = b.fmt("lib{s}.{d}.dylib", .{ self.name, version.major, }); - self.name_only_filename = self.builder.fmt("lib{s}.dylib", .{self.name}); + self.name_only_filename = b.fmt("lib{s}.dylib", .{self.name}); self.out_lib_filename = self.out_filename; } else if (target.os.tag == .windows) { - self.out_lib_filename = self.builder.fmt("{s}.lib", .{self.name}); + self.out_lib_filename = b.fmt("{s}.lib", .{self.name}); } else { - self.major_only_filename = self.builder.fmt("lib{s}.so.{d}", .{ self.name, version.major }); - self.name_only_filename = self.builder.fmt("lib{s}.so", .{self.name}); + self.major_only_filename = b.fmt("lib{s}.so.{d}", .{ self.name, version.major }); + self.name_only_filename = b.fmt("lib{s}.so", .{self.name}); self.out_lib_filename = self.out_filename; } } else { if (target.isDarwin()) { self.out_lib_filename = self.out_filename; } else if (target.os.tag == .windows) { - self.out_lib_filename = self.builder.fmt("{s}.lib", .{self.name}); + self.out_lib_filename = b.fmt("{s}.lib", .{self.name}); } else { self.out_lib_filename = self.out_filename; } } if (self.output_dir != null) { - self.output_lib_path_source.path = self.builder.pathJoin( + self.output_lib_path_source.path = b.pathJoin( &.{ self.output_dir.?, self.out_lib_filename }, ); } @@ -435,17 +434,20 @@ fn computeOutFileNames(self: *CompileStep) void { } pub fn setOutputDir(self: *CompileStep, dir: []const u8) void { - self.output_dir = self.builder.dupePath(dir); + const b = self.step.owner; + self.output_dir = b.dupePath(dir); } pub fn install(self: *CompileStep) void { - self.builder.installArtifact(self); + const b = self.step.owner; + b.installArtifact(self); } -pub fn installHeader(a: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void { - const install_file = a.builder.addInstallHeaderFile(src_path, dest_rel_path); - a.builder.getInstallStep().dependOn(&install_file.step); - a.installed_headers.append(&install_file.step) catch @panic("OOM"); +pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void { + const b = cs.step.owner; + const install_file = b.addInstallHeaderFile(src_path, dest_rel_path); + b.getInstallStep().dependOn(&install_file.step); + cs.installed_headers.append(&install_file.step) catch @panic("OOM"); } pub const InstallConfigHeaderOptions = struct { @@ -459,13 +461,14 @@ pub fn installConfigHeader( options: InstallConfigHeaderOptions, ) void { const dest_rel_path = options.dest_rel_path orelse config_header.include_path; - const install_file = cs.builder.addInstallFileWithDir( + const b = cs.step.owner; + const install_file = b.addInstallFileWithDir( .{ .generated = &config_header.output_file }, options.install_dir, dest_rel_path, ); install_file.step.dependOn(&config_header.step); - cs.builder.getInstallStep().dependOn(&install_file.step); + b.getInstallStep().dependOn(&install_file.step); cs.installed_headers.append(&install_file.step) catch @panic("OOM"); } @@ -482,91 +485,84 @@ pub fn installHeadersDirectory( } pub fn installHeadersDirectoryOptions( - a: *CompileStep, + cs: *CompileStep, options: std.Build.InstallDirStep.Options, ) void { - const install_dir = a.builder.addInstallDirectory(options); - a.builder.getInstallStep().dependOn(&install_dir.step); - a.installed_headers.append(&install_dir.step) catch @panic("OOM"); + const b = cs.step.owner; + const install_dir = b.addInstallDirectory(options); + b.getInstallStep().dependOn(&install_dir.step); + cs.installed_headers.append(&install_dir.step) catch @panic("OOM"); } -pub fn installLibraryHeaders(a: *CompileStep, l: *CompileStep) void { +pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void { assert(l.kind == .lib); - const install_step = a.builder.getInstallStep(); + const b = cs.step.owner; + const install_step = b.getInstallStep(); // Copy each element from installed_headers, modifying the builder // to be the new parent's builder. for (l.installed_headers.items) |step| { const step_copy = switch (step.id) { inline .install_file, .install_dir => |id| blk: { const T = id.Type(); - const ptr = a.builder.allocator.create(T) catch @panic("OOM"); + const ptr = b.allocator.create(T) catch @panic("OOM"); ptr.* = step.cast(T).?.*; - ptr.override_source_builder = ptr.builder; - ptr.builder = a.builder; + ptr.dest_builder = b; break :blk &ptr.step; }, else => unreachable, }; - a.installed_headers.append(step_copy) catch @panic("OOM"); + cs.installed_headers.append(step_copy) catch @panic("OOM"); install_step.dependOn(step_copy); } - a.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM"); + cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM"); } pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep { + const b = cs.step.owner; var copy = options; if (copy.basename == null) { if (options.format) |f| { - copy.basename = cs.builder.fmt("{s}.{s}", .{ cs.name, @tagName(f) }); + copy.basename = b.fmt("{s}.{s}", .{ cs.name, @tagName(f) }); } else { copy.basename = cs.name; } } - return cs.builder.addObjCopy(cs.getOutputSource(), copy); + return b.addObjCopy(cs.getOutputSource(), copy); } /// Deprecated: use `std.Build.addRunArtifact` /// This function will run in the context of the package that created the executable, /// which is undesirable when running an executable provided by a dependency package. -pub fn run(exe: *CompileStep) *RunStep { - return exe.builder.addRunArtifact(exe); -} - -/// Creates an `EmulatableRunStep` with an executable built with `addExecutable`. -/// Allows running foreign binaries through emulation platforms such as Qemu or Rosetta. -/// When a binary cannot be ran through emulation or the option is disabled, a warning -/// will be printed and the binary will *NOT* be ran. -pub fn runEmulatable(exe: *CompileStep) *EmulatableRunStep { - assert(exe.kind == .exe or exe.kind == .test_exe); - - const run_step = EmulatableRunStep.create(exe.builder, exe.builder.fmt("run {s}", .{exe.step.name}), exe); - if (exe.vcpkg_bin_path) |path| { - RunStep.addPathDirInternal(&run_step.step, exe.builder, path); - } - return run_step; +pub fn run(cs: *CompileStep) *RunStep { + return cs.step.owner.addRunArtifact(cs); } pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); + const b = self.step.owner; + return CheckObjectStep.create(b, self.getOutputSource(), obj_format); } pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { - self.linker_script = source.dupe(self.builder); + const b = self.step.owner; + self.linker_script = source.dupe(b); source.addStepDependencies(&self.step); } pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), .{}) catch @panic("OOM"); + const b = self.step.owner; + self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); } pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), .{ + const b = self.step.owner; + self.frameworks.put(b.dupe(framework_name), .{ .needed = true, }) catch @panic("OOM"); } pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), .{ + const b = self.step.owner; + self.frameworks.put(b.dupe(framework_name), .{ .weak = true, }) catch @panic("OOM"); } @@ -619,21 +615,24 @@ pub fn linkLibCpp(self: *CompileStep) void { /// If the value is omitted, it is set to 1. /// `name` and `value` need not live longer than the function call. pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void { - const macro = std.Build.constructCMacro(self.builder.allocator, name, value); + const b = self.step.owner; + const macro = std.Build.constructCMacro(b.allocator, name, value); self.c_macros.append(macro) catch @panic("OOM"); } /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void { - self.c_macros.append(self.builder.dupe(name_and_value)) catch @panic("OOM"); + const b = self.step.owner; + self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); } /// This one has no integration with anything, it just puts -lname on the command line. /// Prefer to use `linkSystemLibrary` instead. pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void { + const b = self.step.owner; self.link_objects.append(.{ .system_lib = .{ - .name = self.builder.dupe(name), + .name = b.dupe(name), .needed = false, .weak = false, .use_pkg_config = .no, @@ -644,9 +643,10 @@ pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void { /// This one has no integration with anything, it just puts -needed-lname on the command line. /// Prefer to use `linkSystemLibraryNeeded` instead. pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void { + const b = self.step.owner; self.link_objects.append(.{ .system_lib = .{ - .name = self.builder.dupe(name), + .name = b.dupe(name), .needed = true, .weak = false, .use_pkg_config = .no, @@ -657,9 +657,10 @@ pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void { /// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the /// command line. Prefer to use `linkSystemLibraryWeak` instead. pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void { + const b = self.step.owner; self.link_objects.append(.{ .system_lib = .{ - .name = self.builder.dupe(name), + .name = b.dupe(name), .needed = false, .weak = true, .use_pkg_config = .no, @@ -670,9 +671,10 @@ pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void { /// This links against a system library, exclusively using pkg-config to find the library. /// Prefer to use `linkSystemLibrary` instead. pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { + const b = self.step.owner; self.link_objects.append(.{ .system_lib = .{ - .name = self.builder.dupe(lib_name), + .name = b.dupe(lib_name), .needed = false, .weak = false, .use_pkg_config = .force, @@ -683,9 +685,10 @@ pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) /// This links against a system library, exclusively using pkg-config to find the library. /// Prefer to use `linkSystemLibraryNeeded` instead. pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { + const b = self.step.owner; self.link_objects.append(.{ .system_lib = .{ - .name = self.builder.dupe(lib_name), + .name = b.dupe(lib_name), .needed = true, .weak = false, .use_pkg_config = .force, @@ -696,13 +699,14 @@ pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []cons /// Run pkg-config for the given library name and parse the output, returning the arguments /// that should be passed to zig to link the given library. pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { + const b = self.step.owner; const pkg_name = match: { // First we have to map the library name to pkg config name. Unfortunately, // there are several examples where this is not straightforward: // -lSDL2 -> pkg-config sdl2 // -lgdk-3 -> pkg-config gdk-3.0 // -latk-1.0 -> pkg-config atk - const pkgs = try getPkgConfigList(self.builder); + const pkgs = try getPkgConfigList(b); // Exact match means instant winner. for (pkgs) |pkg| { @@ -742,7 +746,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u }; var code: u8 = undefined; - const stdout = if (self.builder.execAllowFail(&[_][]const u8{ + const stdout = if (b.execAllowFail(&[_][]const u8{ "pkg-config", pkg_name, "--cflags", @@ -755,7 +759,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u else => return err, }; - var zig_args = ArrayList([]const u8).init(self.builder.allocator); + var zig_args = ArrayList([]const u8).init(b.allocator); defer zig_args.deinit(); var it = mem.tokenize(u8, stdout, " \r\n\t"); @@ -780,7 +784,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); } else if (mem.startsWith(u8, tok, "-D")) { try zig_args.append(tok); - } else if (self.builder.verbose) { + } else if (b.verbose) { log.warn("Ignoring pkg-config flag '{s}'", .{tok}); } } @@ -804,6 +808,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { needed: bool = false, weak: bool = false, }) void { + const b = self.step.owner; if (isLibCLibrary(name)) { self.linkLibC(); return; @@ -815,7 +820,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { self.link_objects.append(.{ .system_lib = .{ - .name = self.builder.dupe(name), + .name = b.dupe(name), .needed = opts.needed, .weak = opts.weak, .use_pkg_config = .yes, @@ -824,26 +829,30 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { } pub fn setNamePrefix(self: *CompileStep, text: []const u8) void { + const b = self.step.owner; assert(self.kind == .@"test" or self.kind == .test_exe); - self.name_prefix = self.builder.dupe(text); + self.name_prefix = b.dupe(text); } pub fn setFilter(self: *CompileStep, text: ?[]const u8) void { + const b = self.step.owner; assert(self.kind == .@"test" or self.kind == .test_exe); - self.filter = if (text) |t| self.builder.dupe(t) else null; + self.filter = if (text) |t| b.dupe(t) else null; } pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void { + const b = self.step.owner; assert(self.kind == .@"test" or self.kind == .test_exe); - self.test_runner = if (path) |p| self.builder.dupePath(p) else null; + self.test_runner = if (path) |p| b.dupePath(p) else null; } /// Handy when you have many C/C++ source files and want them all to have the same flags. pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void { - const c_source_files = self.builder.allocator.create(CSourceFiles) catch @panic("OOM"); + const b = self.step.owner; + const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM"); - const files_copy = self.builder.dupeStrings(files); - const flags_copy = self.builder.dupeStrings(flags); + const files_copy = b.dupeStrings(files); + const flags_copy = b.dupeStrings(flags); c_source_files.* = .{ .files = files_copy, @@ -860,8 +869,9 @@ pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []con } pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void { - const c_source_file = self.builder.allocator.create(CSourceFile) catch @panic("OOM"); - c_source_file.* = source.dupe(self.builder); + const b = self.step.owner; + const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM"); + c_source_file.* = source.dupe(b); self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); source.source.addStepDependencies(&self.step); } @@ -875,15 +885,18 @@ pub fn setVerboseCC(self: *CompileStep, value: bool) void { } pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void { - self.zig_lib_dir = self.builder.dupePath(dir_path); + const b = self.step.owner; + self.zig_lib_dir = b.dupePath(dir_path); } pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void { - self.main_pkg_path = self.builder.dupePath(dir_path); + const b = self.step.owner; + self.main_pkg_path = b.dupePath(dir_path); } pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void { - self.libc_file = if (libc_file) |f| f.dupe(self.builder) else null; + const b = self.step.owner; + self.libc_file = if (libc_file) |f| f.dupe(b) else null; } /// Returns the generated executable, library or object file. @@ -914,13 +927,15 @@ pub fn getOutputPdbSource(self: *CompileStep) FileSource { } pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void { + const b = self.step.owner; self.link_objects.append(.{ - .assembly_file = .{ .path = self.builder.dupe(path) }, + .assembly_file = .{ .path = b.dupe(path) }, }) catch @panic("OOM"); } pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void { - const source_duped = source.dupe(self.builder); + const b = self.step.owner; + const source_duped = source.dupe(b); self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM"); source_duped.addStepDependencies(&self.step); } @@ -930,7 +945,8 @@ pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void { } pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void { - self.link_objects.append(.{ .static_path = source.dupe(self.builder) }) catch @panic("OOM"); + const b = self.step.owner; + self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM"); source.addStepDependencies(&self.step); } @@ -945,11 +961,13 @@ pub const addLibPath = @compileError("deprecated, use addLibraryPath"); pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void { - self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch @panic("OOM"); + const b = self.step.owner; + self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM"); } pub fn addIncludePath(self: *CompileStep, path: []const u8) void { - self.include_dirs.append(IncludeDir{ .raw_path = self.builder.dupe(path) }) catch @panic("OOM"); + const b = self.step.owner; + self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM"); } pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void { @@ -958,23 +976,27 @@ pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) voi } pub fn addLibraryPath(self: *CompileStep, path: []const u8) void { - self.lib_paths.append(self.builder.dupe(path)) catch @panic("OOM"); + const b = self.step.owner; + self.lib_paths.append(b.dupe(path)) catch @panic("OOM"); } pub fn addRPath(self: *CompileStep, path: []const u8) void { - self.rpaths.append(self.builder.dupe(path)) catch @panic("OOM"); + const b = self.step.owner; + self.rpaths.append(b.dupe(path)) catch @panic("OOM"); } pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void { - self.framework_dirs.append(self.builder.dupe(dir_path)) catch @panic("OOM"); + const b = self.step.owner; + self.framework_dirs.append(b.dupe(dir_path)) catch @panic("OOM"); } /// Adds a module to be used with `@import` and exposing it in the current /// package's module table using `name`. pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void { - cs.modules.put(cs.builder.dupe(name), module) catch @panic("OOM"); + const b = cs.step.owner; + cs.modules.put(b.dupe(name), module) catch @panic("OOM"); - var done = std.AutoHashMap(*Module, void).init(cs.builder.allocator); + var done = std.AutoHashMap(*Module, void).init(b.allocator); defer done.deinit(); cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM"); } @@ -982,7 +1004,8 @@ pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void { /// Adds a module to be used with `@import` without exposing it in the current /// package's module table. pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void { - const module = cs.builder.createModule(options); + const b = cs.step.owner; + const module = b.createModule(options); return addModule(cs, name, module); } @@ -1002,12 +1025,13 @@ fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashM /// If Vcpkg was found on the system, it will be added to include and lib /// paths for the specified target. pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { + const b = self.step.owner; // Ideally in the Unattempted case we would call the function recursively // after findVcpkgRoot and have only one switch statement, but the compiler // cannot resolve the error set. - switch (self.builder.vcpkg_root) { + switch (b.vcpkg_root) { .unattempted => { - self.builder.vcpkg_root = if (try findVcpkgRoot(self.builder.allocator)) |root| + b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root| VcpkgRoot{ .found = root } else .not_found; @@ -1016,31 +1040,32 @@ pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { .found => {}, } - switch (self.builder.vcpkg_root) { + switch (b.vcpkg_root) { .unattempted => unreachable, .not_found => return error.VcpkgNotFound, .found => |root| { - const allocator = self.builder.allocator; + const allocator = b.allocator; const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic); - defer self.builder.allocator.free(triplet); + defer b.allocator.free(triplet); - const include_path = self.builder.pathJoin(&.{ root, "installed", triplet, "include" }); + const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" }); errdefer allocator.free(include_path); try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); - const lib_path = self.builder.pathJoin(&.{ root, "installed", triplet, "lib" }); + const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); try self.lib_paths.append(lib_path); - self.vcpkg_bin_path = self.builder.pathJoin(&.{ root, "installed", triplet, "bin" }); + self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" }); }, } } pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void { + const b = self.step.owner; assert(self.kind == .@"test"); - const duped_args = self.builder.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); + const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); for (args, 0..) |arg, i| { - duped_args[i] = if (arg) |a| self.builder.dupe(a) else null; + duped_args[i] = if (arg) |a| b.dupe(a) else null; } self.exec_cmd_args = duped_args; } @@ -1055,16 +1080,17 @@ fn appendModuleArgs( cs: *CompileStep, zig_args: *ArrayList([]const u8), ) error{OutOfMemory}!void { + const b = cs.step.owner; // First, traverse the whole dependency graph and give every module a unique name, ideally one // named after what it's called somewhere in the graph. It will help here to have both a mapping // from module to name and a set of all the currently-used names. - var mod_names = std.AutoHashMap(*Module, []const u8).init(cs.builder.allocator); - var names = std.StringHashMap(void).init(cs.builder.allocator); + var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator); + var names = std.StringHashMap(void).init(b.allocator); var to_name = std.ArrayList(struct { name: []const u8, mod: *Module, - }).init(cs.builder.allocator); + }).init(b.allocator); { var it = cs.modules.iterator(); while (it.next()) |kv| { @@ -1085,7 +1111,7 @@ fn appendModuleArgs( if (mod_names.contains(dep.mod)) continue; // We'll use this buffer to store the name we decide on - var buf = try cs.builder.allocator.alloc(u8, dep.name.len + 32); + var buf = try b.allocator.alloc(u8, dep.name.len + 32); // First, try just the exposed dependency name std.mem.copy(u8, buf, dep.name); var name = buf[0..dep.name.len]; @@ -1122,15 +1148,15 @@ fn appendModuleArgs( const mod = kv.key_ptr.*; const name = kv.value_ptr.*; - const deps_str = try constructDepString(cs.builder.allocator, mod_names, mod.dependencies); + const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies); const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder)); try zig_args.append("--mod"); - try zig_args.append(try std.fmt.allocPrint(cs.builder.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); + try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); } } // Lastly, output the root dependencies - const deps_str = try constructDepString(cs.builder.allocator, mod_names, cs.modules); + const deps_str = try constructDepString(b.allocator, mod_names, cs.modules); if (deps_str.len > 0) { try zig_args.append("--deps"); try zig_args.append(deps_str); @@ -1161,18 +1187,18 @@ fn constructDepString( } fn make(step: *Step, prog_node: *std.Progress.Node) !void { + const b = step.owner; const self = @fieldParentPtr(CompileStep, "step", step); - const builder = self.builder; if (self.root_src == null and self.link_objects.items.len == 0) { log.err("{s}: linker needs 1 or more objects to link", .{self.step.name}); return error.NeedAnObject; } - var zig_args = ArrayList([]const u8).init(builder.allocator); + var zig_args = ArrayList([]const u8).init(b.allocator); defer zig_args.deinit(); - try zig_args.append(builder.zig_exe); + try zig_args.append(b.zig_exe); const cmd = switch (self.kind) { .lib => "build-lib", @@ -1183,15 +1209,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }; try zig_args.append(cmd); - if (builder.reference_trace) |some| { - try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-freference-trace={d}", .{some})); + if (b.reference_trace) |some| { + try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some})); } try addFlag(&zig_args, "LLVM", self.use_llvm); try addFlag(&zig_args, "LLD", self.use_lld); if (self.target.ofmt) |ofmt| { - try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-ofmt={s}", .{@tagName(ofmt)})); + try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); } if (self.entry_symbol_name) |entry| { @@ -1201,18 +1227,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.stack_size) |stack_size| { try zig_args.append("--stack"); - try zig_args.append(try std.fmt.allocPrint(builder.allocator, "{}", .{stack_size})); + try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); } - if (self.root_src) |root_src| try zig_args.append(root_src.getPath(builder)); + if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b)); // We will add link objects from transitive dependencies, but we want to keep // all link objects in the same order provided. // This array is used to keep self.link_objects immutable. var transitive_deps: TransitiveDeps = .{ - .link_objects = ArrayList(LinkObject).init(builder.allocator), - .seen_system_libs = StringHashMap(void).init(builder.allocator), - .seen_steps = std.AutoHashMap(*const Step, void).init(builder.allocator), + .link_objects = ArrayList(LinkObject).init(b.allocator), + .seen_system_libs = StringHashMap(void).init(b.allocator), + .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator), .is_linking_libcpp = self.is_linking_libcpp, .is_linking_libc = self.is_linking_libc, .frameworks = &self.frameworks, @@ -1225,14 +1251,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { for (transitive_deps.link_objects.items) |link_object| { switch (link_object) { - .static_path => |static_path| try zig_args.append(static_path.getPath(builder)), + .static_path => |static_path| try zig_args.append(static_path.getPath(b)), .other_step => |other| switch (other.kind) { .exe => @panic("Cannot link with an executable build artifact"), .test_exe => @panic("Cannot link with an executable build artifact"), .@"test" => @panic("Cannot link with a test"), .obj => { - try zig_args.append(other.getOutputSource().getPath(builder)); + try zig_args.append(other.getOutputSource().getPath(b)); }, .lib => l: { if (self.isStaticLibrary() and other.isStaticLibrary()) { @@ -1240,7 +1266,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { break :l; } - const full_path_lib = other.getOutputLibSource().getPath(builder); + const full_path_lib = other.getOutputLibSource().getPath(b); try zig_args.append(full_path_lib); if (other.linkage == Linkage.dynamic and !self.target.isWindows()) { @@ -1262,7 +1288,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { break :prefix "-l"; }; switch (system_lib.use_pkg_config) { - .no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })), + .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), .yes, .force => { if (self.runPkgConfig(system_lib.name)) |args| { try zig_args.appendSlice(args); @@ -1276,7 +1302,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .yes => { // pkg-config failed, so fall back to linking the library // by name directly. - try zig_args.append(builder.fmt("{s}{s}", .{ + try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name, })); @@ -1299,7 +1325,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append("--"); prev_has_extra_flags = false; } - try zig_args.append(asm_file.getPath(builder)); + try zig_args.append(asm_file.getPath(b)); }, .c_source_file => |c_source_file| { @@ -1316,7 +1342,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } try zig_args.append("--"); } - try zig_args.append(c_source_file.source.getPath(builder)); + try zig_args.append(c_source_file.source.getPath(b)); }, .c_source_files => |c_source_files| { @@ -1334,7 +1360,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append("--"); } for (c_source_files.files) |file| { - try zig_args.append(builder.pathFromRoot(file)); + try zig_args.append(b.pathFromRoot(file)); } }, } @@ -1350,7 +1376,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.image_base) |image_base| { try zig_args.append("--image-base"); - try zig_args.append(builder.fmt("0x{x}", .{image_base})); + try zig_args.append(b.fmt("0x{x}", .{image_base})); } if (self.filter) |filter| { @@ -1369,32 +1395,32 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.test_runner) |test_runner| { try zig_args.append("--test-runner"); - try zig_args.append(builder.pathFromRoot(test_runner)); + try zig_args.append(b.pathFromRoot(test_runner)); } - for (builder.debug_log_scopes) |log_scope| { + for (b.debug_log_scopes) |log_scope| { try zig_args.append("--debug-log"); try zig_args.append(log_scope); } - if (builder.debug_compile_errors) { + if (b.debug_compile_errors) { try zig_args.append("--debug-compile-errors"); } - if (builder.verbose_cimport) try zig_args.append("--verbose-cimport"); - if (builder.verbose_air) try zig_args.append("--verbose-air"); - if (builder.verbose_llvm_ir) try zig_args.append("--verbose-llvm-ir"); - if (builder.verbose_link or self.verbose_link) try zig_args.append("--verbose-link"); - if (builder.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc"); - if (builder.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); + if (b.verbose_cimport) try zig_args.append("--verbose-cimport"); + if (b.verbose_air) try zig_args.append("--verbose-air"); + if (b.verbose_llvm_ir) try zig_args.append("--verbose-llvm-ir"); + if (b.verbose_link or self.verbose_link) try zig_args.append("--verbose-link"); + if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc"); + if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); - if (self.emit_analysis.getArg(builder, "emit-analysis")) |arg| try zig_args.append(arg); - if (self.emit_asm.getArg(builder, "emit-asm")) |arg| try zig_args.append(arg); - if (self.emit_bin.getArg(builder, "emit-bin")) |arg| try zig_args.append(arg); - if (self.emit_docs.getArg(builder, "emit-docs")) |arg| try zig_args.append(arg); - if (self.emit_implib.getArg(builder, "emit-implib")) |arg| try zig_args.append(arg); - if (self.emit_llvm_bc.getArg(builder, "emit-llvm-bc")) |arg| try zig_args.append(arg); - if (self.emit_llvm_ir.getArg(builder, "emit-llvm-ir")) |arg| try zig_args.append(arg); + if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg); + if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg); + if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg); + if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg); + if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg); + if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg); + if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg); if (self.emit_h) try zig_args.append("-femit-h"); @@ -1435,31 +1461,31 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } if (self.link_z_common_page_size) |size| { try zig_args.append("-z"); - try zig_args.append(builder.fmt("common-page-size={d}", .{size})); + try zig_args.append(b.fmt("common-page-size={d}", .{size})); } if (self.link_z_max_page_size) |size| { try zig_args.append("-z"); - try zig_args.append(builder.fmt("max-page-size={d}", .{size})); + try zig_args.append(b.fmt("max-page-size={d}", .{size})); } if (self.libc_file) |libc_file| { try zig_args.append("--libc"); - try zig_args.append(libc_file.getPath(builder)); - } else if (builder.libc_file) |libc_file| { + try zig_args.append(libc_file.getPath(b)); + } else if (b.libc_file) |libc_file| { try zig_args.append("--libc"); try zig_args.append(libc_file); } switch (self.optimize) { .Debug => {}, // Skip since it's the default. - else => try zig_args.append(builder.fmt("-O{s}", .{@tagName(self.optimize)})), + else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), } try zig_args.append("--cache-dir"); - try zig_args.append(builder.cache_root.path orelse "."); + try zig_args.append(b.cache_root.path orelse "."); try zig_args.append("--global-cache-dir"); - try zig_args.append(builder.global_cache_root.path orelse "."); + try zig_args.append(b.global_cache_root.path orelse "."); try zig_args.append("--name"); try zig_args.append(self.name); @@ -1471,11 +1497,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) { if (self.version) |version| { try zig_args.append("--version"); - try zig_args.append(builder.fmt("{}", .{version})); + try zig_args.append(b.fmt("{}", .{version})); } if (self.target.isDarwin()) { - const install_name = self.install_name orelse builder.fmt("@rpath/{s}{s}{s}", .{ + const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ self.target.libPrefix(), self.name, self.target.dynamicLibSuffix(), @@ -1489,7 +1515,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); } if (self.pagezero_size) |pagezero_size| { - const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size}); + const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size}); try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); } if (self.search_strategy) |strat| switch (strat) { @@ -1497,7 +1523,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .dylibs_first => try zig_args.append("-search_dylibs_first"), }; if (self.headerpad_size) |headerpad_size| { - const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size}); + const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size}); try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); } if (self.headerpad_max_install_names) { @@ -1545,16 +1571,16 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append("--export-table"); } if (self.initial_memory) |initial_memory| { - try zig_args.append(builder.fmt("--initial-memory={d}", .{initial_memory})); + try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory})); } if (self.max_memory) |max_memory| { - try zig_args.append(builder.fmt("--max-memory={d}", .{max_memory})); + try zig_args.append(b.fmt("--max-memory={d}", .{max_memory})); } if (self.shared_memory) { try zig_args.append("--shared-memory"); } if (self.global_base) |global_base| { - try zig_args.append(builder.fmt("--global-base={d}", .{global_base})); + try zig_args.append(b.fmt("--global-base={d}", .{global_base})); } if (self.code_model != .default) { @@ -1562,16 +1588,16 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(@tagName(self.code_model)); } if (self.wasi_exec_model) |model| { - try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)})); + try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); } for (self.export_symbol_names) |symbol_name| { - try zig_args.append(builder.fmt("--export={s}", .{symbol_name})); + try zig_args.append(b.fmt("--export={s}", .{symbol_name})); } if (!self.target.isNative()) { try zig_args.appendSlice(&.{ - "-target", try self.target.zigTriple(builder.allocator), - "-mcpu", try std.Build.serializeCpu(builder.allocator, self.target.getCpu()), + "-target", try self.target.zigTriple(b.allocator), + "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()), }); if (self.target.dynamic_linker.get()) |dynamic_linker| { @@ -1582,12 +1608,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.linker_script) |linker_script| { try zig_args.append("--script"); - try zig_args.append(linker_script.getPath(builder)); + try zig_args.append(linker_script.getPath(b)); } if (self.version_script) |version_script| { try zig_args.append("--version-script"); - try zig_args.append(builder.pathFromRoot(version_script)); + try zig_args.append(b.pathFromRoot(version_script)); } if (self.kind == .@"test") { @@ -1603,23 +1629,23 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } else { const need_cross_glibc = self.target.isGnuLibC() and transitive_deps.is_linking_libc; - switch (builder.host.getExternalExecutor(self.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, + switch (b.host.getExternalExecutor(self.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, .link_libc = transitive_deps.is_linking_libc, })) { .native => {}, .bad_dl, .bad_os_or_cpu => { try zig_args.append("--test-no-exec"); }, - .rosetta => if (builder.enable_rosetta) { + .rosetta => if (b.enable_rosetta) { try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); }, .qemu => |bin_name| ok: { - if (builder.enable_qemu) qemu: { + if (b.enable_qemu) qemu: { const glibc_dir_arg = if (need_cross_glibc) - builder.glibc_runtimes_dir orelse break :qemu + b.glibc_runtimes_dir orelse break :qemu else null; try zig_args.append("--test-cmd"); @@ -1636,7 +1662,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { "i686" else @tagName(cpu_arch); - const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{ + const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), }); @@ -1650,14 +1676,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } try zig_args.append("--test-no-exec"); }, - .wine => |bin_name| if (builder.enable_wine) { + .wine => |bin_name| if (b.enable_wine) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd-bin"); } else { try zig_args.append("--test-no-exec"); }, - .wasmtime => |bin_name| if (builder.enable_wasmtime) { + .wasmtime => |bin_name| if (b.enable_wasmtime) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd"); @@ -1666,7 +1692,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } else { try zig_args.append("--test-no-exec"); }, - .darling => |bin_name| if (builder.enable_darling) { + .darling => |bin_name| if (b.enable_darling) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); try zig_args.append("--test-cmd-bin"); @@ -1685,18 +1711,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { switch (include_dir) { .raw_path => |include_path| { try zig_args.append("-I"); - try zig_args.append(builder.pathFromRoot(include_path)); + try zig_args.append(b.pathFromRoot(include_path)); }, .raw_path_system => |include_path| { - if (builder.sysroot != null) { + if (b.sysroot != null) { try zig_args.append("-iwithsysroot"); } else { try zig_args.append("-isystem"); } - const resolved_include_path = builder.pathFromRoot(include_path); + const resolved_include_path = b.pathFromRoot(include_path); - const common_include_path = if (builtin.os.tag == .windows and builder.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { + const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { // We need to check for disk designator and strip it out from dir path so // that zig/clang can concat resolved_include_path with sysroot. const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path); @@ -1712,7 +1738,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }, .other_step => |other| { if (other.emit_h) { - const h_path = other.getOutputHSource().getPath(builder); + const h_path = other.getOutputHSource().getPath(b); try zig_args.append("-isystem"); try zig_args.append(fs.path.dirname(h_path).?); } @@ -1721,8 +1747,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try install_step.make(prog_node); } try zig_args.append("-I"); - try zig_args.append(builder.pathJoin(&.{ - other.builder.install_prefix, "include", + try zig_args.append(b.pathJoin(&.{ + other.step.owner.install_prefix, "include", })); } }, @@ -1751,7 +1777,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.target.isDarwin()) { for (self.framework_dirs.items) |dir| { - if (builder.sysroot != null) { + if (b.sysroot != null) { try zig_args.append("-iframeworkwithsysroot"); } else { try zig_args.append("-iframework"); @@ -1784,17 +1810,17 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - if (builder.sysroot) |sysroot| { + if (b.sysroot) |sysroot| { try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); } - for (builder.search_prefixes.items) |search_prefix| { + for (b.search_prefixes.items) |search_prefix| { try zig_args.append("-L"); - try zig_args.append(builder.pathJoin(&.{ + try zig_args.append(b.pathJoin(&.{ search_prefix, "lib", })); try zig_args.append("-I"); - try zig_args.append(builder.pathJoin(&.{ + try zig_args.append(b.pathJoin(&.{ search_prefix, "include", })); } @@ -1805,15 +1831,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (self.zig_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); - try zig_args.append(builder.pathFromRoot(dir)); - } else if (builder.zig_lib_dir) |dir| { + try zig_args.append(b.pathFromRoot(dir)); + } else if (b.zig_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); try zig_args.append(dir); } if (self.main_pkg_path) |dir| { try zig_args.append("--main-pkg-path"); - try zig_args.append(builder.pathFromRoot(dir)); + try zig_args.append(b.pathFromRoot(dir)); } try addFlag(&zig_args, "PIC", self.force_pic); @@ -1846,15 +1872,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { args_length += arg.len + 1; // +1 to account for null terminator } if (args_length >= 30 * 1024) { - try builder.cache_root.handle.makePath("args"); + try b.cache_root.handle.makePath("args"); const args_to_escape = zig_args.items[2..]; - var escaped_args = try ArrayList([]const u8).initCapacity(builder.allocator, args_to_escape.len); + var escaped_args = try ArrayList([]const u8).initCapacity(b.allocator, args_to_escape.len); arg_blk: for (args_to_escape) |arg| { for (arg, 0..) |c, arg_idx| { if (c == '\\' or c == '"') { // Slow path for arguments that need to be escaped. We'll need to allocate and copy - var escaped = try ArrayList(u8).initCapacity(builder.allocator, arg.len + 1); + var escaped = try ArrayList(u8).initCapacity(b.allocator, arg.len + 1); const writer = escaped.writer(); try writer.writeAll(arg[0..arg_idx]); for (arg[arg_idx..]) |to_escape| { @@ -1870,8 +1896,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // Write the args to zig-cache/args/ to avoid conflicts with // other zig build commands running in parallel. - const partially_quoted = try std.mem.join(builder.allocator, "\" \"", escaped_args.items); - const args = try std.mem.concat(builder.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); + const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items); + const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); var args_hash: [Sha256.digest_length]u8 = undefined; Sha256.hash(args, &args_hash, .{}); @@ -1883,18 +1909,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { ); const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; - try builder.cache_root.handle.writeFile(args_file, args); + try b.cache_root.handle.writeFile(args_file, args); - const resolved_args_file = try mem.concat(builder.allocator, u8, &.{ + const resolved_args_file = try mem.concat(b.allocator, u8, &.{ "@", - try builder.cache_root.join(builder.allocator, &.{args_file}), + try b.cache_root.join(b.allocator, &.{args_file}), }); zig_args.shrinkRetainingCapacity(2); try zig_args.append(resolved_args_file); } - const output_bin_path = try builder.execFromStep(zig_args.items, &self.step, prog_node); + const output_bin_path = try step.evalZigProcess(zig_args.items, prog_node); const build_output_dir = fs.path.dirname(output_bin_path).?; if (self.output_dir) |output_dir| { @@ -1928,25 +1954,25 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // Update generated files if (self.output_dir != null) { - self.output_path_source.path = builder.pathJoin( + self.output_path_source.path = b.pathJoin( &.{ self.output_dir.?, self.out_filename }, ); if (self.emit_h) { - self.output_h_path_source.path = builder.pathJoin( + self.output_h_path_source.path = b.pathJoin( &.{ self.output_dir.?, self.out_h_filename }, ); } if (self.target.isWindows() or self.target.isUefi()) { - self.output_pdb_path_source.path = builder.pathJoin( + self.output_pdb_path_source.path = b.pathJoin( &.{ self.output_dir.?, self.out_pdb_filename }, ); } } if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and self.version != null and self.target.wantSharedLibSymLinks()) { - try doAtomicSymLinks(builder.allocator, self.getOutputSource().getPath(builder), self.major_only_filename.?, self.name_only_filename.?); + try doAtomicSymLinks(b.allocator, self.getOutputSource().getPath(b), self.major_only_filename.?, self.name_only_filename.?); } } diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index 994b71da32..8b4c05bab7 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -34,7 +34,6 @@ pub const Value = union(enum) { }; step: Step, -builder: *std.Build, values: std.StringArrayHashMap(Value), output_file: std.Build.GeneratedFile, @@ -49,8 +48,8 @@ pub const Options = struct { first_ret_addr: ?usize = null, }; -pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep { - const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM"); +pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep { + const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM"); var include_path: []const u8 = "config.h"; @@ -69,29 +68,28 @@ pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep { } const name = if (options.style.getFileSource()) |s| - builder.fmt("configure {s} header {s} to {s}", .{ + owner.fmt("configure {s} header {s} to {s}", .{ @tagName(options.style), s.getDisplayName(), include_path, }) else - builder.fmt("configure {s} header to {s}", .{@tagName(options.style), include_path}); + owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path }); self.* = .{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = base_id, .name = name, + .owner = owner, .makeFn = make, .first_ret_addr = options.first_ret_addr orelse @returnAddress(), }), .style = options.style, - .values = std.StringArrayHashMap(Value).init(builder.allocator), + .values = std.StringArrayHashMap(Value).init(owner.allocator), .max_bytes = options.max_bytes, .include_path = include_path, .output_file = .{ .step = &self.step }, }; - return self; } @@ -161,8 +159,9 @@ fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; + const b = step.owner; const self = @fieldParentPtr(ConfigHeaderStep, "step", step); - const gpa = self.builder.allocator; + const gpa = b.allocator; // The cache is used here not really as a way to speed things up - because writing // the data to a file would probably be very fast - but as a way to find a canonical @@ -191,13 +190,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { switch (self.style) { .autoconf => |file_source| { try output.appendSlice(c_generated_line); - const src_path = file_source.getPath(self.builder); + const src_path = file_source.getPath(b); const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); try render_autoconf(contents, &output, self.values, src_path); }, .cmake => |file_source| { try output.appendSlice(c_generated_line); - const src_path = file_source.getPath(self.builder); + const src_path = file_source.getPath(b); const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); try render_cmake(contents, &output, self.values, src_path); }, @@ -222,7 +221,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .{std.fmt.fmtSliceHexLower(&digest)}, ) catch unreachable; - const output_dir = try self.builder.cache_root.join(gpa, &.{ "o", &hash_basename }); + const output_dir = try b.cache_root.join(gpa, &.{ "o", &hash_basename }); // If output_path has directory parts, deal with them. Example: // output_dir is zig-cache/o/HASH @@ -242,7 +241,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try dir.writeFile(std.fs.path.basename(self.include_path), output.items); - self.output_file.path = try std.fs.path.join(self.builder.allocator, &.{ + self.output_file.path = try std.fs.path.join(b.allocator, &.{ output_dir, self.include_path, }); } diff --git a/lib/std/Build/EmulatableRunStep.zig b/lib/std/Build/EmulatableRunStep.zig deleted file mode 100644 index 44387c36f6..0000000000 --- a/lib/std/Build/EmulatableRunStep.zig +++ /dev/null @@ -1,218 +0,0 @@ -//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries. -//! When a binary is foreign, but emulation for the target is disabled, the specified binary -//! will not be run and therefore also not validated against its output. -//! This step can be useful when wishing to run a built binary on multiple platforms, -//! without having to verify if it's possible to be ran against. - -const std = @import("../std.zig"); -const Step = std.Build.Step; -const CompileStep = std.Build.CompileStep; -const RunStep = std.Build.RunStep; - -const fs = std.fs; -const process = std.process; -const EnvMap = process.EnvMap; - -const EmulatableRunStep = @This(); - -pub const base_id = .emulatable_run; - -const max_stdout_size = 1 * 1024 * 1024; // 1 MiB - -step: Step, -builder: *std.Build, - -/// The artifact (executable) to be run by this step -exe: *CompileStep, - -/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution -expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 }, - -/// Override this field to modify the environment -env_map: ?*EnvMap, - -/// Set this to modify the current working directory -cwd: ?[]const u8, - -stdout_action: RunStep.StdIoAction = .inherit, -stderr_action: RunStep.StdIoAction = .inherit, - -/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host -/// or through emulation. -hide_foreign_binaries_warning: bool, - -/// Creates a step that will execute the given artifact. This step will allow running the -/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true. -/// When set to false, and the binary is foreign, running the executable is skipped. -/// Asserts given artifact is an executable. -pub fn create(builder: *std.Build, name: []const u8, artifact: *CompileStep) *EmulatableRunStep { - std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe); - const self = builder.allocator.create(EmulatableRunStep) catch @panic("OOM"); - - const option_name = "hide-foreign-warnings"; - const hide_warnings = if (builder.available_options_map.get(option_name) == null) warn: { - break :warn builder.option(bool, option_name, "Hide the warning when a foreign binary which is incompatible is skipped") orelse false; - } else false; - - self.* = .{ - .builder = builder, - .step = Step.init(builder.allocator, .{ - .id = .emulatable_run, - .name = name, - .makeFn = make, - }), - .exe = artifact, - .env_map = null, - .cwd = null, - .hide_foreign_binaries_warning = hide_warnings, - }; - self.step.dependOn(&artifact.step); - - return self; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(EmulatableRunStep, "step", step); - const host_info = self.builder.host; - - var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); - defer argv_list.deinit(); - - const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc; - switch (host_info.getExternalExecutor(self.exe.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null, - .link_libc = self.exe.is_linking_libc, - })) { - .native => {}, - .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self), - .wine => |bin_name| if (self.builder.enable_wine) { - try argv_list.append(bin_name); - } else return, - .qemu => |bin_name| if (self.builder.enable_qemu) { - const glibc_dir_arg = if (need_cross_glibc) - self.builder.glibc_runtimes_dir orelse return - else - null; - try argv_list.append(bin_name); - if (glibc_dir_arg) |dir| { - // TODO look into making this a call to `linuxTriple`. This - // needs the directory to be called "i686" rather than - // "x86" which is why we do it manually here. - const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; - const cpu_arch = self.exe.target.getCpuArch(); - const os_tag = self.exe.target.getOsTag(); - const abi = self.exe.target.getAbi(); - const cpu_arch_name: []const u8 = if (cpu_arch == .x86) - "i686" - else - @tagName(cpu_arch); - const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{ - dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), - }); - - try argv_list.append("-L"); - try argv_list.append(full_dir); - } - } else return warnAboutForeignBinaries(self), - .darling => |bin_name| if (self.builder.enable_darling) { - try argv_list.append(bin_name); - } else return warnAboutForeignBinaries(self), - .wasmtime => |bin_name| if (self.builder.enable_wasmtime) { - try argv_list.append(bin_name); - try argv_list.append("--dir=."); - } else return warnAboutForeignBinaries(self), - else => return warnAboutForeignBinaries(self), - } - - if (self.exe.target.isWindows()) { - // On Windows we don't have rpaths so we have to add .dll search paths to PATH - RunStep.addPathForDynLibsInternal(&self.step, self.builder, self.exe); - } - - const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder); - try argv_list.append(executable_path); - - try RunStep.runCommand( - argv_list.items, - self.builder, - self.expected_term, - self.stdout_action, - self.stderr_action, - .Inherit, - self.env_map, - self.cwd, - false, - ); -} - -pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void { - self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; -} - -pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void { - self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; -} - -fn warnAboutForeignBinaries(step: *EmulatableRunStep) void { - if (step.hide_foreign_binaries_warning) return; - const builder = step.builder; - const artifact = step.exe; - - const host_name = builder.host.target.zigTriple(builder.allocator) catch @panic("unhandled error"); - const foreign_name = artifact.target.zigTriple(builder.allocator) catch @panic("unhandled error"); - const target_info = std.zig.system.NativeTargetInfo.detect(artifact.target) catch @panic("unhandled error"); - const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc; - switch (builder.host.getExternalExecutor(target_info, .{ - .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null, - .link_libc = artifact.is_linking_libc, - })) { - .native => unreachable, - .bad_dl => |foreign_dl| { - const host_dl = builder.host.dynamic_linker.get() orelse "(none)"; - std.debug.print("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider setting the dynamic linker as '{s}'.\n", .{ - host_dl, foreign_dl, host_dl, - }); - }, - .bad_os_or_cpu => { - std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{ - host_name, foreign_name, - }); - }, - .darling => if (!builder.enable_darling) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling darling.\n", - .{ host_name, foreign_name }, - ); - }, - .rosetta => if (!builder.enable_rosetta) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling rosetta.\n", - .{ host_name, foreign_name }, - ); - }, - .wine => if (!builder.enable_wine) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling wine.\n", - .{ host_name, foreign_name }, - ); - }, - .qemu => if (!builder.enable_qemu) { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling qemu.\n", - .{ host_name, foreign_name }, - ); - }, - .wasmtime => { - std.debug.print( - "the host system ({s}) does not appear to be capable of executing binaries " ++ - "from the target ({s}). Consider enabling wasmtime.\n", - .{ host_name, foreign_name }, - ); - }, - } -} diff --git a/lib/std/Build/FmtStep.zig b/lib/std/Build/FmtStep.zig index 5efada7507..2a82342336 100644 --- a/lib/std/Build/FmtStep.zig +++ b/lib/std/Build/FmtStep.zig @@ -1,37 +1,73 @@ -const std = @import("../std.zig"); -const Step = std.Build.Step; -const FmtStep = @This(); +//! This step has two modes: +//! * Modify mode: directly modify source files, formatting them in place. +//! * Check mode: fail the step if a non-conforming file is found. + +step: Step, +paths: []const []const u8, +exclude_paths: []const []const u8, +check: bool, pub const base_id = .fmt; -step: Step, -builder: *std.Build, -argv: [][]const u8, +pub const Options = struct { + paths: []const []const u8 = &.{}, + exclude_paths: []const []const u8 = &.{}, + /// If true, fails the build step when any non-conforming files are encountered. + check: bool = false, +}; -pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep { - const self = builder.allocator.create(FmtStep) catch @panic("OOM"); - const name = "zig fmt"; - self.* = FmtStep{ - .step = Step.init(builder.allocator, .{ - .id = .fmt, +pub fn create(owner: *std.Build, options: Options) *FmtStep { + const self = owner.allocator.create(FmtStep) catch @panic("OOM"); + const name = if (options.check) "zig fmt --check" else "zig fmt"; + self.* = .{ + .step = Step.init(.{ + .id = base_id, .name = name, + .owner = owner, .makeFn = make, }), - .builder = builder, - .argv = builder.allocator.alloc([]u8, paths.len + 2) catch @panic("OOM"), + .paths = options.paths, + .exclude_paths = options.exclude_paths, + .check = options.check, }; - - self.argv[0] = builder.zig_exe; - self.argv[1] = "fmt"; - for (paths, 0..) |path, i| { - self.argv[2 + i] = builder.pathFromRoot(path); - } return self; } fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // zig fmt is fast enough that no progress is needed. _ = prog_node; + + // TODO: if check=false, this means we are modifying source files in place, which + // is an operation that could race against other operations also modifying source files + // in place. In this case, this step should obtain a write lock while making those + // modifications. + + const b = step.owner; + const arena = b.allocator; const self = @fieldParentPtr(FmtStep, "step", step); - return self.builder.spawnChild(self.argv); + var argv: std.ArrayListUnmanaged([]const u8) = .{}; + try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len); + + argv.appendAssumeCapacity(b.zig_exe); + argv.appendAssumeCapacity("fmt"); + + if (self.check) { + argv.appendAssumeCapacity("--check"); + } + + for (self.paths) |p| { + argv.appendAssumeCapacity(b.pathFromRoot(p)); + } + + for (self.exclude_paths) |p| { + argv.appendAssumeCapacity("--exclude"); + argv.appendAssumeCapacity(b.pathFromRoot(p)); + } + + return step.evalChildProcess(argv.items); } + +const std = @import("../std.zig"); +const Step = std.Build.Step; +const FmtStep = @This(); diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index d8907eb59f..7e35d0a5ee 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -7,23 +7,24 @@ const InstallArtifactStep = @This(); pub const base_id = .install_artifact; step: Step, -builder: *std.Build, +dest_builder: *std.Build, artifact: *CompileStep, dest_dir: InstallDir, pdb_dir: ?InstallDir, h_dir: ?InstallDir, -pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep { +pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { if (artifact.install_step) |s| return s; - const self = builder.allocator.create(InstallArtifactStep) catch @panic("OOM"); + const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM"); self.* = InstallArtifactStep{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = base_id, - .name = builder.fmt("install {s}", .{artifact.name}), + .name = owner.fmt("install {s}", .{artifact.name}), + .owner = owner, .makeFn = make, }), + .dest_builder = owner, .artifact = artifact, .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { .obj => @panic("Cannot install a .obj build artifact."), @@ -43,48 +44,52 @@ pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep self.step.dependOn(&artifact.step); artifact.install_step = self; - builder.pushInstalledFile(self.dest_dir, artifact.out_filename); + owner.pushInstalledFile(self.dest_dir, artifact.out_filename); if (self.artifact.isDynamicLibrary()) { if (artifact.major_only_filename) |name| { - builder.pushInstalledFile(.lib, name); + owner.pushInstalledFile(.lib, name); } if (artifact.name_only_filename) |name| { - builder.pushInstalledFile(.lib, name); + owner.pushInstalledFile(.lib, name); } if (self.artifact.target.isWindows()) { - builder.pushInstalledFile(.lib, artifact.out_lib_filename); + owner.pushInstalledFile(.lib, artifact.out_lib_filename); } } if (self.pdb_dir) |pdb_dir| { - builder.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); + owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); } if (self.h_dir) |h_dir| { - builder.pushInstalledFile(h_dir, artifact.out_h_filename); + owner.pushInstalledFile(h_dir, artifact.out_h_filename); } return self; } fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; + const src_builder = step.owner; const self = @fieldParentPtr(InstallArtifactStep, "step", step); - const builder = self.builder; + const dest_builder = self.dest_builder; - const full_dest_path = builder.getInstallPath(self.dest_dir, self.artifact.out_filename); - try builder.updateFile(self.artifact.getOutputSource().getPath(builder), full_dest_path); + const full_dest_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_filename); + try src_builder.updateFile( + self.artifact.getOutputSource().getPath(src_builder), + full_dest_path, + ); if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) { - try CompileStep.doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); + try CompileStep.doAtomicSymLinks(src_builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); } if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) { - const full_implib_path = builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); - try builder.updateFile(self.artifact.getOutputLibSource().getPath(builder), full_implib_path); + const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); + try src_builder.updateFile(self.artifact.getOutputLibSource().getPath(src_builder), full_implib_path); } if (self.pdb_dir) |pdb_dir| { - const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); - try builder.updateFile(self.artifact.getOutputPdbSource().getPath(builder), full_pdb_path); + const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); + try src_builder.updateFile(self.artifact.getOutputPdbSource().getPath(src_builder), full_pdb_path); } if (self.h_dir) |h_dir| { - const full_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename); - try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path); + const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename); + try src_builder.updateFile(self.artifact.getOutputHSource().getPath(src_builder), full_h_path); } self.artifact.installed_path = full_dest_path; } diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig index bf89d9e7c7..11553d9bc7 100644 --- a/lib/std/Build/InstallDirStep.zig +++ b/lib/std/Build/InstallDirStep.zig @@ -7,11 +7,10 @@ const InstallDirStep = @This(); const log = std.log; step: Step, -builder: *std.Build, options: Options, /// This is used by the build system when a file being installed comes from one /// package but is being installed by another. -override_source_builder: ?*std.Build = null, +dest_builder: *std.Build, pub const base_id = .install_dir; @@ -40,27 +39,26 @@ pub const Options = struct { } }; -pub fn init( - builder: *std.Build, - options: Options, -) InstallDirStep { - builder.pushInstalledFile(options.install_dir, options.install_subdir); +pub fn init(owner: *std.Build, options: Options) InstallDirStep { + owner.pushInstalledFile(options.install_dir, options.install_subdir); return .{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = .install_dir, - .name = builder.fmt("install {s}/", .{options.source_dir}), + .name = owner.fmt("install {s}/", .{options.source_dir}), + .owner = owner, .makeFn = make, }), - .options = options.dupe(builder), + .options = options.dupe(owner), + .dest_builder = owner, }; } fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const self = @fieldParentPtr(InstallDirStep, "step", step); - const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir); - const src_builder = self.override_source_builder orelse self.builder; + const dest_builder = self.dest_builder; + const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir); + const src_builder = self.step.owner; const full_src_dir = src_builder.pathFromRoot(self.options.source_dir); var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| { log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{ @@ -69,7 +67,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { return error.StepFailed; }; defer src_dir.close(); - var it = try src_dir.walk(self.builder.allocator); + var it = try src_dir.walk(dest_builder.allocator); next_entry: while (try it.next()) |entry| { for (self.options.exclude_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) { @@ -77,20 +75,20 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path }); - const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path }); + const full_path = dest_builder.pathJoin(&.{ full_src_dir, entry.path }); + const dest_path = dest_builder.pathJoin(&.{ dest_prefix, entry.path }); switch (entry.kind) { .Directory => try fs.cwd().makePath(dest_path), .File => { for (self.options.blank_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) { - try self.builder.truncateFile(dest_path); + try dest_builder.truncateFile(dest_path); continue :next_entry; } } - try self.builder.updateFile(full_path, dest_path); + try dest_builder.updateFile(full_path, dest_path); }, else => continue, } diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig index f77b22c112..ed7576f42c 100644 --- a/lib/std/Build/InstallFileStep.zig +++ b/lib/std/Build/InstallFileStep.zig @@ -7,39 +7,40 @@ const InstallFileStep = @This(); pub const base_id = .install_file; step: Step, -builder: *std.Build, source: FileSource, dir: InstallDir, dest_rel_path: []const u8, /// This is used by the build system when a file being installed comes from one /// package but is being installed by another. -override_source_builder: ?*std.Build = null, +dest_builder: *std.Build, pub fn init( - builder: *std.Build, + owner: *std.Build, source: FileSource, dir: InstallDir, dest_rel_path: []const u8, ) InstallFileStep { - builder.pushInstalledFile(dir, dest_rel_path); + owner.pushInstalledFile(dir, dest_rel_path); return InstallFileStep{ - .builder = builder, - .step = Step.init(builder.allocator, .{ - .id = .install_file, - .name = builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), + .step = Step.init(.{ + .id = base_id, + .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), + .owner = owner, .makeFn = make, }), - .source = source.dupe(builder), - .dir = dir.dupe(builder), - .dest_rel_path = builder.dupePath(dest_rel_path), + .source = source.dupe(owner), + .dir = dir.dupe(owner), + .dest_rel_path = owner.dupePath(dest_rel_path), + .dest_builder = owner, }; } fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; + const src_builder = step.owner; const self = @fieldParentPtr(InstallFileStep, "step", step); - const src_builder = self.override_source_builder orelse self.builder; + const dest_builder = self.dest_builder; const full_src_path = self.source.getPath2(src_builder, step); - const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path); - try self.builder.updateFile(full_src_path, full_dest_path); + const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path); + try dest_builder.updateFile(full_src_path, full_dest_path); } diff --git a/lib/std/Build/LogStep.zig b/lib/std/Build/LogStep.zig deleted file mode 100644 index 25bba747bf..0000000000 --- a/lib/std/Build/LogStep.zig +++ /dev/null @@ -1,28 +0,0 @@ -const std = @import("../std.zig"); -const log = std.log; -const Step = std.Build.Step; -const LogStep = @This(); - -pub const base_id = .log; - -step: Step, -builder: *std.Build, -data: []const u8, - -pub fn init(builder: *std.Build, data: []const u8) LogStep { - return LogStep{ - .builder = builder, - .step = Step.init(builder.allocator, .{ - .id = .log, - .name = builder.fmt("log {s}", .{data}), - .makeFn = make, - }), - .data = builder.dupe(data), - }; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void { - _ = prog_node; - const self = @fieldParentPtr(LogStep, "step", step); - log.info("{s}", .{self.data}); -} diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index 839d95903c..7199431ee6 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -21,7 +21,6 @@ pub const RawFormat = enum { }; step: Step, -builder: *std.Build, file_source: std.Build.FileSource, basename: []const u8, output_file: std.Build.GeneratedFile, @@ -38,18 +37,18 @@ pub const Options = struct { }; pub fn create( - builder: *std.Build, + owner: *std.Build, file_source: std.Build.FileSource, options: Options, ) *ObjCopyStep { - const self = builder.allocator.create(ObjCopyStep) catch @panic("OOM"); + const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM"); self.* = ObjCopyStep{ - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = base_id, - .name = builder.fmt("objcopy {s}", .{file_source.getDisplayName()}), + .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}), + .owner = owner, .makeFn = make, }), - .builder = builder, .file_source = file_source, .basename = options.basename orelse file_source.getDisplayName(), .output_file = std.Build.GeneratedFile{ .step = &self.step }, @@ -67,9 +66,8 @@ pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource { } fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; + const b = step.owner; const self = @fieldParentPtr(ObjCopyStep, "step", step); - const b = self.builder; var man = b.cache.obtain(); defer man.deinit(); @@ -84,7 +82,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { man.hash.addOptional(self.pad_to); man.hash.addOptional(self.format); - if (man.hit() catch |err| failWithCacheError(man, err)) { + if (try step.cacheHit(&man)) { // Cache hit, skip subprocess execution. const digest = man.final(); self.output_file.path = try b.cache_root.join(b.allocator, &.{ @@ -116,23 +114,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }; try argv.appendSlice(&.{ full_src_path, full_dest_path }); - _ = try self.builder.execFromStep(argv.items, &self.step); + _ = try step.spawnZigProcess(argv.items, prog_node); self.output_file.path = full_dest_path; try man.writeManifest(); } - -/// TODO consolidate this with the same function in RunStep? -/// Also properly deal with concurrency (see open PR) -fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn { - const i = man.failed_file_index orelse failWithSimpleError(err); - const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err); - const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; - std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path }); - std.process.exit(1); -} - -fn failWithSimpleError(err: anyerror) noreturn { - std.debug.print("{s}\n", .{@errorName(err)}); - std.process.exit(1); -} diff --git a/lib/std/Build/OptionsStep.zig b/lib/std/Build/OptionsStep.zig index 2cb3bb13be..859d0b68c9 100644 --- a/lib/std/Build/OptionsStep.zig +++ b/lib/std/Build/OptionsStep.zig @@ -12,25 +12,24 @@ pub const base_id = .options; step: Step, generated_file: GeneratedFile, -builder: *std.Build, contents: std.ArrayList(u8), artifact_args: std.ArrayList(OptionArtifactArg), file_source_args: std.ArrayList(OptionFileSourceArg), -pub fn create(builder: *std.Build) *OptionsStep { - const self = builder.allocator.create(OptionsStep) catch @panic("OOM"); +pub fn create(owner: *std.Build) *OptionsStep { + const self = owner.allocator.create(OptionsStep) catch @panic("OOM"); self.* = .{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = base_id, .name = "options", + .owner = owner, .makeFn = make, }), .generated_file = undefined, - .contents = std.ArrayList(u8).init(builder.allocator), - .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator), - .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator), + .contents = std.ArrayList(u8).init(owner.allocator), + .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator), + .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator), }; self.generated_file = .{ .step = &self.step }; @@ -196,7 +195,7 @@ pub fn addOptionFileSource( ) void { self.file_source_args.append(.{ .name = name, - .source = source.dupe(self.builder), + .source = source.dupe(self.step.owner), }) catch @panic("OOM"); source.addStepDependencies(&self.step); } @@ -204,12 +203,12 @@ pub fn addOptionFileSource( /// The value is the path in the cache dir. /// Adds a dependency automatically. pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void { - self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch @panic("OOM"); + self.artifact_args.append(.{ .name = self.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM"); self.step.dependOn(&artifact.step); } pub fn createModule(self: *OptionsStep) *std.Build.Module { - return self.builder.createModule(.{ + return self.step.owner.createModule(.{ .source_file = self.getSource(), .dependencies = &.{}, }); @@ -220,14 +219,17 @@ pub fn getSource(self: *OptionsStep) FileSource { } fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // This step completes so quickly that no progress is necessary. _ = prog_node; + + const b = step.owner; const self = @fieldParentPtr(OptionsStep, "step", step); for (self.artifact_args.items) |item| { self.addOption( []const u8, item.name, - self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)), + b.pathFromRoot(item.artifact.getOutputSource().getPath(b)), ); } @@ -235,20 +237,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { self.addOption( []const u8, item.name, - item.source.getPath(self.builder), + item.source.getPath(b), ); } - var options_dir = try self.builder.cache_root.handle.makeOpenPath("options", .{}); + var options_dir = try b.cache_root.handle.makeOpenPath("options", .{}); defer options_dir.close(); const basename = self.hashContentsToFileName(); try options_dir.writeFile(&basename, self.contents.items); - self.generated_file.path = try self.builder.cache_root.join(self.builder.allocator, &.{ - "options", &basename, - }); + self.generated_file.path = try b.cache_root.join(b.allocator, &.{ "options", &basename }); } fn hashContentsToFileName(self: *OptionsStep) [64]u8 { diff --git a/lib/std/Build/RemoveDirStep.zig b/lib/std/Build/RemoveDirStep.zig index 4fc8e6d338..9f291c7523 100644 --- a/lib/std/Build/RemoveDirStep.zig +++ b/lib/std/Build/RemoveDirStep.zig @@ -7,28 +7,37 @@ const RemoveDirStep = @This(); pub const base_id = .remove_dir; step: Step, -builder: *std.Build, dir_path: []const u8, -pub fn init(builder: *std.Build, dir_path: []const u8) RemoveDirStep { +pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep { return RemoveDirStep{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = .remove_dir, - .name = builder.fmt("RemoveDir {s}", .{dir_path}), + .name = owner.fmt("RemoveDir {s}", .{dir_path}), + .owner = owner, .makeFn = make, }), - .dir_path = builder.dupePath(dir_path), + .dir_path = owner.dupePath(dir_path), }; } fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // TODO update progress node while walking file system. + // Should the standard library support this use case?? _ = prog_node; + + const b = step.owner; const self = @fieldParentPtr(RemoveDirStep, "step", step); - const full_path = self.builder.pathFromRoot(self.dir_path); - fs.cwd().deleteTree(full_path) catch |err| { - log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) }); - return err; + b.build_root.handle.deleteTree(self.dir_path) catch |err| { + if (b.build_root.path) |base| { + return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ + base, self.dir_path, @errorName(err), + }); + } else { + return step.fail("unable to recursively delete path '{s}': {s}", .{ + self.dir_path, @errorName(err), + }); + } }; } diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 84fd7f975d..087483fea8 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -11,14 +11,11 @@ const EnvMap = process.EnvMap; const Allocator = mem.Allocator; const ExecError = std.Build.ExecError; -const max_stdout_size = 1 * 1024 * 1024; // 1 MiB - const RunStep = @This(); pub const base_id: Step.Id = .run; step: Step, -builder: *std.Build, /// See also addArg and addArgs to modifying this directly argv: ArrayList(Arg), @@ -29,35 +26,68 @@ cwd: ?[]const u8, /// Override this field to modify the environment, or use setEnvironmentVariable env_map: ?*EnvMap, -stdout_action: StdIoAction = .inherit, -stderr_action: StdIoAction = .inherit, - -stdin_behavior: std.ChildProcess.StdIo = .Inherit, - -/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution -expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 }, - -/// Print the command before running it -print: bool, -/// Controls whether execution is skipped if the output file is up-to-date. -/// The default is to always run if there is no output file, and to skip -/// running if all output files are up-to-date. -condition: enum { output_outdated, always } = .output_outdated, +/// Configures whether the RunStep is considered to have side-effects, and also +/// whether the RunStep will inherit stdio streams, forwarding them to the +/// parent process, in which case will require a global lock to prevent other +/// steps from interfering with stdio while the subprocess associated with this +/// RunStep is running. +/// If the RunStep is determined to not have side-effects, then execution will +/// be skipped if all output files are up-to-date and input files are +/// unchanged. +stdio: StdIo = .infer_from_args, /// Additional file paths relative to build.zig that, when modified, indicate /// that the RunStep should be re-executed. +/// If the RunStep is determined to have side-effects, this field is ignored +/// and the RunStep is always executed when it appears in the build graph. extra_file_dependencies: []const []const u8 = &.{}, /// After adding an output argument, this step will by default rename itself /// for a better display name in the build summary. /// This can be disabled by setting this to false. -rename_step_with_output_arg: bool, +rename_step_with_output_arg: bool = true, -pub const StdIoAction = union(enum) { +/// If this is true, a RunStep which is configured to check the output of the +/// executed binary will not fail the build if the binary cannot be executed +/// due to being for a foreign binary to the host system which is running the +/// build graph. +/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a +/// binary is detected as foreign, as well as system configuration such as +/// Rosetta (macOS) and binfmt_misc (Linux). +skip_foreign_checks: bool = false, + +/// If stderr or stdout exceeds this amount, the child process is killed and +/// the step fails. +max_stdio_size: usize = 10 * 1024 * 1024, + +pub const StdIo = union(enum) { + /// Whether the RunStep has side-effects will be determined by whether or not one + /// of the args is an output file (added with `addOutputFileArg`). + /// If the RunStep is determined to have side-effects, this is the same as `inherit`. + /// The step will fail if the subprocess crashes or returns a non-zero exit code. + infer_from_args, + /// Causes the RunStep to be considered to have side-effects, and therefore + /// always execute when it appears in the build graph. + /// It also means that this step will obtain a global lock to prevent other + /// steps from running in the meantime. + /// The step will fail if the subprocess crashes or returns a non-zero exit code. inherit, - ignore, - expect_exact: []const u8, - expect_matches: []const []const u8, + /// Causes the RunStep to be considered to *not* have side-effects. The + /// process will be re-executed if any of the input dependencies are + /// modified. The exit code and standard I/O streams will be checked for + /// certain conditions, and the step will succeed or fail based on these + /// conditions. + /// Note that an explicit check for exit code 0 needs to be added to this + /// list if such a check is desireable. + check: []const Check, + + pub const Check = union(enum) { + expect_stderr_exact: []const u8, + expect_stderr_match: []const u8, + expect_stdout_exact: []const u8, + expect_stdout_match: []const u8, + expect_term: std.ChildProcess.Term, + }; }; pub const Arg = union(enum) { @@ -72,20 +102,20 @@ pub const Arg = union(enum) { }; }; -pub fn create(builder: *std.Build, name: []const u8) *RunStep { - const self = builder.allocator.create(RunStep) catch @panic("OOM"); +pub fn create(owner: *std.Build, name: []const u8) *RunStep { + const self = owner.allocator.create(RunStep) catch @panic("OOM"); self.* = .{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = base_id, .name = name, + .owner = owner, .makeFn = make, }), - .argv = ArrayList(Arg).init(builder.allocator), + .argv = ArrayList(Arg).init(owner.allocator), .cwd = null, .env_map = null, - .print = builder.verbose, .rename_step_with_output_arg = true, + .max_stdio_size = 10 * 1024 * 1024, }; return self; } @@ -99,16 +129,17 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { /// run, and returns a FileSource which can be used as inputs to other APIs /// throughout the build system. pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { - const generated_file = rs.builder.allocator.create(std.Build.GeneratedFile) catch @panic("OOM"); + const b = rs.step.owner; + const generated_file = b.allocator.create(std.Build.GeneratedFile) catch @panic("OOM"); generated_file.* = .{ .step = &rs.step }; rs.argv.append(.{ .output = .{ .generated_file = generated_file, - .basename = rs.builder.dupe(basename), + .basename = b.dupe(basename), } }) catch @panic("OOM"); if (rs.rename_step_with_output_arg) { rs.rename_step_with_output_arg = false; - rs.step.name = rs.builder.fmt("{s} ({s})", .{ rs.step.name, basename }); + rs.step.name = b.fmt("{s} ({s})", .{ rs.step.name, basename }); } return .{ .generated = generated_file }; @@ -116,13 +147,13 @@ pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { self.argv.append(Arg{ - .file_source = file_source.dupe(self.builder), + .file_source = file_source.dupe(self.step.owner), }) catch @panic("OOM"); file_source.addStepDependencies(&self.step); } pub fn addArg(self: *RunStep, arg: []const u8) void { - self.argv.append(Arg{ .bytes = self.builder.dupe(arg) }) catch @panic("OOM"); + self.argv.append(Arg{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); } pub fn addArgs(self: *RunStep, args: []const []const u8) void { @@ -132,13 +163,14 @@ pub fn addArgs(self: *RunStep, args: []const []const u8) void { } pub fn clearEnvironment(self: *RunStep) void { - const new_env_map = self.builder.allocator.create(EnvMap) catch @panic("OOM"); - new_env_map.* = EnvMap.init(self.builder.allocator); + const b = self.step.owner; + const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); + new_env_map.* = EnvMap.init(b.allocator); self.env_map = new_env_map; } pub fn addPathDir(self: *RunStep, search_path: []const u8) void { - addPathDirInternal(&self.step, self.builder, search_path); + addPathDirInternal(&self.step, self.step.owner, search_path); } /// For internal use only, users of `RunStep` should use `addPathDir` directly. @@ -157,13 +189,12 @@ pub fn addPathDirInternal(step: *Step, builder: *std.Build, search_path: []const } pub fn getEnvMap(self: *RunStep) *EnvMap { - return getEnvMapInternal(&self.step, self.builder.allocator); + return getEnvMapInternal(&self.step, self.step.owner.allocator); } fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { const maybe_env_map = switch (step.id) { .run => step.cast(RunStep).?.env_map, - .emulatable_run => step.cast(std.Build.EmulatableRunStep).?.env_map, else => unreachable, }; return maybe_env_map orelse { @@ -171,7 +202,6 @@ fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { env_map.* = process.getEnvMap(allocator) catch @panic("unhandled error"); switch (step.id) { .run => step.cast(RunStep).?.env_map = env_map, - .emulatable_run => step.cast(RunStep).?.env_map = env_map, else => unreachable, } return env_map; @@ -179,41 +209,85 @@ fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { } pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { + const b = self.step.owner; const env_map = self.getEnvMap(); - env_map.put( - self.builder.dupe(key), - self.builder.dupe(value), - ) catch @panic("unhandled error"); + env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); } pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { - self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) }; + const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; + self.addCheck(new_check); } pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { - self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) }; + const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; + self.addCheck(new_check); } -fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo { - return switch (action) { - .ignore => .Ignore, - .inherit => .Inherit, - .expect_exact, .expect_matches => .Pipe, +pub fn expectExitCode(self: *RunStep, code: u8) void { + const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; + self.addCheck(new_check); +} + +pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { + const arena = self.step.owner.allocator; + switch (self.stdio) { + .infer_from_args => { + const list = arena.create([1]StdIo.Check) catch @panic("OOM"); + list.* = .{new_check}; + self.stdio = .{ .check = list }; + }, + .check => |checks| { + const new_list = arena.alloc(StdIo.Check, checks.len + 1) catch @panic("OOM"); + std.mem.copy(StdIo.Check, new_list, checks); + new_list[checks.len] = new_check; + }, + else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), + } +} + +/// Returns whether the RunStep has side effects *other than* updating the output arguments. +fn hasSideEffects(self: RunStep) bool { + return switch (self.stdio) { + .infer_from_args => !self.hasAnyOutputArgs(), + .inherit => true, + .check => false, }; } -fn needOutputCheck(self: RunStep) bool { - switch (self.condition) { - .always => return false, - .output_outdated => {}, - } - if (self.extra_file_dependencies.len > 0) return true; - +fn hasAnyOutputArgs(self: RunStep) bool { for (self.argv.items) |arg| switch (arg) { .output => return true, else => continue, }; + return false; +} +fn checksContainStdout(checks: []const StdIo.Check) bool { + for (checks) |check| switch (check) { + .expect_stderr_exact, + .expect_stderr_match, + .expect_term, + => continue, + + .expect_stdout_exact, + .expect_stdout_match, + => return true, + }; + return false; +} + +fn checksContainStderr(checks: []const StdIo.Check) bool { + for (checks) |check| switch (check) { + .expect_stdout_exact, + .expect_stdout_match, + .expect_term, + => continue, + + .expect_stderr_exact, + .expect_stderr_match, + => return true, + }; return false; } @@ -223,16 +297,17 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // processes could use to supply progress updates. _ = prog_node; + const b = step.owner; const self = @fieldParentPtr(RunStep, "step", step); - const need_output_check = self.needOutputCheck(); + const has_side_effects = self.hasSideEffects(); - var argv_list = ArrayList([]const u8).init(self.builder.allocator); + var argv_list = ArrayList([]const u8).init(b.allocator); var output_placeholders = ArrayList(struct { index: usize, output: Arg.Output, - }).init(self.builder.allocator); + }).init(b.allocator); - var man = self.builder.cache.obtain(); + var man = b.cache.obtain(); defer man.deinit(); for (self.argv.items) |arg| { @@ -242,7 +317,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { man.hash.addBytes(bytes); }, .file_source => |file| { - const file_path = file.getPath(self.builder); + const file_path = file.getPath(b); try argv_list.append(file_path); _ = try man.addFile(file_path, null); }, @@ -252,7 +327,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { self.addPathForDynLibs(artifact); } const file_path = artifact.installed_path orelse - artifact.getOutputSource().getPath(self.builder); + artifact.getOutputSource().getPath(b); try argv_list.append(file_path); @@ -272,17 +347,17 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - if (need_output_check) { + if (!has_side_effects) { for (self.extra_file_dependencies) |file_path| { - _ = try man.addFile(self.builder.pathFromRoot(file_path), null); + _ = try man.addFile(b.pathFromRoot(file_path), null); } - if (man.hit() catch |err| failWithCacheError(man, err)) { + if (try step.cacheHit(&man)) { // cache hit, skip running command const digest = man.final(); for (output_placeholders.items) |placeholder| { - placeholder.output.generated_file.path = try self.builder.cache_root.join( - self.builder.allocator, + placeholder.output.generated_file.path = try b.cache_root.join( + b.allocator, &.{ "o", &digest, placeholder.output.basename }, ); } @@ -292,8 +367,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const digest = man.final(); for (output_placeholders.items) |placeholder| { - const output_path = try self.builder.cache_root.join( - self.builder.allocator, + const output_path = try b.cache_root.join( + b.allocator, &.{ "o", &digest, placeholder.output.basename }, ); const output_dir = fs.path.dirname(output_path).?; @@ -308,18 +383,16 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } try runCommand( - argv_list.items, - self.builder, - self.expected_term, - self.stdout_action, - self.stderr_action, - self.stdin_behavior, - self.env_map, + step, self.cwd, - self.print, + argv_list.items, + self.env_map, + self.stdio, + has_side_effects, + self.max_stdio_size, ); - if (need_output_check) { + if (!has_side_effects) { try man.writeManifest(); } } @@ -369,165 +442,171 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) }; } -pub fn runCommand( +fn runCommand( + step: *Step, + opt_cwd: ?[]const u8, argv: []const []const u8, - builder: *std.Build, - expected_term: ?std.ChildProcess.Term, - stdout_action: StdIoAction, - stderr_action: StdIoAction, - stdin_behavior: std.ChildProcess.StdIo, env_map: ?*EnvMap, - maybe_cwd: ?[]const u8, - print: bool, + stdio: StdIo, + has_side_effects: bool, + max_stdio_size: usize, ) !void { - const cwd = if (maybe_cwd) |cwd| builder.pathFromRoot(cwd) else builder.build_root.path; + const b = step.owner; + const arena = b.allocator; + const cwd = if (opt_cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path; - if (!std.process.can_spawn) { - const cmd = try std.mem.join(builder.allocator, " ", argv); - std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ - @tagName(builtin.os.tag), cmd, - }); - builder.allocator.free(cmd); - return ExecError.ExecNotSupported; - } + try step.handleChildProcUnsupported(opt_cwd, argv); + try Step.handleVerbose(step.owner, opt_cwd, argv); - var child = std.ChildProcess.init(argv, builder.allocator); + var child = std.ChildProcess.init(argv, arena); child.cwd = cwd; - child.env_map = env_map orelse builder.env_map; + child.env_map = env_map orelse b.env_map; - child.stdin_behavior = stdin_behavior; - child.stdout_behavior = stdIoActionToBehavior(stdout_action); - child.stderr_behavior = stdIoActionToBehavior(stderr_action); - - if (print) - printCmd(cwd, argv); - - child.spawn() catch |err| { - std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); - return err; + child.stdin_behavior = switch (stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .inherit => .Inherit, + .check => .Close, + }; + child.stdout_behavior = switch (stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .inherit => .Inherit, + .check => |checks| if (checksContainStdout(checks)) .Pipe else .Ignore, + }; + child.stderr_behavior = switch (stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Pipe, + .inherit => .Inherit, + .check => .Pipe, }; - // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O). + child.spawn() catch |err| return step.fail("unable to spawn {s}: {s}", .{ + argv[0], @errorName(err), + }); - var stdout: ?[]const u8 = null; - defer if (stdout) |s| builder.allocator.free(s); + var stdout_bytes: ?[]const u8 = null; + var stderr_bytes: ?[]const u8 = null; - switch (stdout_action) { - .expect_exact, .expect_matches => { - stdout = try child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size); - }, - .inherit, .ignore => {}, + if (child.stdout) |stdout| { + if (child.stderr) |stderr| { + var poller = std.io.poll(arena, enum { stdout, stderr }, .{ + .stdout = stdout, + .stderr = stderr, + }); + defer poller.deinit(); + + while (try poller.poll()) { + if (poller.fifo(.stdout).count > max_stdio_size) + return error.StdoutStreamTooLong; + if (poller.fifo(.stderr).count > max_stdio_size) + return error.StderrStreamTooLong; + } + + stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); + stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); + } else { + stdout_bytes = try stdout.reader().readAllAlloc(arena, max_stdio_size); + } + } else if (child.stderr) |stderr| { + stderr_bytes = try stderr.reader().readAllAlloc(arena, max_stdio_size); } - var stderr: ?[]const u8 = null; - defer if (stderr) |s| builder.allocator.free(s); - - switch (stderr_action) { - .expect_exact, .expect_matches => { - stderr = try child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size); - }, - .inherit, .ignore => {}, - } + if (stderr_bytes) |stderr| if (stderr.len > 0) { + const stderr_is_diagnostic = switch (stdio) { + .check => |checks| !checksContainStderr(checks), + else => true, + }; + if (stderr_is_diagnostic) { + try step.result_error_msgs.append(arena, stderr); + } + }; const term = child.wait() catch |err| { - std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) }); - return err; + return step.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) }); }; - if (!termMatches(expected_term, term)) { - std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) }); - printCmd(cwd, argv); - return error.UnexpectedExit; - } - - switch (stderr_action) { - .inherit, .ignore => {}, - .expect_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stderr.?)) { - std.debug.print( - \\ - \\========= Expected this stderr: ========= - \\{s} - \\========= But found: ==================== - \\{s} - \\ - , .{ expected_bytes, stderr.? }); - printCmd(cwd, argv); - return error.TestFailed; - } + switch (stdio) { + .check => |checks| for (checks) |check| switch (check) { + .expect_stderr_exact => |expected_bytes| { + if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) { + return step.fail( + \\========= expected this stderr: ========= + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following command: === + \\{s} + , .{ + expected_bytes, + stderr_bytes.?, + try Step.allocPrintCmd(arena, opt_cwd, argv), + }); + } + }, + .expect_stderr_match => |match| { + if (mem.indexOf(u8, stderr_bytes.?, match) == null) { + return step.fail( + \\========= expected to find in stderr: ========= + \\{s} + \\========= but stderr does not contain it: ===== + \\{s} + \\========= from the following command: ========= + \\{s} + , .{ + match, + stderr_bytes.?, + try Step.allocPrintCmd(arena, opt_cwd, argv), + }); + } + }, + .expect_stdout_exact => |expected_bytes| { + if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) { + return step.fail( + \\========= expected this stdout: ========= + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following command: === + \\{s} + , .{ + expected_bytes, + stdout_bytes.?, + try Step.allocPrintCmd(arena, opt_cwd, argv), + }); + } + }, + .expect_stdout_match => |match| { + if (mem.indexOf(u8, stdout_bytes.?, match) == null) { + return step.fail( + \\========= expected to find in stdout: ========= + \\{s} + \\========= but stdout does not contain it: ===== + \\{s} + \\========= from the following command: ========= + \\{s} + , .{ + match, + stdout_bytes.?, + try Step.allocPrintCmd(arena, opt_cwd, argv), + }); + } + }, + .expect_term => |expected_term| { + if (!termMatches(expected_term, term)) { + return step.fail("the following command {} (expected {}):\n{s}", .{ + fmtTerm(term), + fmtTerm(expected_term), + try Step.allocPrintCmd(arena, opt_cwd, argv), + }); + } + }, }, - .expect_matches => |matches| for (matches) |match| { - if (mem.indexOf(u8, stderr.?, match) == null) { - std.debug.print( - \\ - \\========= Expected to find in stderr: ========= - \\{s} - \\========= But stderr does not contain it: ===== - \\{s} - \\ - , .{ match, stderr.? }); - printCmd(cwd, argv); - return error.TestFailed; - } + else => { + try step.handleChildProcessTerm(term, opt_cwd, argv); }, } - - switch (stdout_action) { - .inherit, .ignore => {}, - .expect_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stdout.?)) { - std.debug.print( - \\ - \\========= Expected this stdout: ========= - \\{s} - \\========= But found: ==================== - \\{s} - \\ - , .{ expected_bytes, stdout.? }); - printCmd(cwd, argv); - return error.TestFailed; - } - }, - .expect_matches => |matches| for (matches) |match| { - if (mem.indexOf(u8, stdout.?, match) == null) { - std.debug.print( - \\ - \\========= Expected to find in stdout: ========= - \\{s} - \\========= But stdout does not contain it: ===== - \\{s} - \\ - , .{ match, stdout.? }); - printCmd(cwd, argv); - return error.TestFailed; - } - }, - } -} - -fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn { - const i = man.failed_file_index orelse failWithSimpleError(err); - const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err); - const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; - std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path }); - std.process.exit(1); -} - -fn failWithSimpleError(err: anyerror) noreturn { - std.debug.print("{s}\n", .{@errorName(err)}); - std.process.exit(1); -} - -fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void { - if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd}); - for (argv) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); } fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { - addPathForDynLibsInternal(&self.step, self.builder, artifact); + addPathForDynLibsInternal(&self.step, self.step.owner, artifact); } /// This should only be used for internal usage, this is called automatically diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 29cf38b55b..f5ac9e1821 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -1,5 +1,6 @@ id: Id, name: []const u8, +owner: *Build, makeFn: MakeFn, dependencies: std.ArrayList(*Step), /// This field is empty during execution of the user's build script, and @@ -39,7 +40,6 @@ pub const Id = enum { translate_c, write_file, run, - emulatable_run, check_file, check_object, config_header, @@ -60,7 +60,6 @@ pub const Id = enum { .translate_c => Build.TranslateCStep, .write_file => Build.WriteFileStep, .run => Build.RunStep, - .emulatable_run => Build.EmulatableRunStep, .check_file => Build.CheckFileStep, .check_object => Build.CheckObjectStep, .config_header => Build.ConfigHeaderStep, @@ -74,11 +73,14 @@ pub const Id = enum { pub const Options = struct { id: Id, name: []const u8, + owner: *Build, makeFn: MakeFn = makeNoOp, first_ret_addr: ?usize = null, }; -pub fn init(allocator: Allocator, options: Options) Step { +pub fn init(options: Options) Step { + const arena = options.owner.allocator; + var addresses = [1]usize{0} ** n_debug_stack_frames; const first_ret_addr = options.first_ret_addr orelse @returnAddress(); var stack_trace = std.builtin.StackTrace{ @@ -89,9 +91,10 @@ pub fn init(allocator: Allocator, options: Options) Step { return .{ .id = options.id, - .name = allocator.dupe(u8, options.name) catch @panic("OOM"), + .name = arena.dupe(u8, options.name) catch @panic("OOM"), + .owner = options.owner, .makeFn = options.makeFn, - .dependencies = std.ArrayList(*Step).init(allocator), + .dependencies = std.ArrayList(*Step).init(arena), .dependants = .{}, .state = .precheck_unstarted, .debug_stack_trace = addresses, @@ -168,3 +171,231 @@ const std = @import("../std.zig"); const Build = std.Build; const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const builtin = @import("builtin"); + +pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void { + const arena = s.owner.allocator; + + try handleChildProcUnsupported(s, null, argv); + try handleVerbose(s.owner, null, argv); + + const result = std.ChildProcess.exec(.{ + .allocator = arena, + .argv = argv, + }) catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); + + if (result.stderr.len > 0) { + try s.result_error_msgs.append(arena, result.stderr); + } + + try handleChildProcessTerm(s, result.term, null, argv); +} + +pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } { + const arena = step.owner.allocator; + const msg = try std.fmt.allocPrint(arena, fmt, args); + try step.result_error_msgs.append(arena, msg); + return error.MakeFailed; +} + +/// Assumes that argv contains `--listen=-` and that the process being spawned +/// is the zig compiler - the same version that compiled the build runner. +pub fn evalZigProcess( + s: *Step, + argv: []const []const u8, + prog_node: *std.Progress.Node, +) ![]const u8 { + assert(argv.len != 0); + const b = s.owner; + const arena = b.allocator; + const gpa = arena; + + try handleChildProcUnsupported(s, null, argv); + try handleVerbose(s.owner, null, argv); + + var child = std.ChildProcess.init(argv, arena); + child.env_map = b.env_map; + child.stdin_behavior = .Pipe; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + + child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{ + argv[0], @errorName(err), + }); + + var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ + .stdout = child.stdout.?, + .stderr = child.stderr.?, + }); + defer poller.deinit(); + + try sendMessage(child.stdin.?, .update); + try sendMessage(child.stdin.?, .exit); + + const Header = std.zig.Server.Message.Header; + var result: ?[]const u8 = null; + + var node_name: std.ArrayListUnmanaged(u8) = .{}; + defer node_name.deinit(gpa); + var sub_prog_node: ?std.Progress.Node = null; + defer if (sub_prog_node) |*n| n.end(); + + while (try poller.poll()) { + const stdout = poller.fifo(.stdout); + const buf = stdout.readableSlice(0); + assert(stdout.readableLength() == buf.len); + if (buf.len >= @sizeOf(Header)) { + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + const header_and_msg_len = header.bytes_len + @sizeOf(Header); + if (buf.len >= header_and_msg_len) { + const body = buf[@sizeOf(Header)..][0..header.bytes_len]; + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return s.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .error_bundle => { + const EbHdr = std.zig.Server.Message.ErrorBundle; + const eb_hdr = @ptrCast(*align(1) const EbHdr, body); + const extra_bytes = + body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + // TODO: use @ptrCast when the compiler supports it + const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try arena.alloc(u32, unaligned_extra.len); + // TODO: use @memcpy when it supports slices + for (extra_array, unaligned_extra) |*dst, src| dst.* = src; + s.result_error_bundle = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .extra = extra_array, + }; + }, + .progress => { + if (sub_prog_node) |*n| n.end(); + node_name.clearRetainingCapacity(); + try node_name.appendSlice(gpa, body); + sub_prog_node = prog_node.start(node_name.items, 0); + sub_prog_node.?.activate(); + }, + .emit_bin_path => { + result = try arena.dupe(u8, body); + }, + _ => { + // Unrecognized message. + }, + } + stdout.discard(header_and_msg_len); + } + } + } + + const stderr = poller.fifo(.stderr); + if (stderr.readableLength() > 0) { + try s.result_error_msgs.append(arena, try stderr.toOwnedSlice()); + } + + // Send EOF to stdin. + child.stdin.?.close(); + child.stdin = null; + + const term = child.wait() catch |err| { + return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) }); + }; + try handleChildProcessTerm(s, term, null, argv); + + if (s.result_error_bundle.errorMessageCount() > 0) { + return s.fail("the following command failed with {d} compilation errors:\n{s}", .{ + s.result_error_bundle.errorMessageCount(), + try allocPrintCmd(arena, null, argv), + }); + } + + return result orelse return s.fail( + "the following command failed to communicate the compilation result:\n{s}", + .{try allocPrintCmd(arena, null, argv)}, + ); +} + +fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(std.mem.asBytes(&header)); +} + +pub fn handleVerbose( + b: *Build, + opt_cwd: ?[]const u8, + argv: []const []const u8, +) error{OutOfMemory}!void { + if (b.verbose) { + // Intention of verbose is to print all sub-process command lines to + // stderr before spawning them. + const text = try allocPrintCmd(b.allocator, opt_cwd, argv); + std.debug.print("{s}\n", .{text}); + } +} + +pub inline fn handleChildProcUnsupported( + s: *Step, + opt_cwd: ?[]const u8, + argv: []const []const u8, +) error{ OutOfMemory, MakeFailed }!void { + if (!std.process.can_spawn) { + return s.fail( + "unable to execute the following command: host cannot spawn child processes\n{s}", + .{try allocPrintCmd(s.owner.allocator, opt_cwd, argv)}, + ); + } +} + +pub fn handleChildProcessTerm( + s: *Step, + term: std.ChildProcess.Term, + opt_cwd: ?[]const u8, + argv: []const []const u8, +) error{ MakeFailed, OutOfMemory }!void { + const arena = s.owner.allocator; + switch (term) { + .Exited => |code| { + if (code != 0) { + return s.fail( + "the following command exited with error code {d}:\n{s}", + .{ code, try allocPrintCmd(arena, opt_cwd, argv) }, + ); + } + }, + .Signal, .Stopped, .Unknown => { + return s.fail( + "the following command terminated unexpectedly:\n{s}", + .{try allocPrintCmd(arena, opt_cwd, argv)}, + ); + }, + } +} + +pub fn allocPrintCmd(arena: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 { + var buf: std.ArrayListUnmanaged(u8) = .{}; + if (opt_cwd) |cwd| try buf.writer(arena).print("cd {s} && ", .{cwd}); + for (argv) |arg| { + try buf.writer(arena).print("{s} ", .{arg}); + } + return buf.toOwnedSlice(arena); +} + +pub fn cacheHit(s: *Step, man: *std.Build.Cache.Manifest) !bool { + return man.hit() catch |err| return failWithCacheError(s, man, err); +} + +fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyerror) anyerror { + const i = man.failed_file_index orelse return err; + const pp = man.files.items[i].prefixed_path orelse return err; + const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; + return s.fail("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path }); +} diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig index fef644f03c..dbb93d8c61 100644 --- a/lib/std/Build/TranslateCStep.zig +++ b/lib/std/Build/TranslateCStep.zig @@ -11,7 +11,6 @@ const TranslateCStep = @This(); pub const base_id = .translate_c; step: Step, -builder: *std.Build, source: std.Build.FileSource, include_dirs: std.ArrayList([]const u8), c_macros: std.ArrayList([]const u8), @@ -26,19 +25,19 @@ pub const Options = struct { optimize: std.builtin.OptimizeMode, }; -pub fn create(builder: *std.Build, options: Options) *TranslateCStep { - const self = builder.allocator.create(TranslateCStep) catch @panic("OOM"); - const source = options.source_file.dupe(builder); +pub fn create(owner: *std.Build, options: Options) *TranslateCStep { + const self = owner.allocator.create(TranslateCStep) catch @panic("OOM"); + const source = options.source_file.dupe(owner); self.* = TranslateCStep{ - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = .translate_c, .name = "translate-c", + .owner = owner, .makeFn = make, }), - .builder = builder, .source = source, - .include_dirs = std.ArrayList([]const u8).init(builder.allocator), - .c_macros = std.ArrayList([]const u8).init(builder.allocator), + .include_dirs = std.ArrayList([]const u8).init(owner.allocator), + .c_macros = std.ArrayList([]const u8).init(owner.allocator), .out_basename = undefined, .target = options.target, .optimize = options.optimize, @@ -58,7 +57,7 @@ pub const AddExecutableOptions = struct { /// Creates a step to build an executable from the translated source. pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep { - return self.builder.addExecutable(.{ + return self.step.owner.addExecutable(.{ .root_source_file = .{ .generated = &self.output_file }, .name = options.name orelse "translated_c", .version = options.version, @@ -69,30 +68,31 @@ pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *Comp } pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { - self.include_dirs.append(self.builder.dupePath(include_dir)) catch @panic("OOM"); + self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM"); } pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { - return CheckFileStep.create(self.builder, .{ .generated = &self.output_file }, self.builder.dupeStrings(expected_matches)); + return CheckFileStep.create(self.step.owner, .{ .generated = &self.output_file }, self.step.owner.dupeStrings(expected_matches)); } /// If the value is omitted, it is set to 1. /// `name` and `value` need not live longer than the function call. pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void { - const macro = std.Build.constructCMacro(self.builder.allocator, name, value); + const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value); self.c_macros.append(macro) catch @panic("OOM"); } /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void { - self.c_macros.append(self.builder.dupe(name_and_value)) catch @panic("OOM"); + self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM"); } fn make(step: *Step, prog_node: *std.Progress.Node) !void { + const b = step.owner; const self = @fieldParentPtr(TranslateCStep, "step", step); - var argv_list = std.ArrayList([]const u8).init(self.builder.allocator); - try argv_list.append(self.builder.zig_exe); + var argv_list = std.ArrayList([]const u8).init(b.allocator); + try argv_list.append(b.zig_exe); try argv_list.append("translate-c"); try argv_list.append("-lc"); @@ -101,12 +101,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (!self.target.isNative()) { try argv_list.append("-target"); - try argv_list.append(try self.target.zigTriple(self.builder.allocator)); + try argv_list.append(try self.target.zigTriple(b.allocator)); } switch (self.optimize) { .Debug => {}, // Skip since it's the default. - else => try argv_list.append(self.builder.fmt("-O{s}", .{@tagName(self.optimize)})), + else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), } for (self.include_dirs.items) |include_dir| { @@ -119,15 +119,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try argv_list.append(c_macro); } - try argv_list.append(self.source.getPath(self.builder)); + try argv_list.append(self.source.getPath(b)); - const output_path = try self.builder.execFromStep(argv_list.items, &self.step, prog_node); + const output_path = try step.evalZigProcess(argv_list.items, prog_node); self.out_basename = fs.path.basename(output_path); const output_dir = fs.path.dirname(output_path).?; self.output_file.path = try fs.path.join( - self.builder.allocator, + b.allocator, &[_][]const u8{ output_dir, self.out_basename }, ); } diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 62acd6e8ee..d554d6efc1 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -10,7 +10,6 @@ //! control. step: Step, -builder: *std.Build, /// The elements here are pointers because we need stable pointers for the /// GeneratedFile field. files: std.ArrayListUnmanaged(*File), @@ -34,12 +33,12 @@ pub const Contents = union(enum) { copy: std.Build.FileSource, }; -pub fn init(builder: *std.Build) WriteFileStep { +pub fn init(owner: *std.Build) WriteFileStep { return .{ - .builder = builder, - .step = Step.init(builder.allocator, .{ + .step = Step.init(.{ .id = .write_file, .name = "writefile", + .owner = owner, .makeFn = make, }), .files = .{}, @@ -48,12 +47,13 @@ pub fn init(builder: *std.Build) WriteFileStep { } pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { - const gpa = wf.builder.allocator; + const b = wf.step.owner; + const gpa = b.allocator; const file = gpa.create(File) catch @panic("OOM"); file.* = .{ .generated_file = .{ .step = &wf.step }, - .sub_path = wf.builder.dupePath(sub_path), - .contents = .{ .bytes = wf.builder.dupe(bytes) }, + .sub_path = b.dupePath(sub_path), + .contents = .{ .bytes = b.dupe(bytes) }, }; wf.files.append(gpa, file) catch @panic("OOM"); } @@ -66,11 +66,12 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { /// required sub-path exists. /// This is the option expected to be used most commonly with `addCopyFile`. pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { - const gpa = wf.builder.allocator; + const b = wf.step.owner; + const gpa = b.allocator; const file = gpa.create(File) catch @panic("OOM"); file.* = .{ .generated_file = .{ .step = &wf.step }, - .sub_path = wf.builder.dupePath(sub_path), + .sub_path = b.dupePath(sub_path), .contents = .{ .copy = source }, }; wf.files.append(gpa, file) catch @panic("OOM"); @@ -83,7 +84,8 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [ /// those changes to version control. /// A file added this way is not available with `getFileSource`. pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { - wf.output_source_files.append(wf.builder.allocator, .{ + const b = wf.step.owner; + wf.output_source_files.append(b.allocator, .{ .contents = .{ .copy = source }, .sub_path = sub_path, }) catch @panic("OOM"); @@ -101,6 +103,7 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; + const b = step.owner; const wf = @fieldParentPtr(WriteFileStep, "step", step); // Writing to source files is kind of an extra capability of this @@ -110,11 +113,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { for (wf.output_source_files.items) |output_source_file| { const basename = fs.path.basename(output_source_file.sub_path); if (fs.path.dirname(output_source_file.sub_path)) |dirname| { - var dir = try wf.builder.build_root.handle.makeOpenPath(dirname, .{}); + var dir = try b.build_root.handle.makeOpenPath(dirname, .{}); defer dir.close(); try writeFile(wf, dir, output_source_file.contents, basename); } else { - try writeFile(wf, wf.builder.build_root.handle, output_source_file.contents, basename); + try writeFile(wf, b.build_root.handle, output_source_file.contents, basename); } } @@ -125,7 +128,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // If, for example, a hard-coded path was used as the location to put WriteFileStep // files, then two WriteFileSteps executing in parallel might clobber each other. - var man = wf.builder.cache.obtain(); + var man = b.cache.obtain(); defer man.deinit(); // Random bytes to make WriteFileStep unique. Refresh this with @@ -140,17 +143,17 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { man.hash.addBytes(bytes); }, .copy => |file_source| { - _ = try man.addFile(file_source.getPath(wf.builder), null); + _ = try man.addFile(file_source.getPath(b), null); }, } } - if (man.hit() catch |err| failWithCacheError(man, err)) { + if (try step.cacheHit(&man)) { // Cache hit, skip writing file data. const digest = man.final(); for (wf.files.items) |file| { - file.generated_file.path = try wf.builder.cache_root.join( - wf.builder.allocator, + file.generated_file.path = try b.cache_root.join( + b.allocator, &.{ "o", &digest, file.sub_path }, ); } @@ -160,7 +163,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const digest = man.final(); const cache_path = "o" ++ fs.path.sep_str ++ digest; - var cache_dir = wf.builder.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { + var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) }); return err; }; @@ -169,15 +172,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { for (wf.files.items) |file| { const basename = fs.path.basename(file.sub_path); if (fs.path.dirname(file.sub_path)) |dirname| { - var dir = try wf.builder.cache_root.handle.makeOpenPath(dirname, .{}); + var dir = try b.cache_root.handle.makeOpenPath(dirname, .{}); defer dir.close(); try writeFile(wf, dir, file.contents, basename); } else { try writeFile(wf, cache_dir, file.contents, basename); } - file.generated_file.path = try wf.builder.cache_root.join( - wf.builder.allocator, + file.generated_file.path = try b.cache_root.join( + b.allocator, &.{ cache_path, file.sub_path }, ); } @@ -186,32 +189,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } fn writeFile(wf: *WriteFileStep, dir: fs.Dir, contents: Contents, basename: []const u8) !void { + const b = wf.step.owner; // TODO after landing concurrency PR, improve error reporting here switch (contents) { .bytes => |bytes| return dir.writeFile(basename, bytes), .copy => |file_source| { - const source_path = file_source.getPath(wf.builder); + const source_path = file_source.getPath(b); const prev_status = try fs.Dir.updateFile(fs.cwd(), source_path, dir, basename, .{}); _ = prev_status; // TODO logging (affected by open PR regarding concurrency) }, } } -/// TODO consolidate this with the same function in RunStep? -/// Also properly deal with concurrency (see open PR) -fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn { - const i = man.failed_file_index orelse failWithSimpleError(err); - const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err); - const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; - std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path }); - std.process.exit(1); -} - -fn failWithSimpleError(err: anyerror) noreturn { - std.debug.print("{s}\n", .{@errorName(err)}); - std.process.exit(1); -} - const std = @import("../std.zig"); const Step = std.Build.Step; const fs = std.fs; diff --git a/src/main.zig b/src/main.zig index fcdb52c342..6bf7177a9e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4419,6 +4419,8 @@ pub const usage_build = \\Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error \\ -fno-reference-trace Disable reference trace + \\ -fsummary Print the build summary, even on success + \\ -fno-summary Omit the build summary, even on failure \\ --build-file [file] Override path to build.zig \\ --cache-dir [path] Override path to local Zig cache directory \\ --global-cache-dir [path] Override path to global Zig cache directory @@ -4920,8 +4922,6 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void }; defer tree.deinit(gpa); - try printAstErrorsToStderr(gpa, tree, "", color); - var has_ast_error = false; if (check_ast_flag) { var file: Module.File = .{ .status = .never_loaded, @@ -4957,11 +4957,11 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(ttyconf); - has_ast_error = true; + process.exit(2); } - } - if (tree.errors.len != 0 or has_ast_error) { - process.exit(1); + } else if (tree.errors.len != 0) { + try printAstErrorsToStderr(gpa, tree, "", color); + process.exit(2); } const formatted = try tree.render(gpa); defer gpa.free(formatted); diff --git a/test/src/compare_output.zig b/test/src/compare_output.zig index 3bda3bdacd..20bd62d8e2 100644 --- a/test/src/compare_output.zig +++ b/test/src/compare_output.zig @@ -166,9 +166,7 @@ pub const CompareOutputContext = struct { const run = exe.run(); run.addArgs(case.cli_args); - run.stderr_action = .ignore; - run.stdout_action = .ignore; - run.expected_term = .{ .Exited = 126 }; + run.expectExitCode(126); self.step.dependOn(&run.step); }, diff --git a/test/tests.zig b/test/tests.zig index fceaa173d1..494ae0ea41 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -858,10 +858,11 @@ pub const StackTracesContext = struct { const allocator = context.b.allocator; const ptr = allocator.create(RunAndCompareStep) catch unreachable; ptr.* = RunAndCompareStep{ - .step = Step.init(allocator, .{ + .step = Step.init(.{ .id = .custom, .name = "StackTraceCompareOutputStep", .makeFn = make, + .owner = context.b, }), .context = context, .exe = exe, @@ -1121,10 +1122,7 @@ pub const StandaloneContext = struct { defer zig_args.resize(zig_args_base_len) catch unreachable; const run_cmd = b.addSystemCommand(zig_args.items); - const log_step = b.addLog("PASS {s} ({s})", .{ annotated_case_name, @tagName(optimize_mode) }); - log_step.step.dependOn(&run_cmd.step); - - self.step.dependOn(&log_step.step); + self.step.dependOn(&run_cmd.step); } } @@ -1150,10 +1148,7 @@ pub const StandaloneContext = struct { exe.linkSystemLibrary("c"); } - const log_step = b.addLog("PASS {s}", .{annotated_case_name}); - log_step.step.dependOn(&exe.step); - - self.step.dependOn(&log_step.step); + self.step.dependOn(&exe.step); } } }; @@ -1203,9 +1198,10 @@ pub const GenHContext = struct { const allocator = context.b.allocator; const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; ptr.* = GenHCmpOutputStep{ - .step = Step.init(allocator, .{ + .step = Step.init(.{ .id = .custom, .name = "ParseCCmpOutput", + .owner = context.b, .makeFn = make, }), .context = context, From a2dc49a0f3d5761eae271d9b353542edb6e8f6e9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Mar 2023 23:45:54 -0700 Subject: [PATCH 182/294] fix Step.evalZigProcess to handle more than 1 message per poll --- lib/std/Build/Step.zig | 97 +++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index f5ac9e1821..3219588050 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -240,57 +240,58 @@ pub fn evalZigProcess( var sub_prog_node: ?std.Progress.Node = null; defer if (sub_prog_node) |*n| n.end(); - while (try poller.poll()) { - const stdout = poller.fifo(.stdout); - const buf = stdout.readableSlice(0); - assert(stdout.readableLength() == buf.len); - if (buf.len >= @sizeOf(Header)) { + const stdout = poller.fifo(.stdout); + + poll: while (try poller.poll()) { + while (true) { + const buf = stdout.readableSlice(0); + assert(stdout.readableLength() == buf.len); + if (buf.len < @sizeOf(Header)) continue :poll; const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); const header_and_msg_len = header.bytes_len + @sizeOf(Header); - if (buf.len >= header_and_msg_len) { - const body = buf[@sizeOf(Header)..][0..header.bytes_len]; - switch (header.tag) { - .zig_version => { - if (!std.mem.eql(u8, builtin.zig_version_string, body)) { - return s.fail( - "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", - .{ builtin.zig_version_string, body }, - ); - } - }, - .error_bundle => { - const EbHdr = std.zig.Server.Message.ErrorBundle; - const eb_hdr = @ptrCast(*align(1) const EbHdr, body); - const extra_bytes = - body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; - const string_bytes = - body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; - // TODO: use @ptrCast when the compiler supports it - const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); - const extra_array = try arena.alloc(u32, unaligned_extra.len); - // TODO: use @memcpy when it supports slices - for (extra_array, unaligned_extra) |*dst, src| dst.* = src; - s.result_error_bundle = .{ - .string_bytes = try arena.dupe(u8, string_bytes), - .extra = extra_array, - }; - }, - .progress => { - if (sub_prog_node) |*n| n.end(); - node_name.clearRetainingCapacity(); - try node_name.appendSlice(gpa, body); - sub_prog_node = prog_node.start(node_name.items, 0); - sub_prog_node.?.activate(); - }, - .emit_bin_path => { - result = try arena.dupe(u8, body); - }, - _ => { - // Unrecognized message. - }, - } - stdout.discard(header_and_msg_len); + if (buf.len < header_and_msg_len) continue :poll; + const body = buf[@sizeOf(Header)..][0..header.bytes_len]; + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return s.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .error_bundle => { + const EbHdr = std.zig.Server.Message.ErrorBundle; + const eb_hdr = @ptrCast(*align(1) const EbHdr, body); + const extra_bytes = + body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + // TODO: use @ptrCast when the compiler supports it + const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try arena.alloc(u32, unaligned_extra.len); + // TODO: use @memcpy when it supports slices + for (extra_array, unaligned_extra) |*dst, src| dst.* = src; + s.result_error_bundle = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .extra = extra_array, + }; + }, + .progress => { + if (sub_prog_node) |*n| n.end(); + node_name.clearRetainingCapacity(); + try node_name.appendSlice(gpa, body); + sub_prog_node = prog_node.start(node_name.items, 0); + sub_prog_node.?.activate(); + }, + .emit_bin_path => { + result = try arena.dupe(u8, body); + }, + _ => { + // Unrecognized message. + }, } + stdout.discard(header_and_msg_len); } } From e0561ad79be81f55d525cc9b847e0fc7f481ee16 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 16:31:16 -0700 Subject: [PATCH 183/294] std.Build.Cache.Directory: add a format() method --- lib/std/Build/Cache.zig | 46 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index d4dbe6ec14..bd2b8a8927 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -7,27 +7,27 @@ pub const Directory = struct { /// directly, but it is needed when passing the directory to a child process. /// `null` means cwd. path: ?[]const u8, - handle: std.fs.Dir, + handle: fs.Dir, pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 { if (self.path) |p| { // TODO clean way to do this with only 1 allocation - const part2 = try std.fs.path.join(allocator, paths); + const part2 = try fs.path.join(allocator, paths); defer allocator.free(part2); - return std.fs.path.join(allocator, &[_][]const u8{ p, part2 }); + return fs.path.join(allocator, &[_][]const u8{ p, part2 }); } else { - return std.fs.path.join(allocator, paths); + return fs.path.join(allocator, paths); } } pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ![:0]u8 { if (self.path) |p| { // TODO clean way to do this with only 1 allocation - const part2 = try std.fs.path.join(allocator, paths); + const part2 = try fs.path.join(allocator, paths); defer allocator.free(part2); - return std.fs.path.joinZ(allocator, &[_][]const u8{ p, part2 }); + return fs.path.joinZ(allocator, &[_][]const u8{ p, part2 }); } else { - return std.fs.path.joinZ(allocator, paths); + return fs.path.joinZ(allocator, paths); } } @@ -39,6 +39,20 @@ pub const Directory = struct { if (self.path) |p| gpa.free(p); self.* = undefined; } + + pub fn format( + self: Directory, + comptime fmt_string: []const u8, + options: fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + if (fmt_string.len != 0) fmt.invalidFmtError(fmt, self); + if (self.path) |p| { + try writer.writeAll(p); + try writer.writeAll(fs.path.sep_str); + } + } }; gpa: Allocator, @@ -243,10 +257,10 @@ pub const HashHelper = struct { hh.hasher.final(&bin_digest); var out_digest: [hex_digest_len]u8 = undefined; - _ = std.fmt.bufPrint( + _ = fmt.bufPrint( &out_digest, "{s}", - .{std.fmt.fmtSliceHexLower(&bin_digest)}, + .{fmt.fmtSliceHexLower(&bin_digest)}, ) catch unreachable; return out_digest; } @@ -365,10 +379,10 @@ pub const Manifest = struct { var bin_digest: BinDigest = undefined; self.hash.hasher.final(&bin_digest); - _ = std.fmt.bufPrint( + _ = fmt.bufPrint( &self.hex_digest, "{s}", - .{std.fmt.fmtSliceHexLower(&bin_digest)}, + .{fmt.fmtSliceHexLower(&bin_digest)}, ) catch unreachable; self.hash.hasher = hasher_init; @@ -469,7 +483,7 @@ pub const Manifest = struct { cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; - _ = std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; + _ = fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; @@ -806,10 +820,10 @@ pub const Manifest = struct { self.hash.hasher.final(&bin_digest); var out_digest: [hex_digest_len]u8 = undefined; - _ = std.fmt.bufPrint( + _ = fmt.bufPrint( &out_digest, "{s}", - .{std.fmt.fmtSliceHexLower(&bin_digest)}, + .{fmt.fmtSliceHexLower(&bin_digest)}, ) catch unreachable; return out_digest; @@ -831,10 +845,10 @@ pub const Manifest = struct { var encoded_digest: [hex_digest_len]u8 = undefined; for (self.files.items) |file| { - _ = std.fmt.bufPrint( + _ = fmt.bufPrint( &encoded_digest, "{s}", - .{std.fmt.fmtSliceHexLower(&file.bin_digest)}, + .{fmt.fmtSliceHexLower(&file.bin_digest)}, ) catch unreachable; try writer.print("{d} {d} {d} {s} {d} {s}\n", .{ file.stat.size, From 7ffdbb3b855ef9e4aa25a8ac911fce752a71e16d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 22:36:27 -0700 Subject: [PATCH 184/294] std.debug.TTY.Config: add yellow --- lib/std/debug.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 97acf81af6..3c5f6d2edf 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -635,6 +635,7 @@ pub const TTY = struct { pub const Color = enum { Red, Green, + Yellow, Cyan, White, Dim, @@ -659,6 +660,7 @@ pub const TTY = struct { const color_string = switch (color) { .Red => "\x1b[31;1m", .Green => "\x1b[32;1m", + .Yellow => "\x1b[33;1m", .Cyan => "\x1b[36;1m", .White => "\x1b[37;1m", .Bold => "\x1b[1m", @@ -671,6 +673,7 @@ pub const TTY = struct { const attributes = switch (color) { .Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, .Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .Yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, .Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, .White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, .Dim => windows.FOREGROUND_INTENSITY, From 9bf63b09963ca6ea1179dfaa9142498556bfac9d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 22:37:07 -0700 Subject: [PATCH 185/294] stage2: avoid linux-only APIs on other operating systems --- src/Compilation.zig | 2 +- src/link.zig | 26 +++++++++++++------- src/link/Elf.zig | 58 ++++++++++++++++++++++++++------------------- src/main.zig | 6 ++--- src/test.zig | 3 +-- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 478f931718..28e7c43702 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1847,7 +1847,7 @@ fn cleanupTmpArtifactDirectory( } } -pub fn hotCodeSwap(comp: *Compilation, prog_node: *std.Progress.Node, pid: std.os.pid_t) !void { +pub fn hotCodeSwap(comp: *Compilation, prog_node: *std.Progress.Node, pid: std.ChildProcess.Id) !void { comp.bin_file.child_pid = pid; try comp.makeBinFileWritable(); try comp.update(prog_node); diff --git a/src/link.zig b/src/link.zig index 96931dd79e..e68f9c97d0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -264,7 +264,7 @@ pub const File = struct { /// of this linking operation. lock: ?Cache.Lock = null, - child_pid: ?std.os.pid_t = null, + child_pid: ?std.ChildProcess.Id = null, /// Attempts incremental linking, if the file already exists. If /// incremental linking fails, falls back to truncating the file and @@ -388,10 +388,14 @@ pub const File = struct { }); try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); - - switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0, 0))) { - .SUCCESS => {}, - else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), + switch (builtin.os.tag) { + .linux => { + switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0, 0))) { + .SUCCESS => {}, + else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), + } + }, + else => return error.HotSwapUnavailableOnHostOperatingSystem, } } base.file = try emit.directory.handle.createFile(emit.sub_path, .{ @@ -444,9 +448,14 @@ pub const File = struct { base.file = null; if (base.child_pid) |pid| { - switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0, 0))) { - .SUCCESS => {}, - else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), + switch (builtin.os.tag) { + .linux => { + switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0, 0))) { + .SUCCESS => {}, + else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), + } + }, + else => return error.HotSwapUnavailableOnHostOperatingSystem, } } }, @@ -487,6 +496,7 @@ pub const File = struct { NetNameDeleted, DeviceBusy, InvalidArgument, + HotSwapUnavailableOnHostOperatingSystem, }; /// Called from within the CodeGen to lower a local variable instantion as an unnamed diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b849dcc9d2..5943277908 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2453,18 +2453,23 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s const file_offset = self.sections.items(.shdr)[shdr_index].sh_offset + section_offset; if (self.base.child_pid) |pid| { - var code_vec: [1]std.os.iovec_const = .{.{ - .iov_base = code.ptr, - .iov_len = code.len, - }}; - var remote_vec: [1]std.os.iovec_const = .{.{ - .iov_base = @intToPtr([*]u8, local_sym.st_value), - .iov_len = code.len, - }}; - const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0); - switch (std.os.errno(rc)) { - .SUCCESS => assert(rc == code.len), - else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + switch (builtin.os.tag) { + .linux => { + var code_vec: [1]std.os.iovec_const = .{.{ + .iov_base = code.ptr, + .iov_len = code.len, + }}; + var remote_vec: [1]std.os.iovec_const = .{.{ + .iov_base = @intToPtr([*]u8, local_sym.st_value), + .iov_len = code.len, + }}; + const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0); + switch (std.os.errno(rc)) { + .SUCCESS => assert(rc == code.len), + else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + } + }, + else => return error.HotSwapUnavailableOnHostOperatingSystem, } } @@ -2856,18 +2861,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { try self.base.file.?.pwriteAll(&buf, off); if (self.base.child_pid) |pid| { - var local_vec: [1]std.os.iovec_const = .{.{ - .iov_base = &buf, - .iov_len = buf.len, - }}; - var remote_vec: [1]std.os.iovec_const = .{.{ - .iov_base = @intToPtr([*]u8, vaddr), - .iov_len = buf.len, - }}; - const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); - switch (std.os.errno(rc)) { - .SUCCESS => assert(rc == buf.len), - else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + switch (builtin.os.tag) { + .linux => { + var local_vec: [1]std.os.iovec_const = .{.{ + .iov_base = &buf, + .iov_len = buf.len, + }}; + var remote_vec: [1]std.os.iovec_const = .{.{ + .iov_base = @intToPtr([*]u8, vaddr), + .iov_len = buf.len, + }}; + const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); + switch (std.os.errno(rc)) { + .SUCCESS => assert(rc == buf.len), + else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}), + } + }, + else => return error.HotSwapUnavailableOnHostOperatingSystem, } } }, diff --git a/src/main.zig b/src/main.zig index 6bf7177a9e..70051d2cc7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3534,7 +3534,7 @@ fn serve( try serveStringMessage(out, .zig_version, build_options.version); - var child_pid: ?i32 = null; + var child_pid: ?std.ChildProcess.Id = null; var receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(gpa); defer receive_fifo.deinit(); @@ -3978,7 +3978,7 @@ fn runOrTestHotSwap( arg_mode: ArgMode, all_args: []const []const u8, runtime_args_start: ?usize, -) !i32 { +) !std.ChildProcess.Id { const exe_emit = comp.bin_file.options.emit.?; // A naive `directory.join` here will indeed get the correct path to the binary, // however, in the case of cwd, we actually want `./foo` so that the path can be executed. @@ -4023,7 +4023,7 @@ fn runOrTestHotSwap( try child.spawn(); - return child.pid; + return child.id; } const AfterUpdateHook = union(enum) { diff --git a/src/test.zig b/src/test.zig index 663c4f1aff..5b73e516c6 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1606,9 +1606,8 @@ pub const TestContext = struct { var module_node = update_node.start("parse/analysis/codegen", 0); module_node.activate(); - module_node.context.refresh(); try comp.makeBinFileWritable(); - try comp.update(); + try comp.update(&module_node); module_node.end(); if (update.case != .Error) { From dcec4d55e36f48e459f4e8f218b8619d9be925db Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 22:38:07 -0700 Subject: [PATCH 186/294] eliminate stderr usage in std.Build make() functions * Eliminate all uses of `std.debug.print` in make() functions, instead properly using the step failure reporting mechanism. * Introduce the concept of skipped build steps. These do not cause the build to fail, and they do allow their dependants to run. * RunStep gains a new flag, `skip_foreign_checks` which causes the RunStep to be skipped if stdio mode is `check` and the binary cannot be executed due to it being a foreign executable. - RunStep is improved to automatically use known interpreters to execute binaries if possible (integrating with flags such as -fqemu and -fwasmtime). It only does this after attempting a native execution and receiving a "exec file format" error. - Update RunStep to use an ArrayList for the checks rather than this ad-hoc reallocation/copying mechanism. - `expectStdOutEqual` now also implicitly adds an exit_code==0 check if there is not already an expected termination. This matches previously expected behavior from older API and can be overridden by directly setting the checks array. * Add `dest_sub_path` to `InstallArtifactStep` which allows choosing an arbitrary subdirectory relative to the prefix, as well as overriding the basename. - Delete the custom InstallWithRename step that I found deep in the test/ directory. * WriteFileStep will now update its step display name after the first file is added. * Add missing stdout checks to various standalone test case build scripts. --- build.zig | 3 +- lib/build_runner.zig | 35 +- lib/std/Build/CheckFileStep.zig | 7 +- lib/std/Build/CheckObjectStep.zig | 138 ++++--- lib/std/Build/CompileStep.zig | 3 +- lib/std/Build/ConfigHeaderStep.zig | 23 +- lib/std/Build/InstallArtifactStep.zig | 8 +- lib/std/Build/ObjCopyStep.zig | 3 +- lib/std/Build/RunStep.zig | 454 +++++++++++++++------- lib/std/Build/Step.zig | 23 +- lib/std/Build/WriteFileStep.zig | 114 ++++-- lib/std/child_process.zig | 1 - test/link/macho/bugs/13457/build.zig | 4 +- test/link/macho/empty/build.zig | 3 +- test/link/macho/needed_library/build.zig | 1 + test/link/macho/objc/build.zig | 4 +- test/link/macho/search_strategy/build.zig | 3 +- test/link/macho/stack_size/build.zig | 1 + test/link/macho/uuid/build.zig | 76 +--- test/link/wasm/extern/build.zig | 3 +- 20 files changed, 554 insertions(+), 353 deletions(-) diff --git a/build.zig b/build.zig index 5f7e214d35..d32f666dac 100644 --- a/build.zig +++ b/build.zig @@ -385,7 +385,7 @@ pub fn build(b: *std.Build) !void { const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index]; const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" }; - const fmt_exclude_paths = &.{ "test/cases" }; + const fmt_exclude_paths = &.{"test/cases"}; const check_fmt = b.addFmt(.{ .paths = fmt_include_paths, .exclude_paths = fmt_exclude_paths, @@ -402,7 +402,6 @@ pub fn build(b: *std.Build) !void { const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting"); do_fmt_step.dependOn(&do_fmt.step); - test_step.dependOn(tests.addPkgTests( b, test_filter, diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 40f45d9ac8..aa846ce799 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -357,6 +357,7 @@ fn runStepNames( } var success_count: usize = 0; + var skipped_count: usize = 0; var failure_count: usize = 0; var pending_count: usize = 0; var total_compile_errors: usize = 0; @@ -379,6 +380,7 @@ fn runStepNames( }, .dependency_failure => pending_count += 1, .success => success_count += 1, + .skipped => skipped_count += 1, .failure => { failure_count += 1; const compile_errors_len = s.result_error_bundle.errorMessageCount(); @@ -395,13 +397,13 @@ fn runStepNames( if (failure_count == 0 and enable_summary != true) return cleanExit(); if (enable_summary != false) { - const total_count = success_count + failure_count + pending_count; + const total_count = success_count + failure_count + pending_count + skipped_count; ttyconf.setColor(stderr, .Cyan) catch {}; stderr.writeAll("Build Summary:") catch {}; ttyconf.setColor(stderr, .Reset) catch {}; - stderr.writer().print(" {d}/{d} steps succeeded; {d} failed", .{ - success_count, total_count, failure_count, - }) catch {}; + stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; + if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {}; + if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {}; if (enable_summary == null) { ttyconf.setColor(stderr, .Dim) catch {}; @@ -503,6 +505,12 @@ fn printTreeStep( try ttyconf.setColor(stderr, .Reset); }, + .skipped => { + try ttyconf.setColor(stderr, .Yellow); + try stderr.writeAll(" skipped\n"); + try ttyconf.setColor(stderr, .Reset); + }, + .failure => { try ttyconf.setColor(stderr, .Red); if (s.result_error_bundle.errorMessageCount() > 0) { @@ -569,6 +577,7 @@ fn checkForDependencyLoop( .running => unreachable, .success => unreachable, .failure => unreachable, + .skipped => unreachable, } } @@ -587,7 +596,7 @@ fn workerMakeOneStep( // queue this step up again when dependencies are met. for (s.dependencies.items) |dep| { switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) { - .success => continue, + .success, .skipped => continue, .failure, .dependency_failure => { @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst); return; @@ -639,13 +648,15 @@ fn workerMakeOneStep( } } - make_result catch |err| { - assert(err == error.MakeFailed); - @atomicStore(Step.State, &s.state, .failure, .SeqCst); - return; - }; - - @atomicStore(Step.State, &s.state, .success, .SeqCst); + if (make_result) |_| { + @atomicStore(Step.State, &s.state, .success, .SeqCst); + } else |err| switch (err) { + error.MakeFailed => { + @atomicStore(Step.State, &s.state, .failure, .SeqCst); + return; + }, + error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst), + } // Successful completion of a step, so we queue up its dependants as well. for (s.dependants.items) |dep| { diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index f70a29840e..03b23d0b03 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -42,15 +42,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { for (self.expected_matches) |expected_match| { if (mem.indexOf(u8, contents, expected_match) == null) { - std.debug.print( + return step.fail( \\ - \\========= Expected to find: =================== + \\========= expected to find: =================== \\{s} - \\========= But file does not contain it: ======= + \\========= but file does not contain it: ======= \\{s} \\ , .{ expected_match, contents }); - return error.TestFailed; } } } diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig index 57d280da0e..2a58850fab 100644 --- a/lib/std/Build/CheckObjectStep.zig +++ b/lib/std/Build/CheckObjectStep.zig @@ -133,7 +133,8 @@ const Action = struct { /// Will return true if the `phrase` is correctly parsed into an RPN program and /// its reduced, computed value compares using `op` with the expected value, either /// a literal or another extracted variable. - fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool { + fn computeCmp(act: Action, step: *Step, global_vars: anytype) !bool { + const gpa = step.owner.allocator; var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); var values = std.ArrayList(u64).init(gpa); @@ -150,11 +151,11 @@ const Action = struct { } else { const val = std.fmt.parseInt(u64, next, 0) catch blk: { break :blk global_vars.get(next) orelse { - std.debug.print( + try step.addError( \\ - \\========= Variable was not extracted: =========== + \\========= variable was not extracted: =========== \\{s} - \\ + \\================================================= , .{next}); return error.UnknownVariable; }; @@ -186,11 +187,11 @@ const Action = struct { const exp_value = switch (act.expected.?.value) { .variable => |name| global_vars.get(name) orelse { - std.debug.print( + try step.addError( \\ - \\========= Variable was not extracted: =========== + \\========= variable was not extracted: =========== \\{s} - \\ + \\================================================= , .{name}); return error.UnknownVariable; }, @@ -323,14 +324,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { ); const output = switch (self.obj_format) { - .macho => try MachODumper.parseAndDump(contents, .{ - .gpa = gpa, + .macho => try MachODumper.parseAndDump(step, contents, .{ .dump_symtab = self.dump_symtab, }), .elf => @panic("TODO elf parser"), .coff => @panic("TODO coff parser"), - .wasm => try WasmDumper.parseAndDump(contents, .{ - .gpa = gpa, + .wasm => try WasmDumper.parseAndDump(step, contents, .{ .dump_symtab = self.dump_symtab, }), else => unreachable, @@ -346,54 +345,50 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { while (it.next()) |line| { if (try act.match(line, &vars)) break; } else { - std.debug.print( + return step.fail( \\ - \\========= Expected to find: ========================== + \\========= expected to find: ========================== \\{s} - \\========= But parsed file does not contain it: ======= + \\========= but parsed file does not contain it: ======= \\{s} - \\ + \\====================================================== , .{ act.phrase, output }); - return error.TestFailed; } }, .not_present => { while (it.next()) |line| { if (try act.match(line, &vars)) { - std.debug.print( + return step.fail( \\ - \\========= Expected not to find: =================== + \\========= expected not to find: =================== \\{s} - \\========= But parsed file does contain it: ======== + \\========= but parsed file does contain it: ======== \\{s} - \\ + \\=================================================== , .{ act.phrase, output }); - return error.TestFailed; } } }, .compute_cmp => { - const res = act.computeCmp(gpa, vars) catch |err| switch (err) { + const res = act.computeCmp(step, vars) catch |err| switch (err) { error.UnknownVariable => { - std.debug.print( - \\========= From parsed file: ===================== + return step.fail( + \\========= from parsed file: ===================== \\{s} - \\ + \\================================================= , .{output}); - return error.TestFailed; }, else => |e| return e, }; if (!res) { - std.debug.print( + return step.fail( \\ - \\========= Comparison failed for action: =========== + \\========= comparison failed for action: =========== \\{s} {} - \\========= From parsed file: ======================= + \\========= from parsed file: ======================= \\{s} - \\ + \\=================================================== , .{ act.phrase, act.expected.?, output }); - return error.TestFailed; } }, } @@ -402,7 +397,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } const Opts = struct { - gpa: ?Allocator = null, dump_symtab: bool = false, }; @@ -410,8 +404,8 @@ const MachODumper = struct { const LoadCommandIterator = macho.LoadCommandIterator; const symtab_label = "symtab"; - fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { - const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator + fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { + const gpa = step.owner.allocator; var stream = std.io.fixedBufferStream(bytes); const reader = stream.reader(); @@ -693,8 +687,8 @@ const MachODumper = struct { const WasmDumper = struct { const symtab_label = "symbols"; - fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { - const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator + fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 { + const gpa = step.owner.allocator; if (opts.dump_symtab) { @panic("TODO: Implement symbol table parsing and dumping"); } @@ -715,20 +709,24 @@ const WasmDumper = struct { const writer = output.writer(); while (reader.readByte()) |current_byte| { - const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| { - std.debug.print("Found invalid section id '{d}'\n", .{current_byte}); - return err; + const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch { + return step.fail("Found invalid section id '{d}'", .{current_byte}); }; const section_length = try std.leb.readULEB128(u32, reader); - try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer); + try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer); fbs.pos += section_length; } else |_| {} // reached end of stream return output.toOwnedSlice(); } - fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void { + fn parseAndDumpSection( + step: *Step, + section: std.wasm.Section, + data: []const u8, + writer: anytype, + ) !void { var fbs = std.io.fixedBufferStream(data); const reader = fbs.reader(); @@ -751,7 +749,7 @@ const WasmDumper = struct { => { const entries = try std.leb.readULEB128(u32, reader); try writer.print("\nentries {d}\n", .{entries}); - try dumpSection(section, data[fbs.pos..], entries, writer); + try dumpSection(step, section, data[fbs.pos..], entries, writer); }, .custom => { const name_length = try std.leb.readULEB128(u32, reader); @@ -760,7 +758,7 @@ const WasmDumper = struct { try writer.print("\nname {s}\n", .{name}); if (mem.eql(u8, name, "name")) { - try parseDumpNames(reader, writer, data); + try parseDumpNames(step, reader, writer, data); } else if (mem.eql(u8, name, "producers")) { try parseDumpProducers(reader, writer, data); } else if (mem.eql(u8, name, "target_features")) { @@ -776,7 +774,7 @@ const WasmDumper = struct { } } - fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { + fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { var fbs = std.io.fixedBufferStream(data); const reader = fbs.reader(); @@ -786,19 +784,18 @@ const WasmDumper = struct { while (i < entries) : (i += 1) { const func_type = try reader.readByte(); if (func_type != std.wasm.function_type) { - std.debug.print("Expected function type, found byte '{d}'\n", .{func_type}); - return error.UnexpectedByte; + return step.fail("expected function type, found byte '{d}'", .{func_type}); } const params = try std.leb.readULEB128(u32, reader); try writer.print("params {d}\n", .{params}); var index: u32 = 0; while (index < params) : (index += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); } else index = 0; const returns = try std.leb.readULEB128(u32, reader); try writer.print("returns {d}\n", .{returns}); while (index < returns) : (index += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); } } }, @@ -812,9 +809,8 @@ const WasmDumper = struct { const name = data[fbs.pos..][0..name_len]; fbs.pos += name_len; - const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| { - std.debug.print("Invalid import kind\n", .{}); - return err; + const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch { + return step.fail("invalid import kind", .{}); }; try writer.print( @@ -831,11 +827,11 @@ const WasmDumper = struct { try parseDumpLimits(reader, writer); }, .global => { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); }, .table => { - try parseDumpType(std.wasm.RefType, reader, writer); + try parseDumpType(step, std.wasm.RefType, reader, writer); try parseDumpLimits(reader, writer); }, } @@ -850,7 +846,7 @@ const WasmDumper = struct { .table => { var i: u32 = 0; while (i < entries) : (i += 1) { - try parseDumpType(std.wasm.RefType, reader, writer); + try parseDumpType(step, std.wasm.RefType, reader, writer); try parseDumpLimits(reader, writer); } }, @@ -863,9 +859,9 @@ const WasmDumper = struct { .global => { var i: u32 = 0; while (i < entries) : (i += 1) { - try parseDumpType(std.wasm.Valtype, reader, writer); + try parseDumpType(step, std.wasm.Valtype, reader, writer); try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); - try parseDumpInit(reader, writer); + try parseDumpInit(step, reader, writer); } }, .@"export" => { @@ -875,9 +871,8 @@ const WasmDumper = struct { const name = data[fbs.pos..][0..name_len]; fbs.pos += name_len; const kind_byte = try std.leb.readULEB128(u8, reader); - const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| { - std.debug.print("invalid export kind value '{d}'\n", .{kind_byte}); - return err; + const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch { + return step.fail("invalid export kind value '{d}'", .{kind_byte}); }; const index = try std.leb.readULEB128(u32, reader); try writer.print( @@ -892,7 +887,7 @@ const WasmDumper = struct { var i: u32 = 0; while (i < entries) : (i += 1) { try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - try parseDumpInit(reader, writer); + try parseDumpInit(step, reader, writer); const function_indexes = try std.leb.readULEB128(u32, reader); var function_index: u32 = 0; @@ -908,7 +903,7 @@ const WasmDumper = struct { while (i < entries) : (i += 1) { const index = try std.leb.readULEB128(u32, reader); try writer.print("memory index 0x{x}\n", .{index}); - try parseDumpInit(reader, writer); + try parseDumpInit(step, reader, writer); const size = try std.leb.readULEB128(u32, reader); try writer.print("size {d}\n", .{size}); try reader.skipBytes(size, .{}); // we do not care about the content of the segments @@ -918,11 +913,10 @@ const WasmDumper = struct { } } - fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void { + fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void { const type_byte = try reader.readByte(); - const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| { - std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte}); - return err; + const valtype = std.meta.intToEnum(WasmType, type_byte) catch { + return step.fail("Invalid wasm type value '{d}'", .{type_byte}); }; try writer.print("type {s}\n", .{@tagName(valtype)}); } @@ -937,11 +931,10 @@ const WasmDumper = struct { } } - fn parseDumpInit(reader: anytype, writer: anytype) !void { + fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { const byte = try std.leb.readULEB128(u8, reader); - const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| { - std.debug.print("invalid wasm opcode '{d}'\n", .{byte}); - return err; + const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { + return step.fail("invalid wasm opcode '{d}'", .{byte}); }; switch (opcode) { .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), @@ -953,14 +946,13 @@ const WasmDumper = struct { } const end_opcode = try std.leb.readULEB128(u8, reader); if (end_opcode != std.wasm.opcode(.end)) { - std.debug.print("expected 'end' opcode in init expression\n", .{}); - return error.MissingEndOpcode; + return step.fail("expected 'end' opcode in init expression", .{}); } } - fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void { + fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void { while (reader.context.pos < data.len) { - try parseDumpType(std.wasm.NameSubsection, reader, writer); + try parseDumpType(step, std.wasm.NameSubsection, reader, writer); const size = try std.leb.readULEB128(u32, reader); const entries = try std.leb.readULEB128(u32, reader); try writer.print( diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index f1a8f71334..99d99694c3 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -538,8 +538,7 @@ pub fn run(cs: *CompileStep) *RunStep { } pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - const b = self.step.owner; - return CheckObjectStep.create(b, self.getOutputSource(), obj_format); + return CheckObjectStep.create(self.step.owner, self.getOutputSource(), obj_format); } pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index 8b4c05bab7..37b04e75a4 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -192,13 +192,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try output.appendSlice(c_generated_line); const src_path = file_source.getPath(b); const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); - try render_autoconf(contents, &output, self.values, src_path); + try render_autoconf(step, contents, &output, self.values, src_path); }, .cmake => |file_source| { try output.appendSlice(c_generated_line); const src_path = file_source.getPath(b); const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); - try render_cmake(contents, &output, self.values, src_path); + try render_cmake(step, contents, &output, self.values, src_path); }, .blank => { try output.appendSlice(c_generated_line); @@ -234,8 +234,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { output_dir; var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) }); - return err; + return step.fail("unable to make path '{s}': {s}", .{ output_dir, @errorName(err) }); }; defer dir.close(); @@ -247,6 +246,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } fn render_autoconf( + step: *Step, contents: []const u8, output: *std.ArrayList(u8), values: std.StringArrayHashMap(Value), @@ -273,7 +273,7 @@ fn render_autoconf( } const name = it.rest(); const kv = values_copy.fetchSwapRemove(name) orelse { - std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ + try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ src_path, line_index + 1, name, }); any_errors = true; @@ -283,15 +283,17 @@ fn render_autoconf( } for (values_copy.keys()) |name| { - std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); + try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); + any_errors = true; } if (any_errors) { - return error.HeaderConfigFailed; + return error.MakeFailed; } } fn render_cmake( + step: *Step, contents: []const u8, output: *std.ArrayList(u8), values: std.StringArrayHashMap(Value), @@ -317,14 +319,14 @@ fn render_cmake( continue; } const name = it.next() orelse { - std.debug.print("{s}:{d}: error: missing define name\n", .{ + try step.addError("{s}:{d}: error: missing define name", .{ src_path, line_index + 1, }); any_errors = true; continue; }; const kv = values_copy.fetchSwapRemove(name) orelse { - std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{ + try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ src_path, line_index + 1, name, }); any_errors = true; @@ -334,7 +336,8 @@ fn render_cmake( } for (values_copy.keys()) |name| { - std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name }); + try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); + any_errors = true; } if (any_errors) { diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index 7e35d0a5ee..377cba301c 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -12,6 +12,9 @@ artifact: *CompileStep, dest_dir: InstallDir, pdb_dir: ?InstallDir, h_dir: ?InstallDir, +/// If non-null, adds additional path components relative to dest_dir, and +/// overrides the basename of the CompileStep. +dest_sub_path: ?[]const u8, pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { if (artifact.install_step) |s| return s; @@ -40,6 +43,7 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { } } else null, .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, + .dest_sub_path = null, }; self.step.dependOn(&artifact.step); artifact.install_step = self; @@ -71,7 +75,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const self = @fieldParentPtr(InstallArtifactStep, "step", step); const dest_builder = self.dest_builder; - const full_dest_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_filename); + const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; + const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); + try src_builder.updateFile( self.artifact.getOutputSource().getPath(src_builder), full_dest_path, diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index 7199431ee6..13046b3efe 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -95,8 +95,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename }); const cache_path = "o" ++ fs.path.sep_str ++ digest; b.cache_root.handle.makePath(cache_path) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) }); - return err; + return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); }; var argv = std.ArrayList([]const u8).init(b.allocator); diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 087483fea8..9304cc758e 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -10,6 +10,7 @@ const ArrayList = std.ArrayList; const EnvMap = process.EnvMap; const Allocator = mem.Allocator; const ExecError = std.Build.ExecError; +const assert = std.debug.assert; const RunStep = @This(); @@ -54,6 +55,8 @@ rename_step_with_output_arg: bool = true, /// Command-line arguments such as -fqemu and -fwasmtime may affect whether a /// binary is detected as foreign, as well as system configuration such as /// Rosetta (macOS) and binfmt_misc (Linux). +/// If this RunStep is considered to have side-effects, then this flag does +/// nothing. skip_foreign_checks: bool = false, /// If stderr or stdout exceeds this amount, the child process is killed and @@ -79,7 +82,7 @@ pub const StdIo = union(enum) { /// conditions. /// Note that an explicit check for exit code 0 needs to be added to this /// list if such a check is desireable. - check: []const Check, + check: std.ArrayList(Check), pub const Check = union(enum) { expect_stderr_exact: []const u8, @@ -214,14 +217,20 @@ pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); } +/// Adds a check for exact stderr match. Does not add any other checks. pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; self.addCheck(new_check); } +/// Adds a check for exact stdout match as well as a check for exit code 0, if +/// there is not already an expected termination check. pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; self.addCheck(new_check); + if (!self.hasTermCheck()) { + self.expectExitCode(0); + } } pub fn expectExitCode(self: *RunStep, code: u8) void { @@ -229,19 +238,21 @@ pub fn expectExitCode(self: *RunStep, code: u8) void { self.addCheck(new_check); } +pub fn hasTermCheck(self: RunStep) bool { + for (self.stdio.check.items) |check| switch (check) { + .expect_term => return true, + else => continue, + }; + return false; +} + pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { - const arena = self.step.owner.allocator; switch (self.stdio) { .infer_from_args => { - const list = arena.create([1]StdIo.Check) catch @panic("OOM"); - list.* = .{new_check}; - self.stdio = .{ .check = list }; - }, - .check => |checks| { - const new_list = arena.alloc(StdIo.Check, checks.len + 1) catch @panic("OOM"); - std.mem.copy(StdIo.Check, new_list, checks); - new_list[checks.len] = new_check; + self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) }; + self.stdio.check.append(new_check) catch @panic("OOM"); }, + .check => |*checks| checks.append(new_check) catch @panic("OOM"), else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), } } @@ -298,14 +309,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const b = step.owner; + const arena = b.allocator; const self = @fieldParentPtr(RunStep, "step", step); const has_side_effects = self.hasSideEffects(); - var argv_list = ArrayList([]const u8).init(b.allocator); + var argv_list = ArrayList([]const u8).init(arena); var output_placeholders = ArrayList(struct { index: usize, output: Arg.Output, - }).init(b.allocator); + }).init(arena); var man = b.cache.obtain(); defer man.deinit(); @@ -357,7 +369,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const digest = man.final(); for (output_placeholders.items) |placeholder| { placeholder.output.generated_file.path = try b.cache_root.join( - b.allocator, + arena, &.{ "o", &digest, placeholder.output.basename }, ); } @@ -367,30 +379,21 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const digest = man.final(); for (output_placeholders.items) |placeholder| { - const output_path = try b.cache_root.join( - b.allocator, - &.{ "o", &digest, placeholder.output.basename }, - ); - const output_dir = fs.path.dirname(output_path).?; - fs.cwd().makePath(output_dir) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) }); - return err; + const output_components = .{ "o", &digest, placeholder.output.basename }; + const output_sub_path = try fs.path.join(arena, &output_components); + const output_sub_dir_path = fs.path.dirname(output_sub_path).?; + b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, output_sub_dir_path, @errorName(err), + }); }; - + const output_path = try b.cache_root.join(arena, &output_components); placeholder.output.generated_file.path = output_path; argv_list.items[placeholder.index] = output_path; } } - try runCommand( - step, - self.cwd, - argv_list.items, - self.env_map, - self.stdio, - has_side_effects, - self.max_stdio_size, - ); + try runCommand(self, argv_list.items, has_side_effects); if (!has_side_effects) { try man.writeManifest(); @@ -442,49 +445,265 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) }; } -fn runCommand( - step: *Step, - opt_cwd: ?[]const u8, - argv: []const []const u8, - env_map: ?*EnvMap, - stdio: StdIo, - has_side_effects: bool, - max_stdio_size: usize, -) !void { +fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) !void { + const step = &self.step; const b = step.owner; const arena = b.allocator; - const cwd = if (opt_cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path; - try step.handleChildProcUnsupported(opt_cwd, argv); - try Step.handleVerbose(step.owner, opt_cwd, argv); + try step.handleChildProcUnsupported(self.cwd, argv); + try Step.handleVerbose(step.owner, self.cwd, argv); + + var stdout_bytes: ?[]const u8 = null; + var stderr_bytes: ?[]const u8 = null; + + const term = spawnChildAndCollect(self, argv, &stdout_bytes, &stderr_bytes, has_side_effects) catch |err| term: { + if (err == error.InvalidExe) interpret: { + // TODO: learn the target from the binary directly rather than from + // relying on it being a CompileStep. This will make this logic + // work even for the edge case that the binary was produced by a + // third party. + const exe = switch (self.argv.items[0]) { + .artifact => |exe| exe, + else => break :interpret, + }; + if (exe.kind != .exe) break :interpret; + + var interp_argv = std.ArrayList([]const u8).init(b.allocator); + defer interp_argv.deinit(); + + const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; + switch (b.host.getExternalExecutor(exe.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, + .link_libc = exe.is_linking_libc, + })) { + .native, .rosetta => { + if (self.stdio == .check and self.skip_foreign_checks) + return error.MakeSkipped; + + break :interpret; + }, + .wine => |bin_name| { + if (b.enable_wine) { + try interp_argv.append(bin_name); + } else { + return failForeign(self, "-fwine", argv[0], exe); + } + }, + .qemu => |bin_name| { + if (b.enable_qemu) { + const glibc_dir_arg = if (need_cross_glibc) + b.glibc_runtimes_dir orelse return + else + null; + + try interp_argv.append(bin_name); + + if (glibc_dir_arg) |dir| { + // TODO look into making this a call to `linuxTriple`. This + // needs the directory to be called "i686" rather than + // "x86" which is why we do it manually here. + const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; + const cpu_arch = exe.target.getCpuArch(); + const os_tag = exe.target.getOsTag(); + const abi = exe.target.getAbi(); + const cpu_arch_name: []const u8 = if (cpu_arch == .x86) + "i686" + else + @tagName(cpu_arch); + const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ + dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), + }); + + try interp_argv.append("-L"); + try interp_argv.append(full_dir); + } + } else { + return failForeign(self, "-fqemu", argv[0], exe); + } + }, + .darling => |bin_name| { + if (b.enable_darling) { + try interp_argv.append(bin_name); + } else { + return failForeign(self, "-fdarling", argv[0], exe); + } + }, + .wasmtime => |bin_name| { + if (b.enable_wasmtime) { + try interp_argv.append(bin_name); + try interp_argv.append("--dir=."); + } else { + return failForeign(self, "-fwasmtime", argv[0], exe); + } + }, + .bad_dl => |foreign_dl| { + if (self.stdio == .check and self.skip_foreign_checks) + return error.MakeSkipped; + + const host_dl = b.host.dynamic_linker.get() orelse "(none)"; + + return step.fail( + \\the host system is unable to execute binaries from the target + \\ because the host dynamic linker is '{s}', + \\ while the target dynamic linker is '{s}'. + \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step + , .{ host_dl, foreign_dl }); + }, + .bad_os_or_cpu => { + if (self.stdio == .check and self.skip_foreign_checks) + return error.MakeSkipped; + + const host_name = try b.host.target.zigTriple(b.allocator); + const foreign_name = try exe.target.zigTriple(b.allocator); + + return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ + host_name, foreign_name, + }); + }, + } + + if (exe.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + RunStep.addPathForDynLibsInternal(&self.step, b, exe); + } + + try interp_argv.append(argv[0]); + + try Step.handleVerbose(step.owner, self.cwd, interp_argv.items); + + assert(stdout_bytes == null); + assert(stderr_bytes == null); + break :term spawnChildAndCollect(self, interp_argv.items, &stdout_bytes, &stderr_bytes, has_side_effects) catch |inner_err| { + return step.fail("unable to spawn {s}: {s}", .{ + interp_argv.items[0], @errorName(inner_err), + }); + }; + } + + return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); + }; + + switch (self.stdio) { + .check => |checks| for (checks.items) |check| switch (check) { + .expect_stderr_exact => |expected_bytes| { + if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) { + return step.fail( + \\ + \\========= expected this stderr: ========= + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following command: === + \\{s} + , .{ + expected_bytes, + stderr_bytes.?, + try Step.allocPrintCmd(arena, self.cwd, argv), + }); + } + }, + .expect_stderr_match => |match| { + if (mem.indexOf(u8, stderr_bytes.?, match) == null) { + return step.fail( + \\ + \\========= expected to find in stderr: ========= + \\{s} + \\========= but stderr does not contain it: ===== + \\{s} + \\========= from the following command: ========= + \\{s} + , .{ + match, + stderr_bytes.?, + try Step.allocPrintCmd(arena, self.cwd, argv), + }); + } + }, + .expect_stdout_exact => |expected_bytes| { + if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) { + return step.fail( + \\ + \\========= expected this stdout: ========= + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following command: === + \\{s} + , .{ + expected_bytes, + stdout_bytes.?, + try Step.allocPrintCmd(arena, self.cwd, argv), + }); + } + }, + .expect_stdout_match => |match| { + if (mem.indexOf(u8, stdout_bytes.?, match) == null) { + return step.fail( + \\ + \\========= expected to find in stdout: ========= + \\{s} + \\========= but stdout does not contain it: ===== + \\{s} + \\========= from the following command: ========= + \\{s} + , .{ + match, + stdout_bytes.?, + try Step.allocPrintCmd(arena, self.cwd, argv), + }); + } + }, + .expect_term => |expected_term| { + if (!termMatches(expected_term, term)) { + return step.fail("the following command {} (expected {}):\n{s}", .{ + fmtTerm(term), + fmtTerm(expected_term), + try Step.allocPrintCmd(arena, self.cwd, argv), + }); + } + }, + }, + else => { + try step.handleChildProcessTerm(term, self.cwd, argv); + }, + } +} + +fn spawnChildAndCollect( + self: *RunStep, + argv: []const []const u8, + stdout_bytes: *?[]const u8, + stderr_bytes: *?[]const u8, + has_side_effects: bool, +) !std.ChildProcess.Term { + const b = self.step.owner; + const arena = b.allocator; + const cwd = if (self.cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path; var child = std.ChildProcess.init(argv, arena); child.cwd = cwd; - child.env_map = env_map orelse b.env_map; + child.env_map = self.env_map orelse b.env_map; - child.stdin_behavior = switch (stdio) { + child.stdin_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Ignore, .inherit => .Inherit, .check => .Close, }; - child.stdout_behavior = switch (stdio) { + child.stdout_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Ignore, .inherit => .Inherit, - .check => |checks| if (checksContainStdout(checks)) .Pipe else .Ignore, + .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, }; - child.stderr_behavior = switch (stdio) { + child.stderr_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Pipe, .inherit => .Inherit, .check => .Pipe, }; - child.spawn() catch |err| return step.fail("unable to spawn {s}: {s}", .{ + child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err), }); - var stdout_bytes: ?[]const u8 = null; - var stderr_bytes: ?[]const u8 = null; - if (child.stdout) |stdout| { if (child.stderr) |stderr| { var poller = std.io.poll(arena, enum { stdout, stderr }, .{ @@ -494,115 +713,32 @@ fn runCommand( defer poller.deinit(); while (try poller.poll()) { - if (poller.fifo(.stdout).count > max_stdio_size) + if (poller.fifo(.stdout).count > self.max_stdio_size) return error.StdoutStreamTooLong; - if (poller.fifo(.stderr).count > max_stdio_size) + if (poller.fifo(.stderr).count > self.max_stdio_size) return error.StderrStreamTooLong; } - stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); - stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); + stdout_bytes.* = try poller.fifo(.stdout).toOwnedSlice(); + stderr_bytes.* = try poller.fifo(.stderr).toOwnedSlice(); } else { - stdout_bytes = try stdout.reader().readAllAlloc(arena, max_stdio_size); + stdout_bytes.* = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); } } else if (child.stderr) |stderr| { - stderr_bytes = try stderr.reader().readAllAlloc(arena, max_stdio_size); + stderr_bytes.* = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); } - if (stderr_bytes) |stderr| if (stderr.len > 0) { - const stderr_is_diagnostic = switch (stdio) { - .check => |checks| !checksContainStderr(checks), + if (stderr_bytes.*) |stderr| if (stderr.len > 0) { + const stderr_is_diagnostic = switch (self.stdio) { + .check => |checks| !checksContainStderr(checks.items), else => true, }; if (stderr_is_diagnostic) { - try step.result_error_msgs.append(arena, stderr); + try self.step.result_error_msgs.append(arena, stderr); } }; - const term = child.wait() catch |err| { - return step.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) }); - }; - - switch (stdio) { - .check => |checks| for (checks) |check| switch (check) { - .expect_stderr_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) { - return step.fail( - \\========= expected this stderr: ========= - \\{s} - \\========= but found: ==================== - \\{s} - \\========= from the following command: === - \\{s} - , .{ - expected_bytes, - stderr_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), - }); - } - }, - .expect_stderr_match => |match| { - if (mem.indexOf(u8, stderr_bytes.?, match) == null) { - return step.fail( - \\========= expected to find in stderr: ========= - \\{s} - \\========= but stderr does not contain it: ===== - \\{s} - \\========= from the following command: ========= - \\{s} - , .{ - match, - stderr_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), - }); - } - }, - .expect_stdout_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) { - return step.fail( - \\========= expected this stdout: ========= - \\{s} - \\========= but found: ==================== - \\{s} - \\========= from the following command: === - \\{s} - , .{ - expected_bytes, - stdout_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), - }); - } - }, - .expect_stdout_match => |match| { - if (mem.indexOf(u8, stdout_bytes.?, match) == null) { - return step.fail( - \\========= expected to find in stdout: ========= - \\{s} - \\========= but stdout does not contain it: ===== - \\{s} - \\========= from the following command: ========= - \\{s} - , .{ - match, - stdout_bytes.?, - try Step.allocPrintCmd(arena, opt_cwd, argv), - }); - } - }, - .expect_term => |expected_term| { - if (!termMatches(expected_term, term)) { - return step.fail("the following command {} (expected {}):\n{s}", .{ - fmtTerm(term), - fmtTerm(expected_term), - try Step.allocPrintCmd(arena, opt_cwd, argv), - }); - } - }, - }, - else => { - try step.handleChildProcessTerm(term, opt_cwd, argv); - }, - } + return child.wait(); } fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { @@ -624,3 +760,29 @@ pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *Co } } } + +fn failForeign( + self: *RunStep, + suggested_flag: []const u8, + argv0: []const u8, + exe: *CompileStep, +) error{ MakeFailed, MakeSkipped, OutOfMemory } { + switch (self.stdio) { + .check => { + if (self.skip_foreign_checks) + return error.MakeSkipped; + + const b = self.step.owner; + const host_name = try b.host.target.zigTriple(b.allocator); + const foreign_name = try exe.target.zigTriple(b.allocator); + + return self.step.fail( + \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) + \\ consider using {s} or enabling skip_foreign_checks in the Run step + , .{ argv0, foreign_name, host_name, suggested_flag }); + }, + else => { + return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); + }, + } +} diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 3219588050..928e7e8735 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -26,6 +26,9 @@ pub const State = enum { dependency_failure, success, failure, + /// This state indicates that the step did not complete, however, it also did not fail, + /// and it is safe to continue executing its dependencies. + skipped, }; pub const Id = enum { @@ -106,13 +109,15 @@ pub fn init(options: Options) Step { /// If the Step's `make` function reports `error.MakeFailed`, it indicates they /// have already reported the error. Otherwise, we add a simple error report /// here. -pub fn make(s: *Step, prog_node: *std.Progress.Node) error{MakeFailed}!void { - return s.makeFn(s, prog_node) catch |err| { - if (err != error.MakeFailed) { +pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void { + return s.makeFn(s, prog_node) catch |err| switch (err) { + error.MakeFailed => return error.MakeFailed, + error.MakeSkipped => return error.MakeSkipped, + else => { const gpa = s.dependencies.allocator; s.result_error_msgs.append(gpa, @errorName(err)) catch @panic("OOM"); - } - return error.MakeFailed; + return error.MakeFailed; + }, }; } @@ -192,10 +197,14 @@ pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void { } pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } { + try step.addError(fmt, args); + return error.MakeFailed; +} + +pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { const arena = step.owner.allocator; const msg = try std.fmt.allocPrint(arena, fmt, args); try step.result_error_msgs.append(arena, msg); - return error.MakeFailed; } /// Assumes that argv contains `--listen=-` and that the process being spawned @@ -398,5 +407,5 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer const i = man.failed_file_index orelse return err; const pp = man.files.items[i].prefixed_path orelse return err; const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; - return s.fail("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path }); + return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path }); } diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index d554d6efc1..b5164d5730 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -37,7 +37,7 @@ pub fn init(owner: *std.Build) WriteFileStep { return .{ .step = Step.init(.{ .id = .write_file, - .name = "writefile", + .name = "WriteFile", .owner = owner, .makeFn = make, }), @@ -56,6 +56,8 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { .contents = .{ .bytes = b.dupe(bytes) }, }; wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); } /// Place the file into the generated directory within the local cache, @@ -75,6 +77,8 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [ .contents = .{ .copy = source }, }; wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); } /// A path relative to the package root. @@ -101,6 +105,15 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo return null; } +fn maybeUpdateName(wf: *WriteFileStep) void { + if (wf.files.items.len == 1) { + // First time adding a file; update name. + if (std.mem.eql(u8, wf.step.name, "WriteFile")) { + wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); + } + } +} + fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const b = step.owner; @@ -110,14 +123,39 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // WriteFileStep - arguably it should be a different step. But anyway here // it is, it happens unconditionally and does not interact with the other // files here. + var any_miss = false; for (wf.output_source_files.items) |output_source_file| { - const basename = fs.path.basename(output_source_file.sub_path); if (fs.path.dirname(output_source_file.sub_path)) |dirname| { - var dir = try b.build_root.handle.makeOpenPath(dirname, .{}); - defer dir.close(); - try writeFile(wf, dir, output_source_file.contents, basename); - } else { - try writeFile(wf, b.build_root.handle, output_source_file.contents, basename); + b.build_root.handle.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.build_root, dirname, @errorName(err), + }); + }; + } + switch (output_source_file.contents) { + .bytes => |bytes| { + b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = true; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + b.build_root.handle, + output_source_file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ + source_path, b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = any_miss or prev_status == .stale; + }, } } @@ -164,19 +202,52 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const cache_path = "o" ++ fs.path.sep_str ++ digest; var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { - std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) }); - return err; + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, cache_path, @errorName(err), + }); }; defer cache_dir.close(); for (wf.files.items) |file| { - const basename = fs.path.basename(file.sub_path); if (fs.path.dirname(file.sub_path)) |dirname| { - var dir = try b.cache_root.handle.makeOpenPath(dirname, .{}); - defer dir.close(); - try writeFile(wf, dir, file.contents, basename); - } else { - try writeFile(wf, cache_dir, file.contents, basename); + cache_dir.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), + }); + }; + } + switch (file.contents) { + .bytes => |bytes| { + cache_dir.writeFile(file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), + }); + }; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + cache_dir, + file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ + source_path, + b.cache_root, + cache_path, + fs.path.sep, + file.sub_path, + @errorName(err), + }); + }; + // At this point we already will mark the step as a cache miss. + // But this is kind of a partial cache hit since individual + // file copies may be avoided. Oh well, this information is + // discarded. + _ = prev_status; + }, } file.generated_file.path = try b.cache_root.join( @@ -188,19 +259,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try man.writeManifest(); } -fn writeFile(wf: *WriteFileStep, dir: fs.Dir, contents: Contents, basename: []const u8) !void { - const b = wf.step.owner; - // TODO after landing concurrency PR, improve error reporting here - switch (contents) { - .bytes => |bytes| return dir.writeFile(basename, bytes), - .copy => |file_source| { - const source_path = file_source.getPath(b); - const prev_status = try fs.Dir.updateFile(fs.cwd(), source_path, dir, basename, .{}); - _ = prev_status; // TODO logging (affected by open PR regarding concurrency) - }, - } -} - const std = @import("../std.zig"); const Step = std.Build.Step; const fs = std.fs; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index f8cba85874..3bdef3177a 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -185,7 +185,6 @@ pub const ChildProcess = struct { } /// Blocks until child process terminates and then cleans up all resources. - /// TODO: set the pid to undefined in this function. pub fn wait(self: *ChildProcess) !Term { const term = if (builtin.os.tag == .windows) try self.waitWindows() diff --git a/test/link/macho/bugs/13457/build.zig b/test/link/macho/bugs/13457/build.zig index 3560b4a168..7ac1435015 100644 --- a/test/link/macho/bugs/13457/build.zig +++ b/test/link/macho/bugs/13457/build.zig @@ -13,6 +13,8 @@ pub fn build(b: *std.Build) void { .target = target, }); - const run = exe.runEmulatable(); + const run = b.addRunArtifact(exe); + run.skip_foreign_checks = true; + run.expectStdOutEqual(""); test_step.dependOn(&run.step); } diff --git a/test/link/macho/empty/build.zig b/test/link/macho/empty/build.zig index 586da1511b..12eb67d9c8 100644 --- a/test/link/macho/empty/build.zig +++ b/test/link/macho/empty/build.zig @@ -16,7 +16,8 @@ pub fn build(b: *std.Build) void { exe.addCSourceFile("empty.c", &[0][]const u8{}); exe.linkLibC(); - const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.skip_foreign_checks = true; run_cmd.expectStdOutEqual("Hello!\n"); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/needed_library/build.zig b/test/link/macho/needed_library/build.zig index 92a73d22b7..e8a58d5381 100644 --- a/test/link/macho/needed_library/build.zig +++ b/test/link/macho/needed_library/build.zig @@ -36,5 +36,6 @@ pub fn build(b: *std.Build) void { check.checkNext("name @rpath/liba.dylib"); const run_cmd = check.runAndCompare(); + run_cmd.expectStdOutEqual(""); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/objc/build.zig b/test/link/macho/objc/build.zig index 10d293baab..9398e28a80 100644 --- a/test/link/macho/objc/build.zig +++ b/test/link/macho/objc/build.zig @@ -17,6 +17,8 @@ pub fn build(b: *std.Build) void { // populate paths to the sysroot here. exe.linkFramework("Foundation"); - const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.skip_foreign_checks = true; + run_cmd.expectStdOutEqual(""); test_step.dependOn(&run_cmd.step); } diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index 62757f885b..a8a4167865 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -27,7 +27,8 @@ pub fn build(b: *std.Build) void { const exe = createScenario(b, optimize, target); exe.search_strategy = .paths_first; - const run = std.Build.EmulatableRunStep.create(b, "run", exe); + const run = b.addRunArtifact(exe); + run.skip_foreign_checks = true; run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 3529a134eb..874f53fbff 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -21,5 +21,6 @@ pub fn build(b: *std.Build) void { check_exe.checkNext("stacksize 100000000"); const run = check_exe.runAndCompare(); + run.expectStdOutEqual(""); test_step.dependOn(&run.step); } diff --git a/test/link/macho/uuid/build.zig b/test/link/macho/uuid/build.zig index ec7664cd72..6ff45e1175 100644 --- a/test/link/macho/uuid/build.zig +++ b/test/link/macho/uuid/build.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const Builder = std.Build.Builder; const CompileStep = std.Build.CompileStep; const FileSource = std.Build.FileSource; const Step = std.Build.Step; @@ -38,13 +37,15 @@ fn testUuid( // stay the same across builds. { const dylib = simpleDylib(b, optimize, target); - const install_step = installWithRename(dylib, "test1.dylib"); + const install_step = b.addInstallArtifact(dylib); + install_step.dest_sub_path = "test1.dylib"; install_step.step.dependOn(&dylib.step); } { const dylib = simpleDylib(b, optimize, target); dylib.strip = true; - const install_step = installWithRename(dylib, "test2.dylib"); + const install_step = b.addInstallArtifact(dylib); + install_step.dest_sub_path = "test2.dylib"; install_step.step.dependOn(&dylib.step); } @@ -68,70 +69,23 @@ fn simpleDylib( return dylib; } -fn installWithRename(cs: *CompileStep, name: []const u8) *InstallWithRename { - const step = InstallWithRename.create(cs.builder, cs.getOutputSource(), name); - cs.builder.getInstallStep().dependOn(&step.step); - return step; -} - -const InstallWithRename = struct { - pub const base_id = .custom; - - step: Step, - builder: *Builder, - source: FileSource, - name: []const u8, - - pub fn create( - builder: *Builder, - source: FileSource, - name: []const u8, - ) *InstallWithRename { - const self = builder.allocator.create(InstallWithRename) catch @panic("OOM"); - self.* = InstallWithRename{ - .builder = builder, - .step = Step.init(builder.allocator, .{ - .id = .custom, - .name = builder.fmt("install and rename: {s} -> {s}", .{ - source.getDisplayName(), name, - }), - .makeFn = make, - }), - .source = source, - .name = builder.dupe(name), - }; - return self; - } - - fn make(step: *Step) anyerror!void { - const self = @fieldParentPtr(InstallWithRename, "step", step); - const source_path = self.source.getPath(self.builder); - const target_path = self.builder.getInstallPath(.lib, self.name); - self.builder.updateFile(source_path, target_path) catch |err| { - std.log.err("Unable to rename: {s} -> {s}", .{ source_path, target_path }); - return err; - }; - } -}; - const CompareUuid = struct { pub const base_id = .custom; step: Step, - builder: *Builder, lhs: []const u8, rhs: []const u8, - pub fn create(builder: *Builder, lhs: []const u8, rhs: []const u8) *CompareUuid { - const self = builder.allocator.create(CompareUuid) catch @panic("OOM"); + pub fn create(owner: *std.Build, lhs: []const u8, rhs: []const u8) *CompareUuid { + const self = owner.allocator.create(CompareUuid) catch @panic("OOM"); self.* = CompareUuid{ - .builder = builder, - .step = Step.init(builder.allocator, .{ - .id = .custom, - .name = builder.fmt("compare uuid: {s} and {s}", .{ + .step = Step.init(.{ + .id = base_id, + .name = owner.fmt("compare uuid: {s} and {s}", .{ lhs, rhs, }), + .owner = owner, .makeFn = make, }), .lhs = lhs, @@ -140,16 +94,18 @@ const CompareUuid = struct { return self; } - fn make(step: *Step) anyerror!void { + fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void { + _ = prog_node; + const b = step.owner; const self = @fieldParentPtr(CompareUuid, "step", step); - const gpa = self.builder.allocator; + const gpa = b.allocator; var lhs_uuid: [16]u8 = undefined; - const lhs_path = self.builder.getInstallPath(.lib, self.lhs); + const lhs_path = b.getInstallPath(.lib, self.lhs); try parseUuid(gpa, lhs_path, &lhs_uuid); var rhs_uuid: [16]u8 = undefined; - const rhs_path = self.builder.getInstallPath(.lib, self.rhs); + const rhs_path = b.getInstallPath(.lib, self.rhs); try parseUuid(gpa, rhs_path, &rhs_uuid); try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid); diff --git a/test/link/wasm/extern/build.zig b/test/link/wasm/extern/build.zig index 569d94091a..fede2b18ab 100644 --- a/test/link/wasm/extern/build.zig +++ b/test/link/wasm/extern/build.zig @@ -11,7 +11,8 @@ pub fn build(b: *std.Build) void { exe.use_llvm = false; exe.use_lld = false; - const run = exe.runEmulatable(); + const run = b.addRunArtifact(exe); + run.skip_foreign_checks = true; run.expectStdOutEqual("Result: 30"); const test_step = b.step("test", "Run linker test"); From d6f7766da298accfa84120dbc7f770a6ab5b25e0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 23:46:25 -0700 Subject: [PATCH 187/294] stage2: avoid networking when generating zig2.c Avoid dragging networking into zig2.c because it adds dependencies on some linker symbols that are annoying to satisfy while bootstrapping. --- src/main.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 70051d2cc7..93082a964d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -668,9 +668,13 @@ const ArgMode = union(enum) { run, }; +/// Avoid dragging networking into zig2.c because it adds dependencies on some +/// linker symbols that are annoying to satisfy while bootstrapping. +const Ip4Address = if (build_options.omit_pkg_fetching_code) void else std.net.Ip4Address; + const Listen = union(enum) { none, - ip4: std.net.Ip4Address, + ip4: Ip4Address, stdio, }; @@ -1159,6 +1163,7 @@ fn buildOutputType( listen = .stdio; watch = true; } else { + if (build_options.omit_pkg_fetching_code) unreachable; // example: --listen 127.0.0.1:9000 var it = std.mem.split(u8, next_arg, ":"); const host = it.next().?; @@ -3303,6 +3308,8 @@ fn buildOutputType( return cleanExit(); }, .ip4 => |ip4_addr| { + if (build_options.omit_pkg_fetching_code) unreachable; + var server = std.net.StreamServer.init(.{ .reuse_address = true, }); From d0cf34a328d7818203e87b8d379e22067b7041e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 23:56:28 -0700 Subject: [PATCH 188/294] stage2: fix compilation on 32-bit targets --- src/link/Elf.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5943277908..f1ab98372e 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2460,7 +2460,7 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s .iov_len = code.len, }}; var remote_vec: [1]std.os.iovec_const = .{.{ - .iov_base = @intToPtr([*]u8, local_sym.st_value), + .iov_base = @intToPtr([*]u8, @intCast(usize, local_sym.st_value)), .iov_len = code.len, }}; const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0); @@ -2868,7 +2868,7 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { .iov_len = buf.len, }}; var remote_vec: [1]std.os.iovec_const = .{.{ - .iov_base = @intToPtr([*]u8, vaddr), + .iov_base = @intToPtr([*]u8, @intCast(usize, vaddr)), .iov_len = buf.len, }}; const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0); From 677a0e294116b0fcc1d69eea99c2fa62eb9873fe Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Mar 2023 23:56:56 -0700 Subject: [PATCH 189/294] stage2: avoid bloat when using -Donly-c --- build.zig | 2 +- src/main.zig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index d32f666dac..40210500d8 100644 --- a/build.zig +++ b/build.zig @@ -192,7 +192,7 @@ pub fn build(b: *std.Build) !void { exe_options.addOption(bool, "llvm_has_arc", llvm_has_arc); exe_options.addOption(bool, "force_gpa", force_gpa); exe_options.addOption(bool, "only_c", only_c); - exe_options.addOption(bool, "omit_pkg_fetching_code", false); + exe_options.addOption(bool, "omit_pkg_fetching_code", only_c); if (link_libc) { exe.linkLibC(); diff --git a/src/main.zig b/src/main.zig index 93082a964d..f81911ffd0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3295,6 +3295,7 @@ fn buildOutputType( switch (listen) { .none => {}, .stdio => { + if (build_options.only_c) unreachable; try serve( comp, std.io.getStdIn(), From 8b054e190a977ccd27302debd5d0f95c28597005 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 13:23:22 -0700 Subject: [PATCH 190/294] std.Build.RunStep: work around a miscompilation See #14783 Also, set the cwd directory handle when spawning the child process if available. --- lib/std/Build/RunStep.zig | 104 ++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 9304cc758e..6224c62897 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -22,6 +22,8 @@ step: Step, argv: ArrayList(Arg), /// Set this to modify the current working directory +/// TODO change this to a Build.Cache.Directory to better integrate with +/// future child process cwd API. cwd: ?[]const u8, /// Override this field to modify the environment, or use setEnvironmentVariable @@ -89,7 +91,7 @@ pub const StdIo = union(enum) { expect_stderr_match: []const u8, expect_stdout_exact: []const u8, expect_stdout_match: []const u8, - expect_term: std.ChildProcess.Term, + expect_term: std.process.Child.Term, }; }; @@ -401,7 +403,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } fn formatTerm( - term: ?std.ChildProcess.Term, + term: ?std.process.Child.Term, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, @@ -417,11 +419,11 @@ fn formatTerm( try writer.writeAll("exited with any code"); } } -fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) { +fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) { return .{ .data = term }; } -fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) bool { +fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool { return if (expected) |e| switch (e) { .Exited => |expected_code| switch (actual) { .Exited => |actual_code| expected_code == actual_code, @@ -453,10 +455,7 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) try step.handleChildProcUnsupported(self.cwd, argv); try Step.handleVerbose(step.owner, self.cwd, argv); - var stdout_bytes: ?[]const u8 = null; - var stderr_bytes: ?[]const u8 = null; - - const term = spawnChildAndCollect(self, argv, &stdout_bytes, &stderr_bytes, has_side_effects) catch |err| term: { + const result = spawnChildAndCollect(self, argv, has_side_effects) catch |err| term: { if (err == error.InvalidExe) interpret: { // TODO: learn the target from the binary directly rather than from // relying on it being a CompileStep. This will make this logic @@ -571,11 +570,9 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) try Step.handleVerbose(step.owner, self.cwd, interp_argv.items); - assert(stdout_bytes == null); - assert(stderr_bytes == null); - break :term spawnChildAndCollect(self, interp_argv.items, &stdout_bytes, &stderr_bytes, has_side_effects) catch |inner_err| { + break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects) catch |e| { return step.fail("unable to spawn {s}: {s}", .{ - interp_argv.items[0], @errorName(inner_err), + interp_argv.items[0], @errorName(e), }); }; } @@ -586,7 +583,8 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) switch (self.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stderr_bytes.?)) { + assert(!result.stderr_null); + if (!mem.eql(u8, expected_bytes, result.stderr)) { return step.fail( \\ \\========= expected this stderr: ========= @@ -597,13 +595,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) \\{s} , .{ expected_bytes, - stderr_bytes.?, + result.stderr, try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_stderr_match => |match| { - if (mem.indexOf(u8, stderr_bytes.?, match) == null) { + assert(!result.stderr_null); + if (mem.indexOf(u8, result.stderr, match) == null) { return step.fail( \\ \\========= expected to find in stderr: ========= @@ -614,13 +613,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) \\{s} , .{ match, - stderr_bytes.?, + result.stderr, try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_stdout_exact => |expected_bytes| { - if (!mem.eql(u8, expected_bytes, stdout_bytes.?)) { + assert(!result.stdout_null); + if (!mem.eql(u8, expected_bytes, result.stdout)) { return step.fail( \\ \\========= expected this stdout: ========= @@ -631,13 +631,14 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) \\{s} , .{ expected_bytes, - stdout_bytes.?, + result.stdout, try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_stdout_match => |match| { - if (mem.indexOf(u8, stdout_bytes.?, match) == null) { + assert(!result.stdout_null); + if (mem.indexOf(u8, result.stdout, match) == null) { return step.fail( \\ \\========= expected to find in stdout: ========= @@ -648,15 +649,15 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) \\{s} , .{ match, - stdout_bytes.?, + result.stdout, try Step.allocPrintCmd(arena, self.cwd, argv), }); } }, .expect_term => |expected_term| { - if (!termMatches(expected_term, term)) { + if (!termMatches(expected_term, result.term)) { return step.fail("the following command {} (expected {}):\n{s}", .{ - fmtTerm(term), + fmtTerm(result.term), fmtTerm(expected_term), try Step.allocPrintCmd(arena, self.cwd, argv), }); @@ -664,24 +665,36 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) }, }, else => { - try step.handleChildProcessTerm(term, self.cwd, argv); + try step.handleChildProcessTerm(result.term, self.cwd, argv); }, } } +const ChildProcResult = struct { + // These use boolean flags instead of optionals as a workaround for + // https://github.com/ziglang/zig/issues/14783 + stdout: []const u8, + stderr: []const u8, + stdout_null: bool, + stderr_null: bool, + term: std.process.Child.Term, +}; + fn spawnChildAndCollect( self: *RunStep, argv: []const []const u8, - stdout_bytes: *?[]const u8, - stderr_bytes: *?[]const u8, has_side_effects: bool, -) !std.ChildProcess.Term { +) !ChildProcResult { const b = self.step.owner; const arena = b.allocator; - const cwd = if (self.cwd) |cwd| b.pathFromRoot(cwd) else b.build_root.path; - var child = std.ChildProcess.init(argv, arena); - child.cwd = cwd; + var child = std.process.Child.init(argv, arena); + if (self.cwd) |cwd| { + child.cwd = b.pathFromRoot(cwd); + } else { + child.cwd = b.build_root.path; + child.cwd_dir = b.build_root.handle; + } child.env_map = self.env_map orelse b.env_map; child.stdin_behavior = switch (self.stdio) { @@ -704,6 +717,13 @@ fn spawnChildAndCollect( argv[0], @errorName(err), }); + // These are not optionals, as a workaround for + // https://github.com/ziglang/zig/issues/14783 + var stdout_bytes: []const u8 = undefined; + var stderr_bytes: []const u8 = undefined; + var stdout_null = true; + var stderr_null = true; + if (child.stdout) |stdout| { if (child.stderr) |stderr| { var poller = std.io.poll(arena, enum { stdout, stderr }, .{ @@ -719,26 +739,36 @@ fn spawnChildAndCollect( return error.StderrStreamTooLong; } - stdout_bytes.* = try poller.fifo(.stdout).toOwnedSlice(); - stderr_bytes.* = try poller.fifo(.stderr).toOwnedSlice(); + stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); + stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); + stdout_null = false; + stderr_null = false; } else { - stdout_bytes.* = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); + stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); + stdout_null = false; } } else if (child.stderr) |stderr| { - stderr_bytes.* = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); + stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); + stderr_null = false; } - if (stderr_bytes.*) |stderr| if (stderr.len > 0) { + if (!stderr_null and stderr_bytes.len > 0) { const stderr_is_diagnostic = switch (self.stdio) { .check => |checks| !checksContainStderr(checks.items), else => true, }; if (stderr_is_diagnostic) { - try self.step.result_error_msgs.append(arena, stderr); + try self.step.result_error_msgs.append(arena, stderr_bytes); } - }; + } - return child.wait(); + return .{ + .stdout = stdout_bytes, + .stderr = stderr_bytes, + .stdout_null = stdout_null, + .stderr_null = stderr_null, + .term = try child.wait(), + }; } fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { From 41a5ad28c9a651efd2822f43486a71ca48ace726 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 15:27:53 -0700 Subject: [PATCH 191/294] std: child process API supports rusage data --- lib/std/c.zig | 3 ++- lib/std/child_process.zig | 49 ++++++++++++++++++++++++++++++++++++++- lib/std/os.zig | 22 +++++++++++++++++- lib/std/os/linux.zig | 10 ++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 9fc3b1d57e..a0b65c31c8 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -153,7 +153,8 @@ pub extern "c" fn linkat(oldfd: c.fd_t, oldpath: [*:0]const u8, newfd: c.fd_t, n pub extern "c" fn unlink(path: [*:0]const u8) c_int; pub extern "c" fn unlinkat(dirfd: c.fd_t, path: [*:0]const u8, flags: c_uint) c_int; pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8; -pub extern "c" fn waitpid(pid: c.pid_t, stat_loc: ?*c_int, options: c_int) c.pid_t; +pub extern "c" fn waitpid(pid: c.pid_t, status: ?*c_int, options: c_int) c.pid_t; +pub extern "c" fn wait4(pid: c.pid_t, status: ?*c_int, options: c_int, ru: ?*c.rusage) c.pid_t; pub extern "c" fn fork() c_int; pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int; pub extern "c" fn faccessat(dirfd: c.fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 3bdef3177a..f9b2007b3e 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -17,6 +17,7 @@ const Os = std.builtin.Os; const TailQueue = std.TailQueue; const maxInt = std.math.maxInt; const assert = std.debug.assert; +const is_darwin = builtin.target.isDarwin(); pub const ChildProcess = struct { pub const Id = switch (builtin.os.tag) { @@ -70,6 +71,43 @@ pub const ChildProcess = struct { /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent. start_suspended: bool = false, + /// Set to true to obtain rusage information for the child process. + /// Depending on the target platform and implementation status, the + /// requested statistics may or may not be available. If they are + /// available, then the `resource_usage_statistics` field will be populated + /// after calling `wait`. + /// On Linux, this obtains rusage statistics from wait4(). + request_resource_usage_statistics: bool = false, + + /// This is available after calling wait if + /// `request_resource_usage_statistics` was set to `true` before calling + /// `spawn`. + resource_usage_statistics: ResourceUsageStatistics = .{}, + + pub const ResourceUsageStatistics = struct { + rusage: @TypeOf(rusage_init) = rusage_init, + + /// Returns the peak resident set size of the child process, in bytes, + /// if available. + pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize { + switch (builtin.os.tag) { + .linux => { + if (rus.rusage) |ru| { + return @intCast(usize, ru.maxrss) * 1024; + } else { + return null; + } + }, + else => return null, + } + } + + const rusage_init = switch (builtin.os.tag) { + .linux => @as(?std.os.rusage, null), + else => {}, + }; + }; + pub const Arg0Expand = os.Arg0Expand; pub const SpawnError = error{ @@ -332,7 +370,16 @@ pub const ChildProcess = struct { } fn waitUnwrapped(self: *ChildProcess) !void { - const res: os.WaitPidResult = os.waitpid(self.id, 0); + const res: os.WaitPidResult = res: { + if (builtin.os.tag == .linux and self.request_resource_usage_statistics) { + var ru: std.os.rusage = undefined; + const res = os.wait4(self.id, 0, &ru); + self.resource_usage_statistics.rusage = ru; + break :res res; + } + + break :res os.waitpid(self.id, 0); + }; const status = res.status; self.cleanupStreams(); self.handleWaitResult(status); diff --git a/lib/std/os.zig b/lib/std/os.zig index 069a910408..9d5625c13e 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4000,8 +4000,28 @@ pub const WaitPidResult = struct { pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { const Status = if (builtin.link_libc) c_int else u32; var status: Status = undefined; + const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags; while (true) { - const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags); + const rc = system.waitpid(pid, &status, coerced_flags); + switch (errno(rc)) { + .SUCCESS => return .{ + .pid = @intCast(pid_t, rc), + .status = @bitCast(u32, status), + }, + .INTR => continue, + .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. + .INVAL => unreachable, // Invalid flags. + else => unreachable, + } + } +} + +pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult { + const Status = if (builtin.link_libc) c_int else u32; + var status: Status = undefined; + const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags; + while (true) { + const rc = system.wait4(pid, &status, coerced_flags, ru); switch (errno(rc)) { .SUCCESS => return .{ .pid = @intCast(pid_t, rc), diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 15b0dbc17b..53f6030b5f 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -944,6 +944,16 @@ pub fn waitpid(pid: pid_t, status: *u32, flags: u32) usize { return syscall4(.wait4, @bitCast(usize, @as(isize, pid)), @ptrToInt(status), flags, 0); } +pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize { + return syscall4( + .wait4, + @bitCast(usize, @as(isize, pid)), + @ptrToInt(status), + flags, + @ptrToInt(usage), + ); +} + pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize { return syscall5(.waitid, @enumToInt(id_type), @bitCast(usize, @as(isize, id)), @ptrToInt(infop), flags, 0); } From d695f36e70f162e4e84087df1ccad5430e53d786 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 15:28:38 -0700 Subject: [PATCH 192/294] build runner supports reporting cached status and duration --- lib/build_runner.zig | 36 +++++++++++++++++++++++++++++++++++- lib/std/Build/Step.zig | 7 +++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index aa846ce799..1c07efd944 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -501,8 +501,42 @@ fn printTreeStep( .success => { try ttyconf.setColor(stderr, .Green); - try stderr.writeAll(" success\n"); + if (s.result_cached) { + try stderr.writeAll(" cached"); + } else { + try stderr.writeAll(" success"); + } try ttyconf.setColor(stderr, .Reset); + if (s.result_duration_ns) |ns| { + try ttyconf.setColor(stderr, .Dim); + if (ns >= std.time.ns_per_min) { + try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min}); + } else if (ns >= std.time.ns_per_s) { + try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s}); + } else if (ns >= std.time.ns_per_ms) { + try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms}); + } else { + try stderr.writer().print(" {d}ns", .{ns}); + } + try ttyconf.setColor(stderr, .Reset); + } + if (s.result_peak_rss != 0) { + const rss = s.result_peak_rss; + try ttyconf.setColor(stderr, .Dim); + if (rss >= 1000_000_000_000) { + try stderr.writer().print(" {d}G MaxRSS", .{rss / 1000_000_000_000}); + } else if (rss >= 1000_000_000) { + try stderr.writer().print(" {d}M MaxRSS", .{rss / 1000_000_000}); + } else if (rss >= 1000_000) { + try stderr.writer().print(" {d}M MaxRSS", .{rss / 1000_000}); + } else if (rss >= 1000) { + try stderr.writer().print(" {d}K MaxRSS", .{rss / 1000}); + } else { + try stderr.writer().print(" {d}B MaxRSS", .{rss}); + } + try ttyconf.setColor(stderr, .Reset); + } + try stderr.writeAll("\n"); }, .skipped => { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 928e7e8735..be14373afe 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -13,6 +13,10 @@ debug_stack_trace: [n_debug_stack_frames]usize, result_error_msgs: std.ArrayListUnmanaged([]const u8), result_error_bundle: std.zig.ErrorBundle, +result_cached: bool, +result_duration_ns: ?u64, +/// 0 means unavailable or not reported. +result_peak_rss: usize, pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; @@ -103,6 +107,9 @@ pub fn init(options: Options) Step { .debug_stack_trace = addresses, .result_error_msgs = .{}, .result_error_bundle = std.zig.ErrorBundle.empty, + .result_cached = false, + .result_duration_ns = null, + .result_peak_rss = 0, }; } From 2b23625510e4f250ae925f9a3a4b090fba69f6a8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 15:29:02 -0700 Subject: [PATCH 193/294] std.Build.RunStep: report duration and cached status --- lib/std/Build/RunStep.zig | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 6224c62897..d671ff7ae8 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -375,6 +375,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { &.{ "o", &digest, placeholder.output.basename }, ); } + step.result_cached = true; return; } @@ -580,6 +581,9 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); }; + step.result_duration_ns = result.elapsed_ns; + step.result_peak_rss = result.peak_rss; + switch (self.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { @@ -678,6 +682,8 @@ const ChildProcResult = struct { stdout_null: bool, stderr_null: bool, term: std.process.Child.Term, + elapsed_ns: u64, + peak_rss: usize, }; fn spawnChildAndCollect( @@ -696,6 +702,7 @@ fn spawnChildAndCollect( child.cwd_dir = b.build_root.handle; } child.env_map = self.env_map orelse b.env_map; + child.request_resource_usage_statistics = true; child.stdin_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Ignore, @@ -716,6 +723,7 @@ fn spawnChildAndCollect( child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err), }); + var timer = try std.time.Timer.start(); // These are not optionals, as a workaround for // https://github.com/ziglang/zig/issues/14783 @@ -762,12 +770,17 @@ fn spawnChildAndCollect( } } + const term = try child.wait(); + const elapsed_ns = timer.read(); + return .{ .stdout = stdout_bytes, .stderr = stderr_bytes, .stdout_null = stdout_null, .stderr_null = stderr_null, - .term = try child.wait(), + .term = term, + .elapsed_ns = elapsed_ns, + .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, }; } From 0322e292e8e55a16f5d61defa9b079c5364aabb9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 15:35:07 -0700 Subject: [PATCH 194/294] update test/standalone/sigpipe build.zig script to latest API --- test/standalone/sigpipe/build.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/standalone/sigpipe/build.zig b/test/standalone/sigpipe/build.zig index 763df5fe46..400f1a970d 100644 --- a/test/standalone/sigpipe/build.zig +++ b/test/standalone/sigpipe/build.zig @@ -23,12 +23,12 @@ pub fn build(b: *std.build.Builder) !void { .root_source_file = .{ .path = "breakpipe.zig" }, }); exe.addOptions("build_options", options); - const run = exe.run(); + const run = b.addRunArtifact(exe); if (keep_sigpipe) { - run.expected_term = .{ .Signal = std.os.SIG.PIPE }; + run.addCheck(.{ .expect_term = .{ .Signal = std.os.SIG.PIPE } }); } else { - run.stdout_action = .{ .expect_exact = "BrokenPipe\n" }; - run.expected_term = .{ .Exited = 123 }; + run.addCheck(.{ .expect_stdout_exact = "BrokenPipe\n" }); + run.addCheck(.{ .expect_term = .{ .Exited = 123 } }); } test_step.dependOn(&run.step); } From 80d1976db9efd21920a45890cbf368d808b166e5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 15:35:42 -0700 Subject: [PATCH 195/294] build runner: add microseconds to elapsed in build summary --- lib/build_runner.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 1c07efd944..da07bc5b26 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -515,6 +515,8 @@ fn printTreeStep( try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s}); } else if (ns >= std.time.ns_per_ms) { try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms}); + } else if (ns >= std.time.ns_per_us) { + try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us}); } else { try stderr.writer().print(" {d}ns", .{ns}); } @@ -524,15 +526,15 @@ fn printTreeStep( const rss = s.result_peak_rss; try ttyconf.setColor(stderr, .Dim); if (rss >= 1000_000_000_000) { - try stderr.writer().print(" {d}G MaxRSS", .{rss / 1000_000_000_000}); + try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000_000}); } else if (rss >= 1000_000_000) { - try stderr.writer().print(" {d}M MaxRSS", .{rss / 1000_000_000}); + try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000_000}); } else if (rss >= 1000_000) { - try stderr.writer().print(" {d}M MaxRSS", .{rss / 1000_000}); + try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000}); } else if (rss >= 1000) { - try stderr.writer().print(" {d}K MaxRSS", .{rss / 1000}); + try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000}); } else { - try stderr.writer().print(" {d}B MaxRSS", .{rss}); + try stderr.writer().print(" MaxRSS:{d}B", .{rss}); } try ttyconf.setColor(stderr, .Reset); } From 2996eb558756c697e2ccfe9691356e536c88916b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 16:39:08 -0700 Subject: [PATCH 196/294] std.Build.RunStep: add maxrss, duration, and cached status --- lib/std/Build/ObjCopyStep.zig | 2 +- lib/std/Build/Step.zig | 10 ++++++++- lib/std/zig/Server.zig | 13 ++++++++++- src/Compilation.zig | 3 +++ src/main.zig | 41 ++++++++++++++++++++++++++++------- 5 files changed, 58 insertions(+), 11 deletions(-) diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index 13046b3efe..5f675ed383 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -113,7 +113,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }; try argv.appendSlice(&.{ full_src_path, full_dest_path }); - _ = try step.spawnZigProcess(argv.items, prog_node); + _ = try step.evalZigProcess(argv.items, prog_node); self.output_file.path = full_dest_path; try man.writeManifest(); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index be14373afe..37bd66678b 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -234,10 +234,12 @@ pub fn evalZigProcess( child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; + child.request_resource_usage_statistics = true; child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err), }); + var timer = try std.time.Timer.start(); var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, @@ -301,7 +303,10 @@ pub fn evalZigProcess( sub_prog_node.?.activate(); }, .emit_bin_path => { - result = try arena.dupe(u8, body); + const EbpHdr = std.zig.Server.Message.EmitBinPath; + const ebp_hdr = @ptrCast(*align(1) const EbpHdr, body); + s.result_cached = ebp_hdr.flags.cache_hit; + result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); }, _ => { // Unrecognized message. @@ -323,6 +328,9 @@ pub fn evalZigProcess( const term = child.wait() catch |err| { return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) }); }; + s.result_duration_ns = timer.read(); + s.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; + try handleChildProcessTerm(s, term, null, argv); if (s.result_error_bundle.errorMessageCount() > 0) { diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 76f2303f6b..d34b2193e9 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -12,7 +12,7 @@ pub const Message = struct { error_bundle, /// Body is a UTF-8 string. progress, - /// Body is a UTF-8 string. + /// Body is a EmitBinPath. emit_bin_path, _, }; @@ -25,4 +25,15 @@ pub const Message = struct { extra_len: u32, string_bytes_len: u32, }; + + /// Trailing: + /// * the file system path the emitted binary can be found + pub const EmitBinPath = extern struct { + flags: Flags, + + pub const Flags = packed struct(u8) { + cache_hit: bool, + reserved: u7 = 0, + }; + }; }; diff --git a/src/Compilation.zig b/src/Compilation.zig index 28e7c43702..b542c86511 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -100,6 +100,7 @@ job_queued_compiler_rt_lib: bool = false, job_queued_compiler_rt_obj: bool = false, alloc_failure_occurred: bool = false, formatted_panics: bool = false, +last_update_was_cache_hit: bool = false, c_source_files: []const CSourceFile, clang_argv: []const []const u8, @@ -1860,6 +1861,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void defer tracy_trace.end(); comp.clearMiscFailures(); + comp.last_update_was_cache_hit = false; var man: Cache.Manifest = undefined; defer if (comp.whole_cache_manifest != null) man.deinit(); @@ -1887,6 +1889,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void return err; }; if (is_hit) { + comp.last_update_was_cache_hit = true; log.debug("CacheMode.whole cache hit for {s}", .{comp.bin_file.options.root_name}); const digest = man.final(); diff --git a/src/main.zig b/src/main.zig index f81911ffd0..699122d26f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3578,9 +3578,11 @@ fn serve( var arena_instance = std.heap.ArenaAllocator.init(gpa); defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var output_path: []const u8 = undefined; - try cmdTranslateC(comp, arena, &output_path); - try serveStringMessage(out, .emit_bin_path, output_path); + var output: TranslateCOutput = undefined; + try cmdTranslateC(comp, arena, &output); + try serveEmitBinPath(out, output.path, .{ + .flags = .{ .cache_hit = output.cache_hit }, + }); continue; } @@ -3760,10 +3762,26 @@ fn serveUpdateResults(out: fs.File, comp: *Compilation) !void { } else if (comp.bin_file.options.emit) |emit| { const full_path = try emit.directory.join(gpa, &.{emit.sub_path}); defer gpa.free(full_path); - try serveStringMessage(out, .emit_bin_path, full_path); + try serveEmitBinPath(out, full_path, .{ + .flags = .{ .cache_hit = comp.last_update_was_cache_hit }, + }); } } +fn serveEmitBinPath( + out: fs.File, + fs_path: []const u8, + header: std.zig.Server.Message.EmitBinPath, +) !void { + try serveMessage(out, .{ + .tag = .emit_bin_path, + .bytes_len = @intCast(u32, fs_path.len + @sizeOf(std.zig.Server.Message.EmitBinPath)), + }, &.{ + std.mem.asBytes(&header), + fs_path, + }); +} + fn serveStringMessage(out: fs.File, tag: std.zig.Server.Message.Tag, s: []const u8) !void { try serveMessage(out, .{ .tag = tag, @@ -4115,7 +4133,12 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void } } -fn cmdTranslateC(comp: *Compilation, arena: Allocator, output_path: ?*[]const u8) !void { +const TranslateCOutput = struct { + path: []const u8, + cache_hit: bool, +}; + +fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*TranslateCOutput) !void { if (!build_options.have_llvm) fatal("cannot translate-c: compiler built without LLVM extensions", .{}); @@ -4126,14 +4149,16 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, output_path: ?*[]const u8 var man: Cache.Manifest = comp.obtainCObjectCacheManifest(); man.want_shared_lock = false; - defer if (output_path != null) man.deinit(); + defer man.deinit(); man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| { fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) }); }; + if (fancy_output) |p| p.cache_hit = true; const digest = if (try man.hit()) man.final() else digest: { + if (fancy_output) |p| p.cache_hit = false; var argv = std.ArrayList([]const u8).init(arena); try argv.append(""); // argv[0] is program name, actual args start at [1] @@ -4229,11 +4254,11 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, output_path: ?*[]const u8 break :digest digest; }; - if (output_path) |out_path| { + if (fancy_output) |p| { const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename, }); - out_path.* = full_zig_path; + p.path = full_zig_path; } else { const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename }); const zig_file = comp.local_cache_directory.handle.openFile(out_zig_path, .{}) catch |err| { From 2cc33f5f4e0b55dcc1fb7cc4fb5d3b565b3a50d2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 17:29:20 -0700 Subject: [PATCH 197/294] std.Build.Step.cacheHit marks step as cached on hit --- lib/std/Build/Step.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 37bd66678b..1a7fe24e7c 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -415,7 +415,8 @@ pub fn allocPrintCmd(arena: Allocator, opt_cwd: ?[]const u8, argv: []const []con } pub fn cacheHit(s: *Step, man: *std.Build.Cache.Manifest) !bool { - return man.hit() catch |err| return failWithCacheError(s, man, err); + s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err); + return s.result_cached; } fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyerror) anyerror { From 405bf1b091bd1dba3a2c904c70aa562f41a6b3a3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 17:29:59 -0700 Subject: [PATCH 198/294] std.Build.ConfigHeaderStep: integrate with the cache system --- lib/std/Build/ConfigHeaderStep.zig | 75 ++++++++++++++---------------- lib/std/Build/WriteFileStep.zig | 1 - 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index 37b04e75a4..c1849b410e 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/lib/std/Build/ConfigHeaderStep.zig @@ -1,9 +1,3 @@ -const std = @import("../std.zig"); -const ConfigHeaderStep = @This(); -const Step = std.Build.Step; - -pub const base_id: Step.Id = .config_header; - pub const Style = union(enum) { /// The configure format supported by autotools. It uses `#undef foo` to /// mark lines that can be substituted with different values. @@ -41,6 +35,8 @@ style: Style, max_bytes: usize, include_path: []const u8, +pub const base_id: Step.Id = .config_header; + pub const Options = struct { style: Style = .blank, max_bytes: usize = 2 * 1024 * 1024, @@ -162,23 +158,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const b = step.owner; const self = @fieldParentPtr(ConfigHeaderStep, "step", step); const gpa = b.allocator; + const arena = b.allocator; - // The cache is used here not really as a way to speed things up - because writing - // the data to a file would probably be very fast - but as a way to find a canonical - // location to put build artifacts. + var man = b.cache.obtain(); + defer man.deinit(); - // If, for example, a hard-coded path was used as the location to put ConfigHeaderStep - // files, then two ConfigHeaderStep executing in parallel might clobber each other. - - // TODO port the cache system from the compiler to zig std lib. Until then - // we construct the path directly, and no "cache hit" detection happens; - // the files are always written. - // Note there is very similar code over in WriteFileStep - const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); // Random bytes to make ConfigHeaderStep unique. Refresh this with new // random bytes when ConfigHeaderStep implementation is modified in a // non-backwards-compatible way. - var hash = Hasher.init("PGuDTpidxyMqnkGM"); + man.hash.add(@as(u32, 0xdef08d23)); var output = std.ArrayList(u8).init(gpa); defer output.deinit(); @@ -191,13 +179,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .autoconf => |file_source| { try output.appendSlice(c_generated_line); const src_path = file_source.getPath(b); - const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); + const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); try render_autoconf(step, contents, &output, self.values, src_path); }, .cmake => |file_source| { try output.appendSlice(c_generated_line); const src_path = file_source.getPath(b); - const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); + const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); try render_cmake(step, contents, &output, self.values, src_path); }, .blank => { @@ -210,39 +198,40 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }, } - hash.update(output.items); + man.hash.addBytes(output.items); - var digest: [16]u8 = undefined; - hash.final(&digest); - var hash_basename: [digest.len * 2]u8 = undefined; - _ = std.fmt.bufPrint( - &hash_basename, - "{s}", - .{std.fmt.fmtSliceHexLower(&digest)}, - ) catch unreachable; + if (try step.cacheHit(&man)) { + const digest = man.final(); + self.output_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, self.include_path, + }); + return; + } - const output_dir = try b.cache_root.join(gpa, &.{ "o", &hash_basename }); + const digest = man.final(); // If output_path has directory parts, deal with them. Example: // output_dir is zig-cache/o/HASH // output_path is libavutil/avconfig.h // We want to open directory zig-cache/o/HASH/libavutil/ // but keep output_dir as zig-cache/o/HASH for -I include - const sub_dir_path = if (std.fs.path.dirname(self.include_path)) |d| - try std.fs.path.join(gpa, &.{ output_dir, d }) - else - output_dir; + const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path }); + const sub_path_dirname = std.fs.path.dirname(sub_path).?; - var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| { - return step.fail("unable to make path '{s}': {s}", .{ output_dir, @errorName(err) }); + b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, sub_path_dirname, @errorName(err), + }); }; - defer dir.close(); - try dir.writeFile(std.fs.path.basename(self.include_path), output.items); + b.cache_root.handle.writeFile(sub_path, output.items) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.cache_root, sub_path, @errorName(err), + }); + }; - self.output_file.path = try std.fs.path.join(b.allocator, &.{ - output_dir, self.include_path, - }); + self.output_file.path = try b.cache_root.join(arena, &.{sub_path}); + try man.writeManifest(); } fn render_autoconf( @@ -442,3 +431,7 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) ! }, } } + +const std = @import("../std.zig"); +const ConfigHeaderStep = @This(); +const Step = std.Build.Step; diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index b5164d5730..1109cf5426 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -187,7 +187,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } if (try step.cacheHit(&man)) { - // Cache hit, skip writing file data. const digest = man.final(); for (wf.files.items) |file| { file.generated_file.path = try b.cache_root.join( From bb1960c2a4ae3257fab3eedd7fdf2293fde59cec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 17:49:14 -0700 Subject: [PATCH 199/294] std.Build.InstallDirStep: avoid std.log And better make use of open directory handles. --- lib/std/Build/InstallDirStep.zig | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig index 11553d9bc7..2807d641f3 100644 --- a/lib/std/Build/InstallDirStep.zig +++ b/lib/std/Build/InstallDirStep.zig @@ -4,7 +4,6 @@ const fs = std.fs; const Step = std.Build.Step; const InstallDir = std.Build.InstallDir; const InstallDirStep = @This(); -const log = std.log; step: Step, options: Options, @@ -57,17 +56,17 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const self = @fieldParentPtr(InstallDirStep, "step", step); const dest_builder = self.dest_builder; + const arena = dest_builder.allocator; const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir); const src_builder = self.step.owner; - const full_src_dir = src_builder.pathFromRoot(self.options.source_dir); - var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| { - log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{ - full_src_dir, @errorName(err), + var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| { + return step.fail("unable to open source directory '{}{s}': {s}", .{ + src_builder.build_root, self.options.source_dir, @errorName(err), }); - return error.StepFailed; }; defer src_dir.close(); - var it = try src_dir.walk(dest_builder.allocator); + var it = try src_dir.walk(arena); + var all_cached = true; next_entry: while (try it.next()) |entry| { for (self.options.exclude_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) { @@ -75,11 +74,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - const full_path = dest_builder.pathJoin(&.{ full_src_dir, entry.path }); - const dest_path = dest_builder.pathJoin(&.{ dest_prefix, entry.path }); + // relative to src build root + const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path }); + const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path }); + const cwd = fs.cwd(); switch (entry.kind) { - .Directory => try fs.cwd().makePath(dest_path), + .Directory => try cwd.makePath(dest_path), .File => { for (self.options.blank_extensions) |ext| { if (mem.endsWith(u8, entry.path, ext)) { @@ -88,9 +89,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - try dest_builder.updateFile(full_path, dest_path); + const prev_status = try fs.Dir.updateFile( + src_builder.build_root.handle, + src_sub_path, + cwd, + dest_path, + .{}, + ); + all_cached = all_cached and prev_status == .fresh; }, else => continue, } } + + step.result_cached = all_cached; } From 1e63573d359f15042ebdcc9b0c32d7ce4662899f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Mar 2023 20:39:40 -0700 Subject: [PATCH 200/294] std.build.CompileStep: eliminate std.log usage --- lib/build_runner.zig | 3 + lib/std/Build.zig | 2 + lib/std/Build/CompileStep.zig | 81 ++++++++++++--------------- lib/std/Build/InstallArtifactStep.zig | 2 +- lib/std/Build/RemoveDirStep.zig | 1 - 5 files changed, 42 insertions(+), 47 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index da07bc5b26..c99d55b783 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -179,6 +179,8 @@ pub fn main() !void { usageAndErr(builder, false, stderr_stream); }; try debug_log_scopes.append(next_arg); + } else if (mem.eql(u8, arg, "--debug-pkg-config")) { + builder.debug_pkg_config = true; } else if (mem.eql(u8, arg, "--debug-compile-errors")) { builder.debug_compile_errors = true; } else if (mem.eql(u8, arg, "--glibc-runtimes")) { @@ -809,6 +811,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\ --zig-lib-dir [arg] Override path to Zig lib directory \\ --build-runner [file] Override path to build runner \\ --debug-log [scope] Enable debugging the compiler + \\ --debug-pkg-config Fail if unknown pkg-config flags encountered \\ --verbose-link Enable compiler debug output for linking \\ --verbose-air Enable compiler debug output for Zig AIR \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 1d87ea961f..8f46499fdc 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -85,6 +85,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, args: ?[][]const u8 = null, debug_log_scopes: []const []const u8 = &.{}, debug_compile_errors: bool = false, +debug_pkg_config: bool = false, /// Experimental. Use system Darling installation to run cross compiled macOS build artifacts. enable_darling: bool = false, @@ -316,6 +317,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc .zig_lib_dir = parent.zig_lib_dir, .debug_log_scopes = parent.debug_log_scopes, .debug_compile_errors = parent.debug_compile_errors, + .debug_pkg_config = parent.debug_pkg_config, .enable_darling = parent.enable_darling, .enable_qemu = parent.enable_qemu, .enable_rosetta = parent.enable_rosetta, diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 99d99694c3..6802f4fb70 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1,7 +1,6 @@ const builtin = @import("builtin"); const std = @import("../std.zig"); const mem = std.mem; -const log = std.log; const fs = std.fs; const assert = std.debug.assert; const panic = std.debug.panic; @@ -697,7 +696,7 @@ pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []cons /// Run pkg-config for the given library name and parse the output, returning the arguments /// that should be passed to zig to link the given library. -pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { +fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { const b = self.step.owner; const pkg_name = match: { // First we have to map the library name to pkg config name. Unfortunately, @@ -783,8 +782,8 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); } else if (mem.startsWith(u8, tok, "-D")) { try zig_args.append(tok); - } else if (b.verbose) { - log.warn("Ignoring pkg-config flag '{s}'", .{tok}); + } else if (b.debug_pkg_config) { + return self.step.fail("unknown pkg-config flag '{s}'", .{tok}); } } @@ -1190,8 +1189,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const self = @fieldParentPtr(CompileStep, "step", step); if (self.root_src == null and self.link_objects.items.len == 0) { - log.err("{s}: linker needs 1 or more objects to link", .{self.step.name}); - return error.NeedAnObject; + return step.fail("the linker needs one or more objects to link", .{}); } var zig_args = ArrayList([]const u8).init(b.allocator); @@ -1280,10 +1278,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .system_lib => |system_lib| { const prefix: []const u8 = prefix: { if (system_lib.needed) break :prefix "-needed-l"; - if (system_lib.weak) { - if (self.target.isDarwin()) break :prefix "-weak-l"; - log.warn("Weak library import used for a non-darwin target, this will be converted to normally library import `-lname`", .{}); - } + if (system_lib.weak) break :prefix "-weak-l"; break :prefix "-l"; }; switch (system_lib.use_pkg_config) { @@ -1774,18 +1769,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(c_macro); } - if (self.target.isDarwin()) { - for (self.framework_dirs.items) |dir| { - if (b.sysroot != null) { - try zig_args.append("-iframeworkwithsysroot"); - } else { - try zig_args.append("-iframework"); - } - try zig_args.append(dir); - try zig_args.append("-F"); - try zig_args.append(dir); + for (self.framework_dirs.items) |dir| { + if (b.sysroot != null) { + try zig_args.append("-iframeworkwithsysroot"); + } else { + try zig_args.append("-iframework"); } + try zig_args.append(dir); + try zig_args.append("-F"); + try zig_args.append(dir); + } + { var it = self.frameworks.iterator(); while (it.next()) |entry| { const name = entry.key_ptr.*; @@ -1799,14 +1794,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } try zig_args.append(name); } - } else { - if (self.framework_dirs.items.len > 0) { - log.info("Framework directories have been added for a non-darwin target, this will have no affect on the build", .{}); - } - - if (self.frameworks.count() > 0) { - log.info("Frameworks have been added for a non-darwin target, this will have no affect on the build", .{}); - } } if (b.sysroot) |sysroot| { @@ -1970,8 +1957,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and self.version != null and self.target.wantSharedLibSymLinks()) { - try doAtomicSymLinks(b.allocator, self.getOutputSource().getPath(b), self.major_only_filename.?, self.name_only_filename.?); + if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and + self.version != null and self.target.wantSharedLibSymLinks()) + { + try doAtomicSymLinks( + step, + self.getOutputSource().getPath(b), + self.major_only_filename.?, + self.name_only_filename.?, + ); } } @@ -2013,30 +2007,27 @@ fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { } pub fn doAtomicSymLinks( - allocator: Allocator, + step: *Step, output_path: []const u8, filename_major_only: []const u8, filename_name_only: []const u8, ) !void { + const arena = step.owner.allocator; const out_dir = fs.path.dirname(output_path) orelse "."; const out_basename = fs.path.basename(output_path); // sym link for libfoo.so.1 to libfoo.so.1.2.3 - const major_only_path = try fs.path.join( - allocator, - &[_][]const u8{ out_dir, filename_major_only }, - ); - fs.atomicSymLink(allocator, out_basename, major_only_path) catch |err| { - log.err("Unable to symlink {s} -> {s}", .{ major_only_path, out_basename }); - return err; + const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only }); + fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| { + return step.fail("unable to symlink {s} -> {s}: {s}", .{ + major_only_path, out_basename, @errorName(err), + }); }; // sym link for libfoo.so to libfoo.so.1 - const name_only_path = try fs.path.join( - allocator, - &[_][]const u8{ out_dir, filename_name_only }, - ); - fs.atomicSymLink(allocator, filename_major_only, name_only_path) catch |err| { - log.err("Unable to symlink {s} -> {s}", .{ name_only_path, filename_major_only }); - return err; + const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only }); + fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| { + return step.fail("Unable to symlink {s} -> {s}: {s}", .{ + name_only_path, filename_major_only, @errorName(err), + }); }; } diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index 377cba301c..c3002c7f54 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -83,7 +83,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { full_dest_path, ); if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) { - try CompileStep.doAtomicSymLinks(src_builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); + try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); } if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) { const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); diff --git a/lib/std/Build/RemoveDirStep.zig b/lib/std/Build/RemoveDirStep.zig index 9f291c7523..a5bf3c3256 100644 --- a/lib/std/Build/RemoveDirStep.zig +++ b/lib/std/Build/RemoveDirStep.zig @@ -1,5 +1,4 @@ const std = @import("../std.zig"); -const log = std.log; const fs = std.fs; const Step = std.Build.Step; const RemoveDirStep = @This(); From a4c35a62454a3970dd44ac8b842be91a46bb896c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Mar 2023 16:11:04 -0700 Subject: [PATCH 201/294] std.Build: audit use of updateFile * remove std.Build.updateFile. I noticed some people use it from build.zig (declare phase) when it is intended only for use in the make phase. - This also was incorrectly reporting errors with std.log. * std.Build.InstallArtifactStep - report better errors on failure - report whether the step was cached or not * std.Build.InstallDirStep: report better error on failure * std.Build.InstallFileStep: report better error on failure --- lib/std/Build.zig | 12 ------ lib/std/Build/CompileStep.zig | 6 +-- lib/std/Build/InstallArtifactStep.zig | 53 ++++++++++++++++++++++----- lib/std/Build/InstallDirStep.zig | 8 +++- lib/std/Build/InstallFileStep.zig | 8 +++- 5 files changed, 60 insertions(+), 27 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 8f46499fdc..e3b44e965e 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1235,18 +1235,6 @@ pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u self.installed_files.append(file.dupe(self)) catch @panic("OOM"); } -pub fn updateFile(self: *Build, source_path: []const u8, dest_path: []const u8) !void { - if (self.verbose) { - log.info("cp {s} {s} ", .{ source_path, dest_path }); - } - const cwd = fs.cwd(); - const prev_status = try fs.Dir.updateFile(cwd, source_path, cwd, dest_path, .{}); - if (self.verbose) switch (prev_status) { - .stale => log.info("# installed", .{}), - .fresh => log.info("# up-to-date", .{}), - }; -} - pub fn truncateFile(self: *Build, dest_path: []const u8) !void { if (self.verbose) { log.info("truncate {s}", .{dest_path}); diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 6802f4fb70..895c1a7678 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1910,13 +1910,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const build_output_dir = fs.path.dirname(output_bin_path).?; if (self.output_dir) |output_dir| { - var src_dir = try std.fs.cwd().openIterableDir(build_output_dir, .{}); + var src_dir = try fs.cwd().openIterableDir(build_output_dir, .{}); defer src_dir.close(); // Create the output directory if it doesn't exist. - try std.fs.cwd().makePath(output_dir); + try fs.cwd().makePath(output_dir); - var dest_dir = try std.fs.cwd().openDir(output_dir, .{}); + var dest_dir = try fs.cwd().openDir(output_dir, .{}); defer dest_dir.close(); var it = src_dir.iterate(); diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index c3002c7f54..803998a619 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -3,6 +3,7 @@ const Step = std.Build.Step; const CompileStep = std.Build.CompileStep; const InstallDir = std.Build.InstallDir; const InstallArtifactStep = @This(); +const fs = std.fs; pub const base_id = .install_artifact; @@ -77,25 +78,59 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); + const cwd = fs.cwd(); - try src_builder.updateFile( - self.artifact.getOutputSource().getPath(src_builder), - full_dest_path, - ); - if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) { + var all_cached = true; + + { + const full_src_path = self.artifact.getOutputSource().getPath(src_builder); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_dest_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; + } + + if (self.artifact.isDynamicLibrary() and + self.artifact.version != null and + self.artifact.target.wantSharedLibSymLinks()) + { try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); } - if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) { + if (self.artifact.isDynamicLibrary() and + self.artifact.target.isWindows() and + self.artifact.emit_implib != .no_emit) + { + const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder); const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); - try src_builder.updateFile(self.artifact.getOutputLibSource().getPath(src_builder), full_implib_path); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_implib_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; } if (self.pdb_dir) |pdb_dir| { + const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder); const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); - try src_builder.updateFile(self.artifact.getOutputPdbSource().getPath(src_builder), full_pdb_path); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_pdb_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; } if (self.h_dir) |h_dir| { + const full_src_path = self.artifact.getOutputHSource().getPath(src_builder); const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename); - try src_builder.updateFile(self.artifact.getOutputHSource().getPath(src_builder), full_h_path); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_h_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; } self.artifact.installed_path = full_dest_path; + step.result_cached = all_cached; } diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig index 2807d641f3..d9ea248913 100644 --- a/lib/std/Build/InstallDirStep.zig +++ b/lib/std/Build/InstallDirStep.zig @@ -89,13 +89,17 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - const prev_status = try fs.Dir.updateFile( + const prev_status = fs.Dir.updateFile( src_builder.build_root.handle, src_sub_path, cwd, dest_path, .{}, - ); + ) catch |err| { + return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{ + src_builder.build_root, src_sub_path, dest_path, @errorName(err), + }); + }; all_cached = all_cached and prev_status == .fresh; }, else => continue, diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig index ed7576f42c..a77fa10b43 100644 --- a/lib/std/Build/InstallFileStep.zig +++ b/lib/std/Build/InstallFileStep.zig @@ -42,5 +42,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const dest_builder = self.dest_builder; const full_src_path = self.source.getPath2(src_builder, step); const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path); - try dest_builder.updateFile(full_src_path, full_dest_path); + const cwd = std.fs.cwd(); + const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_dest_path, @errorName(err), + }); + }; + step.result_cached = prev == .fresh; } From d3cbbe0b1e38394de5c4ea6efe9372203092002f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Mar 2023 16:16:32 -0700 Subject: [PATCH 202/294] std.Build.Step: no-op steps report cached if all deps cached --- lib/std/Build/Step.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 1a7fe24e7c..d146ddb259 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -144,9 +144,16 @@ pub fn getStackTrace(s: *Step) std.builtin.StackTrace { }; } -fn makeNoOp(self: *Step, prog_node: *std.Progress.Node) anyerror!void { - _ = self; +fn makeNoOp(step: *Step, prog_node: *std.Progress.Node) anyerror!void { _ = prog_node; + + var all_cached = true; + + for (step.dependencies.items) |dep| { + all_cached = all_cached and dep.result_cached; + } + + step.result_cached = all_cached; } pub fn cast(step: *Step, comptime T: type) ?*T { From 6377aad23a51ba6d69a01fafa60001e2238cde0f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Mar 2023 17:48:01 -0700 Subject: [PATCH 203/294] build runner: fix typo in max rss display --- lib/build_runner.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index c99d55b783..7a19a6962b 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -527,10 +527,8 @@ fn printTreeStep( if (s.result_peak_rss != 0) { const rss = s.result_peak_rss; try ttyconf.setColor(stderr, .Dim); - if (rss >= 1000_000_000_000) { - try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000_000}); - } else if (rss >= 1000_000_000) { - try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000_000}); + if (rss >= 1000_000_000) { + try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000}); } else if (rss >= 1000_000) { try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000}); } else if (rss >= 1000) { From 3b29d00c9826d8053b63fe2bcd86c6d1517fcc51 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 00:19:32 -0700 Subject: [PATCH 204/294] add std.process.totalSystemMemory --- lib/std/os/windows/kernel32.zig | 3 +++ lib/std/process.zig | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index d3bfeaaf2c..1fd8a406d5 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -67,6 +67,7 @@ const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION; const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS; const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE; const MODULEENTRY32 = windows.MODULEENTRY32; +const ULONGLONG = windows.ULONGLONG; pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque; pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong; @@ -457,3 +458,5 @@ pub extern "kernel32" fn RegOpenKeyExW( samDesired: REGSAM, phkResult: *HKEY, ) callconv(WINAPI) LSTATUS; + +pub extern "kernel32" fn GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes: *ULONGLONG) BOOL; diff --git a/lib/std/process.zig b/lib/std/process.zig index eff29e86fa..8652dd1bbc 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1169,3 +1169,38 @@ pub fn execve( return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); } + +pub const TotalSystemMemoryError = error{ + UnknownTotalSystemMemory, +}; + +/// Returns the total system memory, in bytes. +pub fn totalSystemMemory() TotalSystemMemoryError!usize { + switch (builtin.os.tag) { + .linux => { + return totalSystemMemoryLinux() catch return error.UnknownTotalSystemMemory; + }, + .windows => { + var kilobytes: std.os.windows.ULONGLONG = undefined; + assert(std.os.windows.kernel32.GetPhysicallyInstalledSystemMemory(&kilobytes) == std.os.windows.TRUE); + return kilobytes * 1024; + }, + else => return error.UnknownTotalSystemMemory, + } +} + +fn totalSystemMemoryLinux() !usize { + var file = try std.fs.openFileAbsoluteZ("/proc/meminfo", .{}); + defer file.close(); + var buf: [50]u8 = undefined; + const amt = try file.read(&buf); + if (amt != 50) return error.Unexpected; + var it = std.mem.tokenize(u8, buf[0..amt], " \n"); + const label = it.next().?; + if (!std.mem.eql(u8, label, "MemTotal:")) return error.Unexpected; + const int_text = it.next() orelse return error.Unexpected; + const units = it.next() orelse return error.Unexpected; + if (!std.mem.eql(u8, units, "kB")) return error.Unexpected; + const kilobytes = try std.fmt.parseInt(usize, int_text, 10); + return kilobytes * 1024; +} From f51413d2cf0bd87079dace7f6481d2a361a19ea6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 00:20:11 -0700 Subject: [PATCH 205/294] zig build: add an OOM-prevention system The problem is that one may execute too many subprocesses concurrently that, together, exceed an RSS value that causes the OOM killer to kill something problematic such as the window manager. Or worse, nothing, and the system freezes. This is a real world problem. For example when building LLVM a simple `ninja install` will bring your system to its knees if you don't know that you should add `-DLLVM_PARALLEL_LINK_JOBS=1`. In particular: compiling the zig std lib tests takes about 2G each, which at 16x at once (8 cores + hyperthreading) is using all 32GB of my RAM, causing the OOM killer to kill my window manager The idea here is that you can annotate steps that might use a high amount of system resources with an upper bound. So for example I could mark the std lib tests as having an upper bound peak RSS of 3 GiB. Then the build system will do 2 things: 1. ulimit the child process, so that it will fail if it would exceed that memory limit. 2. Notice how much system RAM is available and avoid running too many concurrent jobs at once that would total more than that. This implements (1) not with an operating system enforced limit, but by checking the maxrss after a child process exits. However it does implement (2) correctly. The available memory used by the build system defaults to the total system memory, regardless of whether it is used by other processes at the time of spawning the build runner. This value can be overridden with the new --maxrss flag to `zig build`. This mechanism will ensure that the sum total of upper bound RSS memory of concurrent tasks will not exceed this value. This system makes it so that project maintainers can annotate problematic subprocesses, avoiding bug reports from users, who can blissfully execute `zig build` without worrying about the project's internals. Nobody's computer crashes, and the build system uses as much parallelism as possible without risking OOM. Users do not need to unnecessarily resort to -j1 when the build system can figure this out for them. --- lib/build_runner.zig | 189 +++++++++++++++++++++++++++------- lib/std/Build.zig | 12 +++ lib/std/Build/CompileStep.zig | 2 + lib/std/Build/Step.zig | 45 ++++++-- 4 files changed, 207 insertions(+), 41 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 7a19a6962b..c30fbaea87 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -84,20 +84,21 @@ pub fn main() !void { ); defer builder.destroy(); + const Color = enum { auto, off, on }; + var targets = ArrayList([]const u8).init(arena); var debug_log_scopes = ArrayList([]const u8).init(arena); var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena }; - const stderr_stream = io.getStdErr().writer(); - const stdout_stream = io.getStdOut().writer(); - var install_prefix: ?[]const u8 = null; var dir_list = std.Build.DirList{}; var enable_summary: ?bool = null; - - const Color = enum { auto, off, on }; + var max_rss: usize = 0; var color: Color = .auto; + const stderr_stream = io.getStdErr().writer(); + const stdout_stream = io.getStdOut().writer(); + while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-D")) { const option_contents = arg[2..]; @@ -147,6 +148,18 @@ pub fn main() !void { usageAndErr(builder, false, stderr_stream); }; builder.sysroot = sysroot; + } else if (mem.eql(u8, arg, "--maxrss")) { + const max_rss_text = nextArg(args, &arg_idx) orelse { + std.debug.print("Expected argument after --sysroot\n\n", .{}); + usageAndErr(builder, false, stderr_stream); + }; + // TODO: support shorthand such as "2GiB", "2GB", or "2G" + max_rss = std.fmt.parseInt(usize, max_rss_text, 10) catch |err| { + std.debug.print("invalid byte size: '{s}': {s}\n", .{ + max_rss_text, @errorName(err), + }); + process.exit(1); + }; } else if (mem.eql(u8, arg, "--search-prefix")) { const search_prefix = nextArg(args, &arg_idx) orelse { std.debug.print("Expected argument after --search-prefix\n\n", .{}); @@ -280,30 +293,55 @@ pub fn main() !void { if (builder.validateUserInputDidItFail()) usageAndErr(builder, true, stderr_stream); + var run: Run = .{ + .max_rss = max_rss, + .max_rss_is_default = false, + .max_rss_mutex = .{}, + .memory_blocked_steps = std.ArrayList(*Step).init(arena), + + .claimed_rss = 0, + .enable_summary = enable_summary, + .ttyconf = ttyconf, + .stderr = stderr, + }; + + if (run.max_rss == 0) { + run.max_rss = process.totalSystemMemory() catch std.math.maxInt(usize); + run.max_rss_is_default = true; + } + runStepNames( arena, builder, targets.items, main_progress_node, thread_pool_options, - ttyconf, - stderr, - enable_summary, + &run, ) catch |err| switch (err) { error.UncleanExit => process.exit(1), else => return err, }; } +const Run = struct { + max_rss: usize, + max_rss_is_default: bool, + max_rss_mutex: std.Thread.Mutex, + memory_blocked_steps: std.ArrayList(*Step), + + claimed_rss: usize, + enable_summary: ?bool, + ttyconf: std.debug.TTY.Config, + stderr: std.fs.File, +}; + fn runStepNames( arena: std.mem.Allocator, b: *std.Build, step_names: []const []const u8, parent_prog_node: *std.Progress.Node, thread_pool_options: std.Thread.Pool.Options, - ttyconf: std.debug.TTY.Config, - stderr: std.fs.File, - enable_summary: ?bool, + run: *Run, ) !void { const gpa = b.allocator; var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{}; @@ -331,6 +369,26 @@ fn runStepNames( }; } + { + // Check that we have enough memory to complete the build. + var any_problems = false; + for (step_stack.keys()) |s| { + if (s.max_rss == 0) continue; + if (s.max_rss > run.max_rss) { + std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{ + s.owner.dep_prefix, s.name, s.max_rss, run.max_rss, + }); + any_problems = true; + } + } + if (any_problems) { + if (run.max_rss_is_default) { + std.debug.print("note: use --maxrss to override the default", .{}); + } + return error.UncleanExit; + } + } + var thread_pool: std.Thread.Pool = undefined; try thread_pool.init(thread_pool_options); defer thread_pool.deinit(); @@ -353,10 +411,11 @@ fn runStepNames( wait_group.start(); thread_pool.spawn(workerMakeOneStep, .{ - &wait_group, &thread_pool, b, step, &step_prog, ttyconf, + &wait_group, &thread_pool, b, step, &step_prog, run, }) catch @panic("OOM"); } } + assert(run.memory_blocked_steps.items.len == 0); var success_count: usize = 0; var skipped_count: usize = 0; @@ -396,9 +455,12 @@ fn runStepNames( // A proper command line application defaults to silently succeeding. // The user may request verbose mode if they have a different preference. - if (failure_count == 0 and enable_summary != true) return cleanExit(); + if (failure_count == 0 and run.enable_summary != true) return cleanExit(); - if (enable_summary != false) { + const ttyconf = run.ttyconf; + const stderr = run.stderr; + + if (run.enable_summary != false) { const total_count = success_count + failure_count + pending_count + skipped_count; ttyconf.setColor(stderr, .Cyan) catch {}; stderr.writeAll("Build Summary:") catch {}; @@ -407,7 +469,7 @@ fn runStepNames( if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {}; if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {}; - if (enable_summary == null) { + if (run.enable_summary == null) { ttyconf.setColor(stderr, .Dim) catch {}; stderr.writeAll(" (disable with -fno-summary)") catch {}; ttyconf.setColor(stderr, .Reset) catch {}; @@ -623,7 +685,7 @@ fn workerMakeOneStep( b: *std.Build, s: *Step, prog_node: *std.Progress.Node, - ttyconf: std.debug.TTY.Config, + run: *Run, ) void { defer wg.finish(); @@ -646,10 +708,32 @@ fn workerMakeOneStep( } } - // Avoid running steps twice. - if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) { - // Another worker got the job. - return; + if (s.max_rss != 0) { + run.max_rss_mutex.lock(); + defer run.max_rss_mutex.unlock(); + + // Avoid running steps twice. + if (s.state != .precheck_done) { + // Another worker got the job. + return; + } + + const new_claimed_rss = run.claimed_rss + s.max_rss; + if (new_claimed_rss > run.max_rss) { + // Running this step right now could possibly exceed the allotted RSS. + // Add this step to the queue of memory-blocked steps. + run.memory_blocked_steps.append(s) catch @panic("OOM"); + return; + } + + run.claimed_rss = new_claimed_rss; + s.state = .running; + } else { + // Avoid running steps twice. + if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) { + // Another worker got the job. + return; + } } var sub_prog_node = prog_node.start(s.name, 0); @@ -667,7 +751,8 @@ fn workerMakeOneStep( sub_prog_node.context.lock_stderr(); defer sub_prog_node.context.unlock_stderr(); - const stderr = std.io.getStdErr(); + const stderr = run.stderr; + const ttyconf = run.ttyconf; for (s.result_error_msgs.items) |msg| { // Sometimes it feels like you just can't catch a break. Finally, @@ -684,22 +769,55 @@ fn workerMakeOneStep( } } - if (make_result) |_| { - @atomicStore(Step.State, &s.state, .success, .SeqCst); - } else |err| switch (err) { - error.MakeFailed => { - @atomicStore(Step.State, &s.state, .failure, .SeqCst); - return; - }, - error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst), + handle_result: { + if (make_result) |_| { + @atomicStore(Step.State, &s.state, .success, .SeqCst); + } else |err| switch (err) { + error.MakeFailed => { + @atomicStore(Step.State, &s.state, .failure, .SeqCst); + break :handle_result; + }, + error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst), + } + + // Successful completion of a step, so we queue up its dependants as well. + for (s.dependants.items) |dep| { + wg.start(); + thread_pool.spawn(workerMakeOneStep, .{ + wg, thread_pool, b, dep, prog_node, run, + }) catch @panic("OOM"); + } } - // Successful completion of a step, so we queue up its dependants as well. - for (s.dependants.items) |dep| { - wg.start(); - thread_pool.spawn(workerMakeOneStep, .{ - wg, thread_pool, b, dep, prog_node, ttyconf, - }) catch @panic("OOM"); + // If this is a step that claims resources, we must now queue up other + // steps that are waiting for resources. + if (s.max_rss != 0) { + run.max_rss_mutex.lock(); + defer run.max_rss_mutex.unlock(); + + // Give the memory back to the scheduler. + run.claimed_rss -= s.max_rss; + // Avoid kicking off too many tasks that we already know will not have + // enough resources. + var remaining = run.max_rss - run.claimed_rss; + var i: usize = 0; + var j: usize = 0; + while (j < run.memory_blocked_steps.items.len) : (j += 1) { + const dep = run.memory_blocked_steps.items[j]; + assert(dep.max_rss != 0); + if (dep.max_rss <= remaining) { + remaining -= dep.max_rss; + + wg.start(); + thread_pool.spawn(workerMakeOneStep, .{ + wg, thread_pool, b, dep, prog_node, run, + }) catch @panic("OOM"); + } else { + run.memory_blocked_steps.items[i] = dep; + i += 1; + } + } + run.memory_blocked_steps.shrinkRetainingCapacity(i); } } @@ -770,6 +888,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\ --color [auto|off|on] Enable or disable colored error messages \\ --prominent-compile-errors Output compile errors formatted for a human to read \\ -j Limit concurrent jobs (default is to use all CPU cores) + \\ --maxrss Limit memory usage (default is to use available memory) \\ \\Project-Specific Options: \\ diff --git a/lib/std/Build.zig b/lib/std/Build.zig index e3b44e965e..df10f55439 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -453,6 +453,7 @@ pub const ExecutableOptions = struct { target: CrossTarget = .{}, optimize: std.builtin.Mode = .Debug, linkage: ?CompileStep.Linkage = null, + max_rss: usize = 0, }; pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep { @@ -464,6 +465,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep { .optimize = options.optimize, .kind = .exe, .linkage = options.linkage, + .max_rss = options.max_rss, }); } @@ -472,6 +474,7 @@ pub const ObjectOptions = struct { root_source_file: ?FileSource = null, target: CrossTarget, optimize: std.builtin.Mode, + max_rss: usize = 0, }; pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep { @@ -481,6 +484,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep { .target = options.target, .optimize = options.optimize, .kind = .obj, + .max_rss = options.max_rss, }); } @@ -490,6 +494,7 @@ pub const SharedLibraryOptions = struct { version: ?std.builtin.Version = null, target: CrossTarget, optimize: std.builtin.Mode, + max_rss: usize = 0, }; pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep { @@ -501,6 +506,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep { .version = options.version, .target = options.target, .optimize = options.optimize, + .max_rss = options.max_rss, }); } @@ -510,6 +516,7 @@ pub const StaticLibraryOptions = struct { target: CrossTarget, optimize: std.builtin.Mode, version: ?std.builtin.Version = null, + max_rss: usize = 0, }; pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep { @@ -521,6 +528,7 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep { .version = options.version, .target = options.target, .optimize = options.optimize, + .max_rss = options.max_rss, }); } @@ -531,6 +539,7 @@ pub const TestOptions = struct { target: CrossTarget = .{}, optimize: std.builtin.Mode = .Debug, version: ?std.builtin.Version = null, + max_rss: usize = 0, }; pub fn addTest(b: *Build, options: TestOptions) *CompileStep { @@ -540,6 +549,7 @@ pub fn addTest(b: *Build, options: TestOptions) *CompileStep { .root_source_file = options.root_source_file, .target = options.target, .optimize = options.optimize, + .max_rss = options.max_rss, }); } @@ -548,6 +558,7 @@ pub const AssemblyOptions = struct { source_file: FileSource, target: CrossTarget, optimize: std.builtin.Mode, + max_rss: usize = 0, }; pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep { @@ -557,6 +568,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep { .root_source_file = null, .target = options.target, .optimize = options.optimize, + .max_rss = options.max_rss, }); obj_step.addAssemblyFileSource(options.source_file.dupe(b)); return obj_step; diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 895c1a7678..df9abfbc6d 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -274,6 +274,7 @@ pub const Options = struct { kind: Kind, linkage: ?Linkage = null, version: ?std.builtin.Version = null, + max_rss: usize = 0, }; pub const Kind = enum { @@ -333,6 +334,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .name = step_name, .owner = owner, .makeFn = make, + .max_rss = options.max_rss, }), .version = options.version, .out_filename = undefined, diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index d146ddb259..f1edab5881 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -2,14 +2,32 @@ id: Id, name: []const u8, owner: *Build, makeFn: MakeFn, + dependencies: std.ArrayList(*Step), /// This field is empty during execution of the user's build script, and /// then populated during dependency loop checking in the build runner. dependants: std.ArrayListUnmanaged(*Step), state: State, -/// The return addresss associated with creation of this step that can be useful -/// to print along with debugging messages. -debug_stack_trace: [n_debug_stack_frames]usize, +/// Set this field to declare an upper bound on the amount of bytes of memory it will +/// take to run the step. Zero means no limit. +/// +/// The idea to annotate steps that might use a high amount of RAM with an +/// upper bound. For example, perhaps a particular set of unit tests require 4 +/// GiB of RAM, and those tests will be run under 4 different build +/// configurations at once. This would potentially require 16 GiB of memory on +/// the system if all 4 steps executed simultaneously, which could easily be +/// greater than what is actually available, potentially causing the system to +/// crash when using `zig build` at the default concurrency level. +/// +/// This field causes the build runner to do two things: +/// 1. ulimit child processes, so that they will fail if it would exceed this +/// memory limit. This serves to enforce that this upper bound value is +/// correct. +/// 2. Ensure that the set of concurrent steps at any given time have a total +/// max_rss value that does not exceed the `max_total_rss` value of the build +/// runner. This value is configurable on the command line, and defaults to the +/// total system memory available. +max_rss: usize, result_error_msgs: std.ArrayListUnmanaged([]const u8), result_error_bundle: std.zig.ErrorBundle, @@ -18,6 +36,10 @@ result_duration_ns: ?u64, /// 0 means unavailable or not reported. result_peak_rss: usize, +/// The return addresss associated with creation of this step that can be useful +/// to print along with debugging messages. +debug_stack_trace: [n_debug_stack_frames]usize, + pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; const n_debug_stack_frames = 4; @@ -83,6 +105,7 @@ pub const Options = struct { owner: *Build, makeFn: MakeFn = makeNoOp, first_ret_addr: ?usize = null, + max_rss: usize = 0, }; pub fn init(options: Options) Step { @@ -104,6 +127,7 @@ pub fn init(options: Options) Step { .dependencies = std.ArrayList(*Step).init(arena), .dependants = .{}, .state = .precheck_unstarted, + .max_rss = options.max_rss, .debug_stack_trace = addresses, .result_error_msgs = .{}, .result_error_bundle = std.zig.ErrorBundle.empty, @@ -117,15 +141,24 @@ pub fn init(options: Options) Step { /// have already reported the error. Otherwise, we add a simple error report /// here. pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void { - return s.makeFn(s, prog_node) catch |err| switch (err) { + const arena = s.owner.allocator; + + s.makeFn(s, prog_node) catch |err| switch (err) { error.MakeFailed => return error.MakeFailed, error.MakeSkipped => return error.MakeSkipped, else => { - const gpa = s.dependencies.allocator; - s.result_error_msgs.append(gpa, @errorName(err)) catch @panic("OOM"); + s.result_error_msgs.append(arena, @errorName(err)) catch @panic("OOM"); return error.MakeFailed; }, }; + + if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) { + const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{ + s.result_peak_rss, s.max_rss, + }) catch @panic("OOM"); + s.result_error_msgs.append(arena, msg) catch @panic("OOM"); + return error.MakeFailed; + } } pub fn dependOn(self: *Step, other: *Step) void { From 7bad6958657c6b4a791c73a3f827f7f848cab762 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 00:27:46 -0700 Subject: [PATCH 206/294] build.zig: annotate std lib tests maxrss --- build.zig | 102 ++++++++++++++++++++++++------------------------- test/tests.zig | 35 ++++++++++------- 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/build.zig b/build.zig index 40210500d8..a8b06be40c 100644 --- a/build.zig +++ b/build.zig @@ -402,47 +402,45 @@ pub fn build(b: *std.Build) !void { const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting"); do_fmt_step.dependOn(&do_fmt.step); - test_step.dependOn(tests.addPkgTests( - b, - test_filter, - "test/behavior.zig", - "behavior", - "Run the behavior tests", - optimization_modes, - skip_single_threaded, - skip_non_native, - skip_libc, - skip_stage1, - skip_stage2_tests, - )); + test_step.dependOn(tests.addModuleTests(b, .{ + .test_filter = test_filter, + .root_src = "test/behavior.zig", + .name = "behavior", + .desc = "Run the behavior tests", + .optimize_modes = optimization_modes, + .skip_single_threaded = skip_single_threaded, + .skip_non_native = skip_non_native, + .skip_libc = skip_libc, + .skip_stage1 = skip_stage1, + .skip_stage2 = skip_stage2_tests, + .max_rss = 1 * 1024 * 1024 * 1024, + })); - test_step.dependOn(tests.addPkgTests( - b, - test_filter, - "lib/compiler_rt.zig", - "compiler-rt", - "Run the compiler_rt tests", - optimization_modes, - true, // skip_single_threaded - skip_non_native, - true, // skip_libc - skip_stage1, - skip_stage2_tests or true, // TODO get these all passing - )); + test_step.dependOn(tests.addModuleTests(b, .{ + .test_filter = test_filter, + .root_src = "lib/compiler_rt.zig", + .name = "compiler-rt", + .desc = "Run the compiler_rt tests", + .optimize_modes = optimization_modes, + .skip_single_threaded = true, + .skip_non_native = skip_non_native, + .skip_libc = true, + .skip_stage1 = skip_stage1, + .skip_stage2 = true, // TODO get all these passing + })); - test_step.dependOn(tests.addPkgTests( - b, - test_filter, - "lib/c.zig", - "universal-libc", - "Run the universal libc tests", - optimization_modes, - true, // skip_single_threaded - skip_non_native, - true, // skip_libc - skip_stage1, - skip_stage2_tests or true, // TODO get these all passing - )); + test_step.dependOn(tests.addModuleTests(b, .{ + .test_filter = test_filter, + .root_src = "lib/c.zig", + .name = "universal-libc", + .desc = "Run the universal libc tests", + .optimize_modes = optimization_modes, + .skip_single_threaded = true, + .skip_non_native = skip_non_native, + .skip_libc = true, + .skip_stage1 = skip_stage1, + .skip_stage2 = true, // TODO get all these passing + })); test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addStandaloneTests( @@ -472,19 +470,19 @@ pub fn build(b: *std.Build) !void { // tests for this feature are disabled until we have the self-hosted compiler available // test_step.dependOn(tests.addGenHTests(b, test_filter)); - test_step.dependOn(tests.addPkgTests( - b, - test_filter, - "lib/std/std.zig", - "std", - "Run the standard library tests", - optimization_modes, - skip_single_threaded, - skip_non_native, - skip_libc, - skip_stage1, - true, // TODO get these all passing - )); + test_step.dependOn(tests.addModuleTests(b, .{ + .test_filter = test_filter, + .root_src = "lib/std/std.zig", + .name = "std", + .desc = "Run the standard library tests", + .optimize_modes = optimization_modes, + .skip_single_threaded = skip_single_threaded, + .skip_non_native = skip_non_native, + .skip_libc = skip_libc, + .skip_stage1 = skip_stage1, + .skip_stage2 = true, // TODO get all these passing + .max_rss = 3 * 1024 * 1024 * 1024, + })); try addWasiUpdateStep(b, version); } diff --git a/test/tests.zig b/test/tests.zig index 494ae0ea41..2d110b28af 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -639,8 +639,7 @@ pub fn addGenHTests(b: *std.Build, test_filter: ?[]const u8) *Step { return cases.step; } -pub fn addPkgTests( - b: *std.Build, +const ModuleTestOptions = struct { test_filter: ?[]const u8, root_src: []const u8, name: []const u8, @@ -651,14 +650,17 @@ pub fn addPkgTests( skip_libc: bool, skip_stage1: bool, skip_stage2: bool, -) *Step { - const step = b.step(b.fmt("test-{s}", .{name}), desc); + max_rss: usize = 0, +}; + +pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { + const step = b.step(b.fmt("test-{s}", .{options.name}), options.desc); for (test_targets) |test_target| { - if (skip_non_native and !test_target.target.isNative()) + if (options.skip_non_native and !test_target.target.isNative()) continue; - if (skip_libc and test_target.link_libc) + if (options.skip_libc and test_target.link_libc) continue; if (test_target.link_libc and test_target.target.getOs().requiresLibC()) { @@ -666,7 +668,7 @@ pub fn addPkgTests( continue; } - if (skip_single_threaded and test_target.single_threaded) + if (options.skip_single_threaded and test_target.single_threaded) continue; if (test_target.disable_native and @@ -677,12 +679,12 @@ pub fn addPkgTests( } if (test_target.backend) |backend| switch (backend) { - .stage1 => if (skip_stage1) continue, + .stage1 => if (options.skip_stage1) continue, .stage2_llvm => {}, - else => if (skip_stage2) continue, + else => if (options.skip_stage2) continue, }; - const want_this_mode = for (optimize_modes) |m| { + const want_this_mode = for (options.optimize_modes) |m| { if (m == test_target.optimize_mode) break true; } else false; if (!want_this_mode) continue; @@ -696,15 +698,22 @@ pub fn addPkgTests( const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable; + // wasm32-wasi builds need more RAM, idk why + const max_rss = if (test_target.target.getOs().tag == .wasi) + options.max_rss * 2 + else + options.max_rss; + const these_tests = b.addTest(.{ - .root_source_file = .{ .path = root_src }, + .root_source_file = .{ .path = options.root_src }, .optimize = test_target.optimize_mode, .target = test_target.target, + .max_rss = max_rss, }); const single_threaded_txt = if (test_target.single_threaded) "single" else "multi"; const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default"; these_tests.setNamePrefix(b.fmt("{s}-{s}-{s}-{s}-{s}-{s} ", .{ - name, + options.name, triple_prefix, @tagName(test_target.optimize_mode), libc_prefix, @@ -712,7 +721,7 @@ pub fn addPkgTests( backend_txt, })); these_tests.single_threaded = test_target.single_threaded; - these_tests.setFilter(test_filter); + these_tests.setFilter(options.test_filter); if (test_target.link_libc) { these_tests.linkSystemLibrary("c"); } From a24af8e4005d00cf76755635eb6c661d9c260758 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 19:10:37 -0700 Subject: [PATCH 207/294] re-integrate stack trace tests with the new std.Build API * RunStep: ability to set stdin * RunStep: ability to capture stdout and stderr as a FileSource * RunStep: add setName method * RunStep: hash the stdio checks --- build.zig | 44 +- lib/std/Build/RunStep.zig | 246 ++++++++--- lib/std/Build/WriteFileStep.zig | 14 +- test/src/StackTrace.zig | 105 +++++ test/src/Standalone.zig | 141 ++++++ test/src/check-stack-trace.zig | 79 ++++ test/tests.zig | 735 ++++---------------------------- 7 files changed, 637 insertions(+), 727 deletions(-) create mode 100644 test/src/StackTrace.zig create mode 100644 test/src/Standalone.zig create mode 100644 test/src/check-stack-trace.zig diff --git a/build.zig b/build.zig index a8b06be40c..1f1b119ed3 100644 --- a/build.zig +++ b/build.zig @@ -442,33 +442,33 @@ pub fn build(b: *std.Build) !void { .skip_stage2 = true, // TODO get all these passing })); - test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addStandaloneTests( - b, - test_filter, - optimization_modes, - skip_non_native, - enable_macos_sdk, - target, - skip_stage2_tests, - b.enable_darling, - b.enable_qemu, - b.enable_rosetta, - b.enable_wasmtime, - b.enable_wine, - enable_symlinks_windows, - )); - test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); - test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); + _ = enable_symlinks_windows; + _ = enable_macos_sdk; + //test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); + //test_step.dependOn(tests.addStandaloneTests( + // b, + // test_filter, + // optimization_modes, + // skip_non_native, + // enable_macos_sdk, + // target, + // skip_stage2_tests, + // b.enable_darling, + // b.enable_qemu, + // b.enable_rosetta, + // b.enable_wasmtime, + // b.enable_wine, + // enable_symlinks_windows, + //)); + //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); + //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); + //test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); + //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); if (!skip_run_translated_c) { test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target)); } - // tests for this feature are disabled until we have the self-hosted compiler available - // test_step.dependOn(tests.addGenHTests(b, test_filter)); test_step.dependOn(tests.addModuleTests(b, .{ .test_filter = test_filter, diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index d671ff7ae8..95e37f230a 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -38,6 +38,8 @@ env_map: ?*EnvMap, /// be skipped if all output files are up-to-date and input files are /// unchanged. stdio: StdIo = .infer_from_args, +/// This field must be `null` if stdio is `inherit`. +stdin: ?[]const u8 = null, /// Additional file paths relative to build.zig that, when modified, indicate /// that the RunStep should be re-executed. @@ -65,6 +67,9 @@ skip_foreign_checks: bool = false, /// the step fails. max_stdio_size: usize = 10 * 1024 * 1024, +captured_stdout: ?*Output = null, +captured_stderr: ?*Output = null, + pub const StdIo = union(enum) { /// Whether the RunStep has side-effects will be determined by whether or not one /// of the args is an output file (added with `addOutputFileArg`). @@ -99,12 +104,12 @@ pub const Arg = union(enum) { artifact: *CompileStep, file_source: std.Build.FileSource, bytes: []u8, - output: Output, + output: *Output, +}; - pub const Output = struct { - generated_file: *std.Build.GeneratedFile, - basename: []const u8, - }; +pub const Output = struct { + generated_file: std.Build.GeneratedFile, + basename: []const u8, }; pub fn create(owner: *std.Build, name: []const u8) *RunStep { @@ -119,12 +124,15 @@ pub fn create(owner: *std.Build, name: []const u8) *RunStep { .argv = ArrayList(Arg).init(owner.allocator), .cwd = null, .env_map = null, - .rename_step_with_output_arg = true, - .max_stdio_size = 10 * 1024 * 1024, }; return self; } +pub fn setName(self: *RunStep, name: []const u8) void { + self.step.name = name; + self.rename_step_with_output_arg = false; +} + pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM"); self.step.dependOn(&artifact.step); @@ -135,19 +143,19 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { /// throughout the build system. pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { const b = rs.step.owner; - const generated_file = b.allocator.create(std.Build.GeneratedFile) catch @panic("OOM"); - generated_file.* = .{ .step = &rs.step }; - rs.argv.append(.{ .output = .{ - .generated_file = generated_file, - .basename = b.dupe(basename), - } }) catch @panic("OOM"); + + const output = b.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .basename = basename, + .generated_file = .{ .step = &rs.step }, + }; + rs.argv.append(.{ .output = output }) catch @panic("OOM"); if (rs.rename_step_with_output_arg) { - rs.rename_step_with_output_arg = false; - rs.step.name = b.fmt("{s} ({s})", .{ rs.step.name, basename }); + rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename })); } - return .{ .generated = generated_file }; + return .{ .generated = &output.generated_file }; } pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { @@ -259,6 +267,34 @@ pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { } } +pub fn captureStdErr(self: *RunStep) std.Build.FileSource { + assert(self.stdio != .inherit); + + if (self.captured_stderr) |output| return .{ .generated = &output.generated_file }; + + const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .basename = "stderr", + .generated_file = .{ .step = &self.step }, + }; + self.captured_stderr = output; + return .{ .generated = &output.generated_file }; +} + +pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile { + assert(self.stdio != .inherit); + + if (self.captured_stdout) |output| return .{ .generated = &output.generated_file }; + + const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .basename = "stdout", + .generated_file = .{ .step = &self.step }, + }; + self.captured_stdout = output; + return .{ .generated = &output.generated_file }; +} + /// Returns whether the RunStep has side effects *other than* updating the output arguments. fn hasSideEffects(self: RunStep) bool { return switch (self.stdio) { @@ -269,6 +305,8 @@ fn hasSideEffects(self: RunStep) bool { } fn hasAnyOutputArgs(self: RunStep) bool { + if (self.captured_stdout != null) return true; + if (self.captured_stderr != null) return true; for (self.argv.items) |arg| switch (arg) { .output => return true, else => continue, @@ -318,7 +356,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { var argv_list = ArrayList([]const u8).init(arena); var output_placeholders = ArrayList(struct { index: usize, - output: Arg.Output, + output: *Output, }).init(arena); var man = b.cache.obtain(); @@ -361,46 +399,68 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - if (!has_side_effects) { - for (self.extra_file_dependencies) |file_path| { - _ = try man.addFile(b.pathFromRoot(file_path), null); - } + if (self.captured_stdout) |output| { + man.hash.addBytes(output.basename); + } - if (try step.cacheHit(&man)) { - // cache hit, skip running command - const digest = man.final(); - for (output_placeholders.items) |placeholder| { - placeholder.output.generated_file.path = try b.cache_root.join( - arena, - &.{ "o", &digest, placeholder.output.basename }, - ); - } - step.result_cached = true; - return; - } + if (self.captured_stderr) |output| { + man.hash.addBytes(output.basename); + } + hashStdIo(&man.hash, self.stdio); + + if (has_side_effects) { + try runCommand(self, argv_list.items, has_side_effects, null); + return; + } + + for (self.extra_file_dependencies) |file_path| { + _ = try man.addFile(b.pathFromRoot(file_path), null); + } + + if (try step.cacheHit(&man)) { + // cache hit, skip running command const digest = man.final(); - for (output_placeholders.items) |placeholder| { - const output_components = .{ "o", &digest, placeholder.output.basename }; - const output_sub_path = try fs.path.join(arena, &output_components); - const output_sub_dir_path = fs.path.dirname(output_sub_path).?; - b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, output_sub_dir_path, @errorName(err), - }); - }; - const output_path = try b.cache_root.join(arena, &output_components); - placeholder.output.generated_file.path = output_path; - argv_list.items[placeholder.index] = output_path; + placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, placeholder.output.basename, + }); } + + if (self.captured_stdout) |output| { + output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, output.basename, + }); + } + + if (self.captured_stderr) |output| { + output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, output.basename, + }); + } + + step.result_cached = true; + return; } - try runCommand(self, argv_list.items, has_side_effects); + const digest = man.final(); - if (!has_side_effects) { - try man.writeManifest(); + for (output_placeholders.items) |placeholder| { + const output_components = .{ "o", &digest, placeholder.output.basename }; + const output_sub_path = try fs.path.join(arena, &output_components); + const output_sub_dir_path = fs.path.dirname(output_sub_path).?; + b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, output_sub_dir_path, @errorName(err), + }); + }; + const output_path = try b.cache_root.join(arena, &output_components); + placeholder.output.generated_file.path = output_path; + argv_list.items[placeholder.index] = output_path; } + + try runCommand(self, argv_list.items, has_side_effects, &digest); + try man.writeManifest(); } fn formatTerm( @@ -448,7 +508,12 @@ fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term }; } -fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) !void { +fn runCommand( + self: *RunStep, + argv: []const []const u8, + has_side_effects: bool, + digest: ?*const [std.Build.Cache.hex_digest_len]u8, +) !void { const step = &self.step; const b = step.owner; const arena = b.allocator; @@ -584,6 +649,46 @@ fn runCommand(self: *RunStep, argv: []const []const u8, has_side_effects: bool) step.result_duration_ns = result.elapsed_ns; step.result_peak_rss = result.peak_rss; + // Capture stdout and stderr to GeneratedFile objects. + const Stream = struct { + captured: ?*Output, + is_null: bool, + bytes: []const u8, + }; + for ([_]Stream{ + .{ + .captured = self.captured_stdout, + .is_null = result.stdout_null, + .bytes = result.stdout, + }, + .{ + .captured = self.captured_stderr, + .is_null = result.stderr_null, + .bytes = result.stderr, + }, + }) |stream| { + if (stream.captured) |output| { + assert(!stream.is_null); + + const output_components = .{ "o", digest.?, output.basename }; + const output_path = try b.cache_root.join(arena, &output_components); + output.generated_file.path = output_path; + + const sub_path = try fs.path.join(arena, &output_components); + const sub_path_dirname = fs.path.dirname(sub_path).?; + b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, sub_path_dirname, @errorName(err), + }); + }; + b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.cache_root, sub_path, @errorName(err), + }); + }; + } + } + switch (self.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { @@ -705,7 +810,7 @@ fn spawnChildAndCollect( child.request_resource_usage_statistics = true; child.stdin_behavior = switch (self.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .infer_from_args => if (has_side_effects) .Inherit else .Close, .inherit => .Inherit, .check => .Close, }; @@ -719,12 +824,26 @@ fn spawnChildAndCollect( .inherit => .Inherit, .check => .Pipe, }; + if (self.captured_stdout != null) child.stdout_behavior = .Pipe; + if (self.captured_stderr != null) child.stderr_behavior = .Pipe; + if (self.stdin != null) { + assert(child.stdin_behavior != .Inherit); + child.stdin_behavior = .Pipe; + } child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err), }); var timer = try std.time.Timer.start(); + if (self.stdin) |stdin| { + child.stdin.?.writeAll(stdin) catch |err| { + return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); + }; + child.stdin.?.close(); + child.stdin = null; + } + // These are not optionals, as a workaround for // https://github.com/ziglang/zig/issues/14783 var stdout_bytes: []const u8 = undefined; @@ -761,7 +880,8 @@ fn spawnChildAndCollect( } if (!stderr_null and stderr_bytes.len > 0) { - const stderr_is_diagnostic = switch (self.stdio) { + // Treat stderr as an error message. + const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) { .check => |checks| !checksContainStderr(checks.items), else => true, }; @@ -829,3 +949,27 @@ fn failForeign( }, } } + +fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { + switch (stdio) { + .infer_from_args, .inherit => {}, + .check => |checks| for (checks.items) |check| { + hh.add(@as(std.meta.Tag(StdIo.Check), check)); + switch (check) { + .expect_stderr_exact, + .expect_stderr_match, + .expect_stdout_exact, + .expect_stdout_match, + => |s| hh.addBytes(s), + + .expect_term => |term| { + hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); + switch (term) { + .Exited => |x| hh.add(x), + .Signal, .Stopped, .Unknown => |x| hh.add(x), + } + }, + } + }, + } +} diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 1109cf5426..64025ce0fe 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -189,10 +189,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { if (try step.cacheHit(&man)) { const digest = man.final(); for (wf.files.items) |file| { - file.generated_file.path = try b.cache_root.join( - b.allocator, - &.{ "o", &digest, file.sub_path }, - ); + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + "o", &digest, file.sub_path, + }); } return; } @@ -249,10 +248,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }, } - file.generated_file.path = try b.cache_root.join( - b.allocator, - &.{ cache_path, file.sub_path }, - ); + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + cache_path, file.sub_path, + }); } try man.writeManifest(); diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig new file mode 100644 index 0000000000..5709b9d29e --- /dev/null +++ b/test/src/StackTrace.zig @@ -0,0 +1,105 @@ +b: *std.Build, +step: *Step, +test_index: usize, +test_filter: ?[]const u8, +optimize_modes: []const OptimizeMode, +check_exe: *std.Build.CompileStep, + +const Expect = [@typeInfo(OptimizeMode).Enum.fields.len][]const u8; + +pub fn addCase(self: *StackTrace, config: anytype) void { + if (@hasField(@TypeOf(config), "exclude")) { + if (config.exclude.exclude()) return; + } + if (@hasField(@TypeOf(config), "exclude_arch")) { + const exclude_arch: []const std.Target.Cpu.Arch = &config.exclude_arch; + for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; + } + if (@hasField(@TypeOf(config), "exclude_os")) { + const exclude_os: []const std.Target.Os.Tag = &config.exclude_os; + for (exclude_os) |os| if (os == builtin.os.tag) return; + } + for (self.optimize_modes) |optimize_mode| { + switch (optimize_mode) { + .Debug => { + if (@hasField(@TypeOf(config), "Debug")) { + self.addExpect(config.name, config.source, optimize_mode, config.Debug); + } + }, + .ReleaseSafe => { + if (@hasField(@TypeOf(config), "ReleaseSafe")) { + self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSafe); + } + }, + .ReleaseFast => { + if (@hasField(@TypeOf(config), "ReleaseFast")) { + self.addExpect(config.name, config.source, optimize_mode, config.ReleaseFast); + } + }, + .ReleaseSmall => { + if (@hasField(@TypeOf(config), "ReleaseSmall")) { + self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSmall); + } + }, + } + } +} + +fn addExpect( + self: *StackTrace, + name: []const u8, + source: []const u8, + optimize_mode: OptimizeMode, + mode_config: anytype, +) void { + if (@hasField(@TypeOf(mode_config), "exclude")) { + if (mode_config.exclude.exclude()) return; + } + if (@hasField(@TypeOf(mode_config), "exclude_arch")) { + const exclude_arch: []const std.Target.Cpu.Arch = &mode_config.exclude_arch; + for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; + } + if (@hasField(@TypeOf(mode_config), "exclude_os")) { + const exclude_os: []const std.Target.Os.Tag = &mode_config.exclude_os; + for (exclude_os) |os| if (os == builtin.os.tag) return; + } + + const b = self.b; + const annotated_case_name = fmt.allocPrint(b.allocator, "check {s} ({s})", .{ + name, @tagName(optimize_mode), + }) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + const src_basename = "source.zig"; + const write_src = b.addWriteFile(src_basename, source); + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = write_src.getFileSource(src_basename).?, + .optimize = optimize_mode, + .target = .{}, + }); + + const run = b.addRunArtifact(exe); + run.expectExitCode(1); + run.expectStdOutEqual(""); + + const check_run = b.addRunArtifact(self.check_exe); + check_run.setName(annotated_case_name); + check_run.addFileSourceArg(run.captureStdErr()); + check_run.addArgs(&.{ + @tagName(optimize_mode), + }); + check_run.expectStdOutEqual(mode_config.expect); + + self.step.dependOn(&check_run.step); +} + +const StackTrace = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const Step = std.Build.Step; +const OptimizeMode = std.builtin.OptimizeMode; +const fmt = std.fmt; +const mem = std.mem; diff --git a/test/src/Standalone.zig b/test/src/Standalone.zig new file mode 100644 index 0000000000..c07bb511c5 --- /dev/null +++ b/test/src/Standalone.zig @@ -0,0 +1,141 @@ +b: *std.Build, +step: *Step, +test_index: usize, +test_filter: ?[]const u8, +optimize_modes: []const OptimizeMode, +skip_non_native: bool, +enable_macos_sdk: bool, +target: std.zig.CrossTarget, +omit_stage2: bool, +enable_darling: bool = false, +enable_qemu: bool = false, +enable_rosetta: bool = false, +enable_wasmtime: bool = false, +enable_wine: bool = false, +enable_symlinks_windows: bool, + +pub fn addC(self: *Standalone, root_src: []const u8) void { + self.addAllArgs(root_src, true); +} + +pub fn add(self: *Standalone, root_src: []const u8) void { + self.addAllArgs(root_src, false); +} + +pub fn addBuildFile(self: *Standalone, build_file: []const u8, features: struct { + build_modes: bool = false, + cross_targets: bool = false, + requires_macos_sdk: bool = false, + requires_stage2: bool = false, + use_emulation: bool = false, + requires_symlinks: bool = false, + extra_argv: []const []const u8 = &.{}, +}) void { + const b = self.b; + + if (features.requires_macos_sdk and !self.enable_macos_sdk) return; + if (features.requires_stage2 and self.omit_stage2) return; + if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return; + + const annotated_case_name = b.fmt("build {s}", .{build_file}); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + var zig_args = ArrayList([]const u8).init(b.allocator); + const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable; + zig_args.append(rel_zig_exe) catch unreachable; + zig_args.append("build") catch unreachable; + + // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, + // and then remove this! + zig_args.append("-j1") catch @panic("OOM"); + + zig_args.append("--build-file") catch unreachable; + zig_args.append(b.pathFromRoot(build_file)) catch unreachable; + + zig_args.appendSlice(features.extra_argv) catch unreachable; + + zig_args.append("test") catch unreachable; + + if (b.verbose) { + zig_args.append("--verbose") catch unreachable; + } + + if (features.cross_targets and !self.target.isNative()) { + const target_triple = self.target.zigTriple(b.allocator) catch unreachable; + const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; + zig_args.append(target_arg) catch unreachable; + } + + if (features.use_emulation) { + if (self.enable_darling) { + zig_args.append("-fdarling") catch unreachable; + } + if (self.enable_qemu) { + zig_args.append("-fqemu") catch unreachable; + } + if (self.enable_rosetta) { + zig_args.append("-frosetta") catch unreachable; + } + if (self.enable_wasmtime) { + zig_args.append("-fwasmtime") catch unreachable; + } + if (self.enable_wine) { + zig_args.append("-fwine") catch unreachable; + } + } + + const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug}; + for (optimize_modes) |optimize_mode| { + const arg = switch (optimize_mode) { + .Debug => "", + .ReleaseFast => "-Doptimize=ReleaseFast", + .ReleaseSafe => "-Doptimize=ReleaseSafe", + .ReleaseSmall => "-Doptimize=ReleaseSmall", + }; + const zig_args_base_len = zig_args.items.len; + if (arg.len > 0) + zig_args.append(arg) catch unreachable; + defer zig_args.resize(zig_args_base_len) catch unreachable; + + const run_cmd = b.addSystemCommand(zig_args.items); + self.step.dependOn(&run_cmd.step); + } +} + +pub fn addAllArgs(self: *Standalone, root_src: []const u8, link_libc: bool) void { + const b = self.b; + + for (self.optimize_modes) |optimize| { + const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{ + root_src, + @tagName(optimize), + }) catch unreachable; + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; + } + + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = root_src }, + .optimize = optimize, + .target = .{}, + }); + if (link_libc) { + exe.linkSystemLibrary("c"); + } + + self.step.dependOn(&exe.step); + } +} + +const Standalone = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const Step = std.Build.Step; +const OptimizeMode = std.builtin.OptimizeMode; +const fmt = std.fmt; +const mem = std.mem; +const ArrayList = std.ArrayList; +const fs = std.fs; diff --git a/test/src/check-stack-trace.zig b/test/src/check-stack-trace.zig new file mode 100644 index 0000000000..bb1db55076 --- /dev/null +++ b/test/src/check-stack-trace.zig @@ -0,0 +1,79 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const mem = std.mem; +const fs = std.fs; + +pub fn main() !void { + var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const args = try std.process.argsAlloc(arena); + + const input_path = args[1]; + const optimize_mode_text = args[2]; + + const input_bytes = try std.fs.cwd().readFileAlloc(arena, input_path, 5 * 1024 * 1024); + const optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, optimize_mode_text).?; + + var stderr = input_bytes; + + // process result + // - keep only basename of source file path + // - replace address with symbolic string + // - replace function name with symbolic string when optimize_mode != .Debug + // - skip empty lines + const got: []const u8 = got_result: { + var buf = std.ArrayList(u8).init(arena); + defer buf.deinit(); + if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; + var it = mem.split(u8, stderr, "\n"); + process_lines: while (it.next()) |line| { + if (line.len == 0) continue; + + // offset search past `[drive]:` on windows + var pos: usize = if (builtin.os.tag == .windows) 2 else 0; + // locate delims/anchor + const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" }; + var marks = [_]usize{0} ** delims.len; + for (delims, 0..) |delim, i| { + marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { + // unexpected pattern: emit raw line and cont + try buf.appendSlice(line); + try buf.appendSlice("\n"); + continue :process_lines; + }; + pos = marks[i] + delim.len; + } + // locate source basename + pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse { + // unexpected pattern: emit raw line and cont + try buf.appendSlice(line); + try buf.appendSlice("\n"); + continue :process_lines; + }; + // end processing if source basename changes + if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break; + // emit substituted line + try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]); + try buf.appendSlice(" [address]"); + if (optimize_mode == .Debug) { + // On certain platforms (windows) or possibly depending on how we choose to link main + // the object file extension may be present so we simply strip any extension. + if (mem.indexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| { + try buf.appendSlice(line[marks[3] .. marks[4] + idot]); + try buf.appendSlice(line[marks[5]..]); + } else { + try buf.appendSlice(line[marks[3]..]); + } + } else { + try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]); + try buf.appendSlice("[function]"); + } + try buf.appendSlice("\n"); + } + break :got_result try buf.toOwnedSlice(); + }; + + try std.io.getStdOut().writeAll(got); +} diff --git a/test/tests.zig b/test/tests.zig index 2d110b28af..1a80d7de8d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -20,13 +20,14 @@ const stack_traces = @import("stack_traces.zig"); const assemble_and_link = @import("assemble_and_link.zig"); const translate_c = @import("translate_c.zig"); const run_translated_c = @import("run_translated_c.zig"); -const gen_h = @import("gen_h.zig"); const link = @import("link.zig"); // Implementations pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext; +pub const StackTracesContext = @import("src/StackTrace.zig"); +pub const StandaloneContext = @import("src/Standalone.zig"); const TestTarget = struct { target: CrossTarget = @as(CrossTarget, .{}), @@ -460,10 +461,71 @@ const test_targets = blk: { }; }; -const max_stdout_size = 1 * 1024 * 1024; // 1 MB +const c_abi_targets = [_]CrossTarget{ + .{}, + .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .x86, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .arm, + .os_tag = .linux, + .abi = .musleabihf, + }, + .{ + .cpu_arch = .mips, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .riscv64, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + .abi = .musl, + }, + .{ + .cpu_arch = .powerpc, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .powerpc64le, + .os_tag = .linux, + .abi = .musl, + }, + .{ + .cpu_arch = .x86, + .os_tag = .windows, + .abi = .gnu, + }, + .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, + }, +}; -pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - const cases = b.allocator.create(CompareOutputContext) catch unreachable; +pub fn addCompareOutputTests( + b: *std.Build, + test_filter: ?[]const u8, + optimize_modes: []const OptimizeMode, +) *Step { + const cases = b.allocator.create(CompareOutputContext) catch @panic("OOM"); cases.* = CompareOutputContext{ .b = b, .step = b.step("test-compare-output", "Run the compare output tests"), @@ -477,14 +539,26 @@ pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_m return cases.step; } -pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - const cases = b.allocator.create(StackTracesContext) catch unreachable; - cases.* = StackTracesContext{ +pub fn addStackTraceTests( + b: *std.Build, + test_filter: ?[]const u8, + optimize_modes: []const OptimizeMode, +) *Step { + const check_exe = b.addExecutable(.{ + .name = "check-stack-trace", + .root_source_file = .{ .path = "test/src/check-stack-trace.zig" }, + .target = .{}, + .optimize = .Debug, + }); + + const cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); + cases.* = .{ .b = b, .step = b.step("test-stack-traces", "Run the stack trace tests"), .test_index = 0, .test_filter = test_filter, .optimize_modes = optimize_modes, + .check_exe = check_exe, }; stack_traces.addCases(cases); @@ -507,7 +581,7 @@ pub fn addStandaloneTests( enable_wine: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch unreachable; + const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); cases.* = StandaloneContext{ .b = b, .step = b.step("test-standalone", "Run the standalone tests"), @@ -539,7 +613,7 @@ pub fn addLinkTests( omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch unreachable; + const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); cases.* = StandaloneContext{ .b = b, .step = b.step("test-link", "Run the linker tests"), @@ -569,7 +643,7 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co }); const run_cmd = exe.run(); run_cmd.addArgs(&[_][]const u8{ - fs.realpathAlloc(b.allocator, b.zig_exe) catch unreachable, + fs.realpathAlloc(b.allocator, b.zig_exe) catch @panic("OOM"), b.pathFromRoot(b.cache_root.path orelse "."), }); @@ -578,7 +652,7 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co } pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - const cases = b.allocator.create(CompareOutputContext) catch unreachable; + const cases = b.allocator.create(CompareOutputContext) catch @panic("OOM"); cases.* = CompareOutputContext{ .b = b, .step = b.step("test-asm-link", "Run the assemble and link tests"), @@ -593,7 +667,7 @@ pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize } pub fn addTranslateCTests(b: *std.Build, test_filter: ?[]const u8) *Step { - const cases = b.allocator.create(TranslateCContext) catch unreachable; + const cases = b.allocator.create(TranslateCContext) catch @panic("OOM"); cases.* = TranslateCContext{ .b = b, .step = b.step("test-translate-c", "Run the C translation tests"), @@ -611,7 +685,7 @@ pub fn addRunTranslatedCTests( test_filter: ?[]const u8, target: std.zig.CrossTarget, ) *Step { - const cases = b.allocator.create(RunTranslatedCContext) catch unreachable; + const cases = b.allocator.create(RunTranslatedCContext) catch @panic("OOM"); cases.* = .{ .b = b, .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"), @@ -625,20 +699,6 @@ pub fn addRunTranslatedCTests( return cases.step; } -pub fn addGenHTests(b: *std.Build, test_filter: ?[]const u8) *Step { - const cases = b.allocator.create(GenHContext) catch unreachable; - cases.* = GenHContext{ - .b = b, - .step = b.step("test-gen-h", "Run the C header file generation tests"), - .test_index = 0, - .test_filter = test_filter, - }; - - gen_h.addCases(cases); - - return cases.step; -} - const ModuleTestOptions = struct { test_filter: ?[]const u8, root_src: []const u8, @@ -696,7 +756,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { else "bare"; - const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable; + const triple_prefix = test_target.target.zigTriple(b.allocator) catch @panic("OOM"); // wasm32-wasi builds need more RAM, idk why const max_rss = if (test_target.target.getOs().tag == .wasi) @@ -750,623 +810,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { return step; } -pub const StackTracesContext = struct { - b: *std.Build, - step: *Step, - test_index: usize, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, - - const Expect = [@typeInfo(OptimizeMode).Enum.fields.len][]const u8; - - pub fn addCase(self: *StackTracesContext, config: anytype) void { - if (@hasField(@TypeOf(config), "exclude")) { - if (config.exclude.exclude()) return; - } - if (@hasField(@TypeOf(config), "exclude_arch")) { - const exclude_arch: []const std.Target.Cpu.Arch = &config.exclude_arch; - for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; - } - if (@hasField(@TypeOf(config), "exclude_os")) { - const exclude_os: []const std.Target.Os.Tag = &config.exclude_os; - for (exclude_os) |os| if (os == builtin.os.tag) return; - } - for (self.optimize_modes) |optimize_mode| { - switch (optimize_mode) { - .Debug => { - if (@hasField(@TypeOf(config), "Debug")) { - self.addExpect(config.name, config.source, optimize_mode, config.Debug); - } - }, - .ReleaseSafe => { - if (@hasField(@TypeOf(config), "ReleaseSafe")) { - self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSafe); - } - }, - .ReleaseFast => { - if (@hasField(@TypeOf(config), "ReleaseFast")) { - self.addExpect(config.name, config.source, optimize_mode, config.ReleaseFast); - } - }, - .ReleaseSmall => { - if (@hasField(@TypeOf(config), "ReleaseSmall")) { - self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSmall); - } - }, - } - } - } - - fn addExpect( - self: *StackTracesContext, - name: []const u8, - source: []const u8, - optimize_mode: OptimizeMode, - mode_config: anytype, - ) void { - if (@hasField(@TypeOf(mode_config), "exclude")) { - if (mode_config.exclude.exclude()) return; - } - if (@hasField(@TypeOf(mode_config), "exclude_arch")) { - const exclude_arch: []const std.Target.Cpu.Arch = &mode_config.exclude_arch; - for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return; - } - if (@hasField(@TypeOf(mode_config), "exclude_os")) { - const exclude_os: []const std.Target.Os.Tag = &mode_config.exclude_os; - for (exclude_os) |os| if (os == builtin.os.tag) return; - } - - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{ - "stack-trace", - name, - @tagName(optimize_mode), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const b = self.b; - const src_basename = "source.zig"; - const write_src = b.addWriteFile(src_basename, source); - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = write_src.getFileSource(src_basename).?, - .optimize = optimize_mode, - .target = .{}, - }); - - const run_and_compare = RunAndCompareStep.create( - self, - exe, - annotated_case_name, - optimize_mode, - mode_config.expect, - ); - - self.step.dependOn(&run_and_compare.step); - } - - const RunAndCompareStep = struct { - pub const base_id = .custom; - - step: Step, - context: *StackTracesContext, - exe: *CompileStep, - name: []const u8, - optimize_mode: OptimizeMode, - expect_output: []const u8, - test_index: usize, - - pub fn create( - context: *StackTracesContext, - exe: *CompileStep, - name: []const u8, - optimize_mode: OptimizeMode, - expect_output: []const u8, - ) *RunAndCompareStep { - const allocator = context.b.allocator; - const ptr = allocator.create(RunAndCompareStep) catch unreachable; - ptr.* = RunAndCompareStep{ - .step = Step.init(.{ - .id = .custom, - .name = "StackTraceCompareOutputStep", - .makeFn = make, - .owner = context.b, - }), - .context = context, - .exe = exe, - .name = name, - .optimize_mode = optimize_mode, - .expect_output = expect_output, - .test_index = context.test_index, - }; - ptr.step.dependOn(&exe.step); - context.test_index += 1; - return ptr; - } - - fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(RunAndCompareStep, "step", step); - const b = self.context.b; - - const full_exe_path = self.exe.getOutputSource().getPath(b); - var args = ArrayList([]const u8).init(b.allocator); - defer args.deinit(); - args.append(full_exe_path) catch unreachable; - - std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name }); - - if (!std.process.can_spawn) { - const cmd = try std.mem.join(b.allocator, " ", args.items); - std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd }); - b.allocator.free(cmd); - return ExecError.ExecNotSupported; - } - - var child = std.ChildProcess.init(args.items, b.allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - child.env_map = b.env_map; - - if (b.verbose) { - printInvocation(args.items); - } - child.spawn() catch |err| debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) }); - - const stdout = child.stdout.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; - defer b.allocator.free(stdout); - const stderrFull = child.stderr.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; - defer b.allocator.free(stderrFull); - var stderr = stderrFull; - - const term = child.wait() catch |err| { - debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) }); - }; - - switch (term) { - .Exited => |code| { - const expect_code: u32 = 1; - if (code != expect_code) { - std.debug.print("Process {s} exited with error code {d} but expected code {d}\n", .{ - full_exe_path, - code, - expect_code, - }); - printInvocation(args.items); - return error.TestFailed; - } - }, - .Signal => |signum| { - std.debug.print("Process {s} terminated on signal {d}\n", .{ full_exe_path, signum }); - printInvocation(args.items); - return error.TestFailed; - }, - .Stopped => |signum| { - std.debug.print("Process {s} stopped on signal {d}\n", .{ full_exe_path, signum }); - printInvocation(args.items); - return error.TestFailed; - }, - .Unknown => |code| { - std.debug.print("Process {s} terminated unexpectedly with error code {d}\n", .{ full_exe_path, code }); - printInvocation(args.items); - return error.TestFailed; - }, - } - - // process result - // - keep only basename of source file path - // - replace address with symbolic string - // - replace function name with symbolic string when optimize_mode != .Debug - // - skip empty lines - const got: []const u8 = got_result: { - var buf = ArrayList(u8).init(b.allocator); - defer buf.deinit(); - if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; - var it = mem.split(u8, stderr, "\n"); - process_lines: while (it.next()) |line| { - if (line.len == 0) continue; - - // offset search past `[drive]:` on windows - var pos: usize = if (builtin.os.tag == .windows) 2 else 0; - // locate delims/anchor - const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" }; - var marks = [_]usize{0} ** delims.len; - for (delims, 0..) |delim, i| { - marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { - // unexpected pattern: emit raw line and cont - try buf.appendSlice(line); - try buf.appendSlice("\n"); - continue :process_lines; - }; - pos = marks[i] + delim.len; - } - // locate source basename - pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse { - // unexpected pattern: emit raw line and cont - try buf.appendSlice(line); - try buf.appendSlice("\n"); - continue :process_lines; - }; - // end processing if source basename changes - if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break; - // emit substituted line - try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]); - try buf.appendSlice(" [address]"); - if (self.optimize_mode == .Debug) { - // On certain platforms (windows) or possibly depending on how we choose to link main - // the object file extension may be present so we simply strip any extension. - if (mem.indexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| { - try buf.appendSlice(line[marks[3] .. marks[4] + idot]); - try buf.appendSlice(line[marks[5]..]); - } else { - try buf.appendSlice(line[marks[3]..]); - } - } else { - try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]); - try buf.appendSlice("[function]"); - } - try buf.appendSlice("\n"); - } - break :got_result try buf.toOwnedSlice(); - }; - - if (!mem.eql(u8, self.expect_output, got)) { - std.debug.print( - \\ - \\========= Expected this output: ========= - \\{s} - \\================================================ - \\{s} - \\ - , .{ self.expect_output, got }); - return error.TestFailed; - } - std.debug.print("OK\n", .{}); - } - }; -}; - -pub const StandaloneContext = struct { - b: *std.Build, - step: *Step, - test_index: usize, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, - skip_non_native: bool, - enable_macos_sdk: bool, - target: std.zig.CrossTarget, - omit_stage2: bool, - enable_darling: bool = false, - enable_qemu: bool = false, - enable_rosetta: bool = false, - enable_wasmtime: bool = false, - enable_wine: bool = false, - enable_symlinks_windows: bool, - - pub fn addC(self: *StandaloneContext, root_src: []const u8) void { - self.addAllArgs(root_src, true); - } - - pub fn add(self: *StandaloneContext, root_src: []const u8) void { - self.addAllArgs(root_src, false); - } - - pub fn addBuildFile(self: *StandaloneContext, build_file: []const u8, features: struct { - build_modes: bool = false, - cross_targets: bool = false, - requires_macos_sdk: bool = false, - requires_stage2: bool = false, - use_emulation: bool = false, - requires_symlinks: bool = false, - extra_argv: []const []const u8 = &.{}, - }) void { - const b = self.b; - - if (features.requires_macos_sdk and !self.enable_macos_sdk) return; - if (features.requires_stage2 and self.omit_stage2) return; - if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return; - - const annotated_case_name = b.fmt("build {s}", .{build_file}); - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - var zig_args = ArrayList([]const u8).init(b.allocator); - const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable; - zig_args.append(rel_zig_exe) catch unreachable; - zig_args.append("build") catch unreachable; - - // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, - // and then remove this! - zig_args.append("-j1") catch @panic("OOM"); - - zig_args.append("--build-file") catch unreachable; - zig_args.append(b.pathFromRoot(build_file)) catch unreachable; - - zig_args.appendSlice(features.extra_argv) catch unreachable; - - zig_args.append("test") catch unreachable; - - if (b.verbose) { - zig_args.append("--verbose") catch unreachable; - } - - if (features.cross_targets and !self.target.isNative()) { - const target_triple = self.target.zigTriple(b.allocator) catch unreachable; - const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; - zig_args.append(target_arg) catch unreachable; - } - - if (features.use_emulation) { - if (self.enable_darling) { - zig_args.append("-fdarling") catch unreachable; - } - if (self.enable_qemu) { - zig_args.append("-fqemu") catch unreachable; - } - if (self.enable_rosetta) { - zig_args.append("-frosetta") catch unreachable; - } - if (self.enable_wasmtime) { - zig_args.append("-fwasmtime") catch unreachable; - } - if (self.enable_wine) { - zig_args.append("-fwine") catch unreachable; - } - } - - const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug}; - for (optimize_modes) |optimize_mode| { - const arg = switch (optimize_mode) { - .Debug => "", - .ReleaseFast => "-Doptimize=ReleaseFast", - .ReleaseSafe => "-Doptimize=ReleaseSafe", - .ReleaseSmall => "-Doptimize=ReleaseSmall", - }; - const zig_args_base_len = zig_args.items.len; - if (arg.len > 0) - zig_args.append(arg) catch unreachable; - defer zig_args.resize(zig_args_base_len) catch unreachable; - - const run_cmd = b.addSystemCommand(zig_args.items); - self.step.dependOn(&run_cmd.step); - } - } - - pub fn addAllArgs(self: *StandaloneContext, root_src: []const u8, link_libc: bool) void { - const b = self.b; - - for (self.optimize_modes) |optimize| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{ - root_src, - @tagName(optimize), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; - } - - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = root_src }, - .optimize = optimize, - .target = .{}, - }); - if (link_libc) { - exe.linkSystemLibrary("c"); - } - - self.step.dependOn(&exe.step); - } - } -}; - -pub const GenHContext = struct { - b: *std.Build, - step: *Step, - test_index: usize, - test_filter: ?[]const u8, - - const TestCase = struct { - name: []const u8, - sources: ArrayList(SourceFile), - expected_lines: ArrayList([]const u8), - - const SourceFile = struct { - filename: []const u8, - source: []const u8, - }; - - pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { - self.sources.append(SourceFile{ - .filename = filename, - .source = source, - }) catch unreachable; - } - - pub fn addExpectedLine(self: *TestCase, text: []const u8) void { - self.expected_lines.append(text) catch unreachable; - } - }; - - const GenHCmpOutputStep = struct { - step: Step, - context: *GenHContext, - obj: *CompileStep, - name: []const u8, - test_index: usize, - case: *const TestCase, - - pub fn create( - context: *GenHContext, - obj: *CompileStep, - name: []const u8, - case: *const TestCase, - ) *GenHCmpOutputStep { - const allocator = context.b.allocator; - const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; - ptr.* = GenHCmpOutputStep{ - .step = Step.init(.{ - .id = .custom, - .name = "ParseCCmpOutput", - .owner = context.b, - .makeFn = make, - }), - .context = context, - .obj = obj, - .name = name, - .test_index = context.test_index, - .case = case, - }; - ptr.step.dependOn(&obj.step); - context.test_index += 1; - return ptr; - } - - fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); - const b = self.context.b; - - std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name }); - - const full_h_path = self.obj.getOutputHPath(); - const actual_h = try io.readFileAlloc(b.allocator, full_h_path); - - for (self.case.expected_lines.items) |expected_line| { - if (mem.indexOf(u8, actual_h, expected_line) == null) { - std.debug.print( - \\ - \\========= Expected this output: ================ - \\{s} - \\========= But found: =========================== - \\{s} - \\ - , .{ expected_line, actual_h }); - return error.TestFailed; - } - } - std.debug.print("OK\n", .{}); - } - }; - - pub fn create( - self: *GenHContext, - filename: []const u8, - name: []const u8, - source: []const u8, - expected_lines: []const []const u8, - ) *TestCase { - const tc = self.b.allocator.create(TestCase) catch unreachable; - tc.* = TestCase{ - .name = name, - .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), - .expected_lines = ArrayList([]const u8).init(self.b.allocator), - }; - - tc.addSourceFile(filename, source); - var arg_i: usize = 0; - while (arg_i < expected_lines.len) : (arg_i += 1) { - tc.addExpectedLine(expected_lines[arg_i]); - } - return tc; - } - - pub fn add(self: *GenHContext, name: []const u8, source: []const u8, expected_lines: []const []const u8) void { - const tc = self.create("test.zig", name, source, expected_lines); - self.addCase(tc); - } - - pub fn addCase(self: *GenHContext, case: *const TestCase) void { - const b = self.b; - - const optimize_mode = std.builtin.OptimizeMode.Debug; - const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {s} ({s})", .{ case.name, @tagName(optimize_mode) }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const write_src = b.addWriteFiles(); - for (case.sources.items) |src_file| { - write_src.add(src_file.filename, src_file.source); - } - - const obj = b.addObjectFromWriteFileStep("test", write_src, case.sources.items[0].filename); - obj.setBuildMode(optimize_mode); - - const cmp_h = GenHCmpOutputStep.create(self, obj, annotated_case_name, case); - - self.step.dependOn(&cmp_h.step); - } -}; - -fn printInvocation(args: []const []const u8) void { - for (args) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); -} - -const c_abi_targets = [_]CrossTarget{ - .{}, - .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .x86, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .aarch64, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .arm, - .os_tag = .linux, - .abi = .musleabihf, - }, - .{ - .cpu_arch = .mips, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .riscv64, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - .abi = .musl, - }, - .{ - .cpu_arch = .powerpc, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .powerpc64le, - .os_tag = .linux, - .abi = .musl, - }, - .{ - .cpu_arch = .x86, - .os_tag = .windows, - .abi = .gnu, - }, - .{ - .cpu_arch = .x86_64, - .os_tag = .windows, - .abi = .gnu, - }, -}; - pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *Step { const step = b.step("test-c-abi", "Run the C ABI tests"); @@ -1395,7 +838,7 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S test_step.want_lto = false; } - const triple_prefix = c_abi_target.zigTriple(b.allocator) catch unreachable; + const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM"); test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{ "test-c-abi", triple_prefix, From e897637d8d25f5b6b118356d2355da7c9148d8cb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 19:19:48 -0700 Subject: [PATCH 208/294] re-enable compare-output test cases --- build.zig | 2 +- lib/build_runner.zig | 4 +- test/src/CompareOutput.zig | 176 ++++++++++++++++++++++++++++++++++++ test/src/compare_output.zig | 175 ----------------------------------- test/tests.zig | 2 +- 5 files changed, 180 insertions(+), 179 deletions(-) create mode 100644 test/src/CompareOutput.zig delete mode 100644 test/src/compare_output.zig diff --git a/build.zig b/build.zig index 1f1b119ed3..40dc823eb7 100644 --- a/build.zig +++ b/build.zig @@ -444,7 +444,7 @@ pub fn build(b: *std.Build) !void { _ = enable_symlinks_windows; _ = enable_macos_sdk; - //test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); + test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); //test_step.dependOn(tests.addStandaloneTests( // b, // test_filter, diff --git a/lib/build_runner.zig b/lib/build_runner.zig index c30fbaea87..bb04b3e132 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -887,6 +887,8 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\ --verbose Print commands before executing them \\ --color [auto|off|on] Enable or disable colored error messages \\ --prominent-compile-errors Output compile errors formatted for a human to read + \\ -fsummary Print the build summary, even on success + \\ -fno-summary Omit the build summary, even on failure \\ -j Limit concurrent jobs (default is to use all CPU cores) \\ --maxrss Limit memory usage (default is to use available memory) \\ @@ -920,8 +922,6 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi \\Advanced Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error \\ -fno-reference-trace Disable reference trace - \\ -fsummary Print the build summary, even on success - \\ -fno-summary Omit the build summary, even on failure \\ --build-file [file] Override path to build.zig \\ --cache-dir [path] Override path to local Zig cache directory \\ --global-cache-dir [path] Override path to global Zig cache directory diff --git a/test/src/CompareOutput.zig b/test/src/CompareOutput.zig new file mode 100644 index 0000000000..68cf942684 --- /dev/null +++ b/test/src/CompareOutput.zig @@ -0,0 +1,176 @@ +//! This is the implementation of the test harness. +//! For the actual test cases, see test/compare_output.zig. + +b: *std.Build, +step: *std.Build.Step, +test_index: usize, +test_filter: ?[]const u8, +optimize_modes: []const OptimizeMode, + +const Special = enum { + None, + Asm, + RuntimeSafety, +}; + +const TestCase = struct { + name: []const u8, + sources: ArrayList(SourceFile), + expected_output: []const u8, + link_libc: bool, + special: Special, + cli_args: []const []const u8, + + const SourceFile = struct { + filename: []const u8, + source: []const u8, + }; + + pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { + self.sources.append(SourceFile{ + .filename = filename, + .source = source, + }) catch @panic("OOM"); + } + + pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void { + self.cli_args = args; + } +}; + +pub fn createExtra(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase { + var tc = TestCase{ + .name = name, + .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), + .expected_output = expected_output, + .link_libc = false, + .special = special, + .cli_args = &[_][]const u8{}, + }; + const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; + tc.addSourceFile(root_src_name, source); + return tc; +} + +pub fn create(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) TestCase { + return createExtra(self, name, source, expected_output, Special.None); +} + +pub fn addC(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void { + var tc = self.create(name, source, expected_output); + tc.link_libc = true; + self.addCase(tc); +} + +pub fn add(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void { + const tc = self.create(name, source, expected_output); + self.addCase(tc); +} + +pub fn addAsm(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void { + const tc = self.createExtra(name, source, expected_output, Special.Asm); + self.addCase(tc); +} + +pub fn addRuntimeSafety(self: *CompareOutput, name: []const u8, source: []const u8) void { + const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety); + self.addCase(tc); +} + +pub fn addCase(self: *CompareOutput, case: TestCase) void { + const b = self.b; + + const write_src = b.addWriteFiles(); + for (case.sources.items) |src_file| { + write_src.add(src_file.filename, src_file.source); + } + + switch (case.special) { + Special.Asm => { + const annotated_case_name = fmt.allocPrint(self.b.allocator, "run assemble-and-link {s}", .{ + case.name, + }) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + const exe = b.addExecutable(.{ + .name = "test", + .target = .{}, + .optimize = .Debug, + }); + exe.addAssemblyFileSource(write_src.getFileSource(case.sources.items[0].filename).?); + + const run = exe.run(); + run.setName(annotated_case_name); + run.addArgs(case.cli_args); + run.expectStdErrEqual(""); + run.expectStdOutEqual(case.expected_output); + + self.step.dependOn(&run.step); + }, + Special.None => { + for (self.optimize_modes) |optimize| { + const annotated_case_name = fmt.allocPrint(self.b.allocator, "run compare-output {s} ({s})", .{ + case.name, @tagName(optimize), + }) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; + } + + const basename = case.sources.items[0].filename; + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = write_src.getFileSource(basename).?, + .optimize = optimize, + .target = .{}, + }); + if (case.link_libc) { + exe.linkSystemLibrary("c"); + } + + const run = exe.run(); + run.setName(annotated_case_name); + run.addArgs(case.cli_args); + run.expectStdErrEqual(""); + run.expectStdOutEqual(case.expected_output); + + self.step.dependOn(&run.step); + } + }, + Special.RuntimeSafety => { + // TODO iterate over self.optimize_modes and test this in both + // debug and release safe mode + const annotated_case_name = fmt.allocPrint(self.b.allocator, "run safety {s}", .{case.name}) catch @panic("OOM"); + if (self.test_filter) |filter| { + if (mem.indexOf(u8, annotated_case_name, filter) == null) return; + } + + const basename = case.sources.items[0].filename; + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = write_src.getFileSource(basename).?, + .target = .{}, + .optimize = .Debug, + }); + if (case.link_libc) { + exe.linkSystemLibrary("c"); + } + + const run = exe.run(); + run.setName(annotated_case_name); + run.addArgs(case.cli_args); + run.expectExitCode(126); + + self.step.dependOn(&run.step); + }, + } +} + +const CompareOutput = @This(); +const std = @import("std"); +const ArrayList = std.ArrayList; +const fmt = std.fmt; +const mem = std.mem; +const fs = std.fs; +const OptimizeMode = std.builtin.OptimizeMode; diff --git a/test/src/compare_output.zig b/test/src/compare_output.zig deleted file mode 100644 index 20bd62d8e2..0000000000 --- a/test/src/compare_output.zig +++ /dev/null @@ -1,175 +0,0 @@ -// This is the implementation of the test harness. -// For the actual test cases, see test/compare_output.zig. -const std = @import("std"); -const ArrayList = std.ArrayList; -const fmt = std.fmt; -const mem = std.mem; -const fs = std.fs; -const OptimizeMode = std.builtin.OptimizeMode; - -pub const CompareOutputContext = struct { - b: *std.Build, - step: *std.Build.Step, - test_index: usize, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, - - const Special = enum { - None, - Asm, - RuntimeSafety, - }; - - const TestCase = struct { - name: []const u8, - sources: ArrayList(SourceFile), - expected_output: []const u8, - link_libc: bool, - special: Special, - cli_args: []const []const u8, - - const SourceFile = struct { - filename: []const u8, - source: []const u8, - }; - - pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void { - self.sources.append(SourceFile{ - .filename = filename, - .source = source, - }) catch unreachable; - } - - pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void { - self.cli_args = args; - } - }; - - pub fn createExtra(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase { - var tc = TestCase{ - .name = name, - .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), - .expected_output = expected_output, - .link_libc = false, - .special = special, - .cli_args = &[_][]const u8{}, - }; - const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; - tc.addSourceFile(root_src_name, source); - return tc; - } - - pub fn create(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) TestCase { - return createExtra(self, name, source, expected_output, Special.None); - } - - pub fn addC(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { - var tc = self.create(name, source, expected_output); - tc.link_libc = true; - self.addCase(tc); - } - - pub fn add(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { - const tc = self.create(name, source, expected_output); - self.addCase(tc); - } - - pub fn addAsm(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { - const tc = self.createExtra(name, source, expected_output, Special.Asm); - self.addCase(tc); - } - - pub fn addRuntimeSafety(self: *CompareOutputContext, name: []const u8, source: []const u8) void { - const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety); - self.addCase(tc); - } - - pub fn addCase(self: *CompareOutputContext, case: TestCase) void { - const b = self.b; - - const write_src = b.addWriteFiles(); - for (case.sources.items) |src_file| { - write_src.add(src_file.filename, src_file.source); - } - - switch (case.special) { - Special.Asm => { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {s}", .{ - case.name, - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const exe = b.addExecutable(.{ - .name = "test", - .target = .{}, - .optimize = .Debug, - }); - exe.addAssemblyFileSource(write_src.getFileSource(case.sources.items[0].filename).?); - - const run = exe.run(); - run.addArgs(case.cli_args); - run.expectStdErrEqual(""); - run.expectStdOutEqual(case.expected_output); - - self.step.dependOn(&run.step); - }, - Special.None => { - for (self.optimize_modes) |optimize| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{ - "compare-output", - case.name, - @tagName(optimize), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; - } - - const basename = case.sources.items[0].filename; - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = write_src.getFileSource(basename).?, - .optimize = optimize, - .target = .{}, - }); - if (case.link_libc) { - exe.linkSystemLibrary("c"); - } - - const run = exe.run(); - run.addArgs(case.cli_args); - run.expectStdErrEqual(""); - run.expectStdOutEqual(case.expected_output); - - self.step.dependOn(&run.step); - } - }, - Special.RuntimeSafety => { - // TODO iterate over self.optimize_modes and test this in both - // debug and release safe mode - const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {s}", .{case.name}) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - const basename = case.sources.items[0].filename; - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = write_src.getFileSource(basename).?, - .target = .{}, - .optimize = .Debug, - }); - if (case.link_libc) { - exe.linkSystemLibrary("c"); - } - - const run = exe.run(); - run.addArgs(case.cli_args); - run.expectExitCode(126); - - self.step.dependOn(&run.step); - }, - } - } -}; diff --git a/test/tests.zig b/test/tests.zig index 1a80d7de8d..e21e652cba 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -25,7 +25,7 @@ const link = @import("link.zig"); // Implementations pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; -pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext; +pub const CompareOutputContext = @import("src/CompareOutput.zig"); pub const StackTracesContext = @import("src/StackTrace.zig"); pub const StandaloneContext = @import("src/Standalone.zig"); From 0b8736f5ed6e7ae08e8ef84beb03351a95daabdd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 22:44:36 -0700 Subject: [PATCH 209/294] re-enable CLI tests CLI tests are now ported over to the new std.Build API and thus work properly with concurrency. * add `std.Build.addCheckFile` for creating a `std.Build.CheckFileStep`. * add `std.Build.makeTempPath`. This function is intended to be called in the `configure` phase only. It returns an absolute directory path, which is potentially going to be a source of API breakage in the future, so keep that in mind when using this function. * add `std.Build.CheckFileStep.setName`. * `std.Build.CheckFileStep`: better error message when reading the input file fails. * `std.Build.RunStep`: add a `has_side_effects` flag for when you need to override the autodetection. * `std.Build.RunStep`: add the ability to obtain a FileSource for the directory that contains the written files. * `std.Build.WriteFileStep`: add a way to write bytes to an arbitrary path - absolute or relative to the package root. Be careful with this because it updates source files. This should not be used as part of the normal build process, but as a utility occasionally run by a developer with intent to modify source files and then commit those changes to version control. A file added this way is not available with `getFileSource`. --- build.zig | 2 +- lib/std/Build.zig | 44 ++++++- lib/std/Build/CheckFileStep.zig | 20 +++- lib/std/Build/RunStep.zig | 39 ++++++- lib/std/Build/TranslateCStep.zig | 6 +- lib/std/Build/WriteFileStep.zig | 31 ++++- test/cli.zig | 195 ------------------------------- test/tests.zig | 194 ++++++++++++++++++++++++++++-- 8 files changed, 309 insertions(+), 222 deletions(-) delete mode 100644 test/cli.zig diff --git a/build.zig b/build.zig index 40dc823eb7..7c22016bb7 100644 --- a/build.zig +++ b/build.zig @@ -463,7 +463,7 @@ pub fn build(b: *std.Build) !void { //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); - //test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); + test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); if (!skip_run_translated_c) { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index df10f55439..68e80435f8 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -699,10 +699,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ return write_file_step; } -pub fn addWriteFiles(self: *Build) *WriteFileStep { - const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM"); - write_file_step.* = WriteFileStep.init(self); - return write_file_step; +pub fn addWriteFiles(b: *Build) *WriteFileStep { + return WriteFileStep.create(b); } pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep { @@ -1239,6 +1237,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst return install_step; } +pub fn addCheckFile( + b: *Build, + file_source: FileSource, + options: CheckFileStep.Options, +) *CheckFileStep { + return CheckFileStep.create(b, file_source, options); +} + pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void { const file = InstalledFile{ .dir = dir, @@ -1713,6 +1719,36 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 { } } +/// This function is intended to be called in the `configure` phase only. +/// It returns an absolute directory path, which is potentially going to be a +/// source of API breakage in the future, so keep that in mind when using this +/// function. +pub fn makeTempPath(b: *Build) []const u8 { + const rand_int = std.crypto.random.int(u64); + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int); + const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM"); + fs.cwd().makePath(result_path) catch |err| { + std.debug.print("unable to make tmp path '{s}': {s}\n", .{ + result_path, @errorName(err), + }); + }; + return result_path; +} + +/// There are a few copies of this function in miscellaneous places. Would be nice to find +/// a home for them. +fn hex64(x: u64) [16]u8 { + const hex_charset = "0123456789abcdef"; + var result: [16]u8 = undefined; + var i: usize = 0; + while (i < 8) : (i += 1) { + const byte = @truncate(u8, x >> @intCast(u6, 8 * i)); + result[i * 2 + 0] = hex_charset[byte >> 4]; + result[i * 2 + 1] = hex_charset[byte & 15]; + } + return result; +} + test { _ = CheckFileStep; _ = CheckObjectStep; diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index 03b23d0b03..a65810681b 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -12,13 +12,17 @@ expected_matches: []const []const u8, source: std.Build.FileSource, max_bytes: usize = 20 * 1024 * 1024, +pub const Options = struct { + expected_matches: []const []const u8, +}; + pub fn create( owner: *std.Build, source: std.Build.FileSource, - expected_matches: []const []const u8, + options: Options, ) *CheckFileStep { const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); - self.* = CheckFileStep{ + self.* = .{ .step = Step.init(.{ .id = .check_file, .name = "CheckFile", @@ -26,19 +30,27 @@ pub fn create( .makeFn = make, }), .source = source.dupe(owner), - .expected_matches = owner.dupeStrings(expected_matches), + .expected_matches = owner.dupeStrings(options.expected_matches), }; self.source.addStepDependencies(&self.step); return self; } +pub fn setName(self: *CheckFileStep, name: []const u8) void { + self.step.name = name; +} + fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = prog_node; const b = step.owner; const self = @fieldParentPtr(CheckFileStep, "step", step); const src_path = self.source.getPath(b); - const contents = try fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes); + const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| { + return step.fail("unable to read '{s}': {s}", .{ + src_path, @errorName(err), + }); + }; for (self.expected_matches) |expected_match| { if (mem.indexOf(u8, contents, expected_match) == null) { diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 95e37f230a..9ab9972c2e 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -70,6 +70,8 @@ max_stdio_size: usize = 10 * 1024 * 1024, captured_stdout: ?*Output = null, captured_stderr: ?*Output = null, +has_side_effects: bool = false, + pub const StdIo = union(enum) { /// Whether the RunStep has side-effects will be determined by whether or not one /// of the args is an output file (added with `addOutputFileArg`). @@ -103,12 +105,14 @@ pub const StdIo = union(enum) { pub const Arg = union(enum) { artifact: *CompileStep, file_source: std.Build.FileSource, + directory_source: std.Build.FileSource, bytes: []u8, output: *Output, }; pub const Output = struct { generated_file: std.Build.GeneratedFile, + prefix: []const u8, basename: []const u8, }; @@ -142,10 +146,19 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { /// run, and returns a FileSource which can be used as inputs to other APIs /// throughout the build system. pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { + return addPrefixedOutputFileArg(rs, "", basename); +} + +pub fn addPrefixedOutputFileArg( + rs: *RunStep, + prefix: []const u8, + basename: []const u8, +) std.Build.FileSource { const b = rs.step.owner; const output = b.allocator.create(Output) catch @panic("OOM"); output.* = .{ + .prefix = prefix, .basename = basename, .generated_file = .{ .step = &rs.step }, }; @@ -159,14 +172,21 @@ pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource } pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { - self.argv.append(Arg{ + self.argv.append(.{ .file_source = file_source.dupe(self.step.owner), }) catch @panic("OOM"); file_source.addStepDependencies(&self.step); } +pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void { + self.argv.append(.{ + .directory_source = directory_source.dupe(self.step.owner), + }) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); +} + pub fn addArg(self: *RunStep, arg: []const u8) void { - self.argv.append(Arg{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); + self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); } pub fn addArgs(self: *RunStep, args: []const []const u8) void { @@ -274,6 +294,7 @@ pub fn captureStdErr(self: *RunStep) std.Build.FileSource { const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); output.* = .{ + .prefix = "", .basename = "stderr", .generated_file = .{ .step = &self.step }, }; @@ -288,6 +309,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile { const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); output.* = .{ + .prefix = "", .basename = "stdout", .generated_file = .{ .step = &self.step }, }; @@ -297,6 +319,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile { /// Returns whether the RunStep has side effects *other than* updating the output arguments. fn hasSideEffects(self: RunStep) bool { + if (self.has_side_effects) return true; return switch (self.stdio) { .infer_from_args => !self.hasAnyOutputArgs(), .inherit => true, @@ -373,6 +396,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try argv_list.append(file_path); _ = try man.addFile(file_path, null); }, + .directory_source => |file| { + const file_path = file.getPath(b); + try argv_list.append(file_path); + man.hash.addBytes(file_path); + }, .artifact => |artifact| { if (artifact.target.isWindows()) { // On Windows we don't have rpaths so we have to add .dll search paths to PATH @@ -386,6 +414,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { _ = try man.addFile(file_path, null); }, .output => |output| { + man.hash.addBytes(output.prefix); man.hash.addBytes(output.basename); // Add a placeholder into the argument list because we need the // manifest hash to be updated with all arguments before the @@ -456,7 +485,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }; const output_path = try b.cache_root.join(arena, &output_components); placeholder.output.generated_file.path = output_path; - argv_list.items[placeholder.index] = output_path; + const cli_arg = if (placeholder.output.prefix.len == 0) + output_path + else + b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); + argv_list.items[placeholder.index] = cli_arg; } try runCommand(self, argv_list.items, has_side_effects, &digest); diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig index dbb93d8c61..0cfd5d85a8 100644 --- a/lib/std/Build/TranslateCStep.zig +++ b/lib/std/Build/TranslateCStep.zig @@ -72,7 +72,11 @@ pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { } pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { - return CheckFileStep.create(self.step.owner, .{ .generated = &self.output_file }, self.step.owner.dupeStrings(expected_matches)); + return CheckFileStep.create( + self.step.owner, + .{ .generated = &self.output_file }, + .{ .expected_matches = expected_matches }, + ); } /// If the value is omitted, it is set to 1. diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 64025ce0fe..e6ceb4777c 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -14,6 +14,7 @@ step: Step, /// GeneratedFile field. files: std.ArrayListUnmanaged(*File), output_source_files: std.ArrayListUnmanaged(OutputSourceFile), +generated_directory: std.Build.GeneratedFile, pub const base_id = .write_file; @@ -33,8 +34,9 @@ pub const Contents = union(enum) { copy: std.Build.FileSource, }; -pub fn init(owner: *std.Build) WriteFileStep { - return .{ +pub fn create(owner: *std.Build) *WriteFileStep { + const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); + wf.* = .{ .step = Step.init(.{ .id = .write_file, .name = "WriteFile", @@ -43,7 +45,9 @@ pub fn init(owner: *std.Build) WriteFileStep { }), .files = .{}, .output_source_files = .{}, + .generated_directory = .{ .step = &wf.step }, }; + return wf; } pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { @@ -95,6 +99,20 @@ pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub }) catch @panic("OOM"); } +/// A path relative to the package root. +/// Be careful with this because it updates source files. This should not be +/// used as part of the normal build process, but as a utility occasionally +/// run by a developer with intent to modify source files and then commit +/// those changes to version control. +/// A file added this way is not available with `getFileSource`. +pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void { + const b = wf.step.owner; + wf.output_source_files.append(b.allocator, .{ + .contents = .{ .bytes = bytes }, + .sub_path = sub_path, + }) catch @panic("OOM"); +} + /// Gets a file source for the given sub_path. If the file does not exist, returns `null`. pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { for (wf.files.items) |file| { @@ -105,6 +123,12 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo return null; } +/// Returns a `FileSource` representing the base directory that contains all the +/// files from this `WriteFileStep`. +pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { + return .{ .generated = &wf.generated_directory }; +} + fn maybeUpdateName(wf: *WriteFileStep) void { if (wf.files.items.len == 1) { // First time adding a file; update name. @@ -193,12 +217,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { "o", &digest, file.sub_path, }); } + wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); return; } const digest = man.final(); const cache_path = "o" ++ fs.path.sep_str ++ digest; + wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); + var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { return step.fail("unable to make path '{}{s}': {s}", .{ b.cache_root, cache_path, @errorName(err), diff --git a/test/cli.zig b/test/cli.zig deleted file mode 100644 index 57f26f73d7..0000000000 --- a/test/cli.zig +++ /dev/null @@ -1,195 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const testing = std.testing; -const process = std.process; -const fs = std.fs; -const ChildProcess = std.ChildProcess; - -var a: std.mem.Allocator = undefined; - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - var arena = std.heap.ArenaAllocator.init(gpa.allocator()); - defer arena.deinit(); - - a = arena.allocator(); - var arg_it = try process.argsWithAllocator(a); - - // skip my own exe name - _ = arg_it.skip(); - - const zig_exe_rel = arg_it.next() orelse { - std.debug.print("Expected first argument to be path to zig compiler\n", .{}); - return error.InvalidArgs; - }; - const cache_root = arg_it.next() orelse { - std.debug.print("Expected second argument to be cache root directory path\n", .{}); - return error.InvalidArgs; - }; - const zig_exe = try fs.path.resolve(a, &[_][]const u8{zig_exe_rel}); - - const dir_path = try fs.path.join(a, &[_][]const u8{ cache_root, "clitest" }); - defer fs.cwd().deleteTree(dir_path) catch {}; - - const TestFn = fn ([]const u8, []const u8) anyerror!void; - const Test = struct { - func: TestFn, - name: []const u8, - }; - const tests = [_]Test{ - .{ .func = testZigInitLib, .name = "zig init-lib" }, - .{ .func = testZigInitExe, .name = "zig init-exe" }, - .{ .func = testGodboltApi, .name = "godbolt API" }, - .{ .func = testMissingOutputPath, .name = "missing output path" }, - .{ .func = testZigFmt, .name = "zig fmt" }, - }; - inline for (tests) |t| { - try fs.cwd().deleteTree(dir_path); - try fs.cwd().makeDir(dir_path); - t.func(zig_exe, dir_path) catch |err| { - std.debug.print("test '{s}' failed: {s}\n", .{ - t.name, @errorName(err), - }); - return err; - }; - } -} - -fn printCmd(cwd: []const u8, argv: []const []const u8) void { - std.debug.print("cd {s} && ", .{cwd}); - for (argv) |arg| { - std.debug.print("{s} ", .{arg}); - } - std.debug.print("\n", .{}); -} - -fn exec(cwd: []const u8, expect_0: bool, argv: []const []const u8) !ChildProcess.ExecResult { - const max_output_size = 100 * 1024; - const result = ChildProcess.exec(.{ - .allocator = a, - .argv = argv, - .cwd = cwd, - .max_output_bytes = max_output_size, - }) catch |err| { - std.debug.print("The following command failed:\n", .{}); - printCmd(cwd, argv); - return err; - }; - switch (result.term) { - .Exited => |code| { - if ((code != 0) == expect_0) { - std.debug.print("The following command exited with error code {}:\n", .{code}); - printCmd(cwd, argv); - std.debug.print("stderr:\n{s}\n", .{result.stderr}); - return error.CommandFailed; - } - }, - else => { - std.debug.print("The following command terminated unexpectedly:\n", .{}); - printCmd(cwd, argv); - std.debug.print("stderr:\n{s}\n", .{result.stderr}); - return error.CommandFailed; - }, - } - return result; -} - -fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void { - _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-lib" }); - const test_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "test" }); - try testing.expectStringEndsWith(test_result.stderr, "All 1 tests passed.\n"); -} - -fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void { - _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" }); - const run_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "run" }); - try testing.expectEqualStrings("All your codebase are belong to us.\n", run_result.stderr); - try testing.expectEqualStrings("Run `zig build test` to run the tests.\n", run_result.stdout); -} - -fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void { - if (builtin.os.tag != .linux or builtin.cpu.arch != .x86_64) return; - - const example_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.zig" }); - const example_s_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.s" }); - - try fs.cwd().writeFile(example_zig_path, - \\// Type your code here, or load an example. - \\export fn square(num: i32) i32 { - \\ return num * num; - \\} - \\extern fn zig_panic() noreturn; - \\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn { - \\ _ = msg; - \\ _ = error_return_trace; - \\ zig_panic(); - \\} - ); - - var args = std.ArrayList([]const u8).init(a); - try args.appendSlice(&[_][]const u8{ - zig_exe, "build-obj", - "--cache-dir", dir_path, - "--name", "example", - "-fno-emit-bin", "-fno-emit-h", - "-fstrip", "-OReleaseFast", - example_zig_path, - }); - - const emit_asm_arg = try std.fmt.allocPrint(a, "-femit-asm={s}", .{example_s_path}); - try args.append(emit_asm_arg); - - _ = try exec(dir_path, true, args.items); - - const out_asm = try std.fs.cwd().readFileAlloc(a, example_s_path, std.math.maxInt(usize)); - try testing.expect(std.mem.indexOf(u8, out_asm, "square:") != null); - try testing.expect(std.mem.indexOf(u8, out_asm, "mov\teax, edi") != null); - try testing.expect(std.mem.indexOf(u8, out_asm, "imul\teax, edi") != null); -} - -fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void { - _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" }); - const output_path = try fs.path.join(a, &[_][]const u8{ "does", "not", "exist", "foo.exe" }); - const output_arg = try std.fmt.allocPrint(a, "-femit-bin={s}", .{output_path}); - const source_path = try fs.path.join(a, &[_][]const u8{ "src", "main.zig" }); - const result = try exec(dir_path, false, &[_][]const u8{ zig_exe, "build-exe", source_path, output_arg }); - const s = std.fs.path.sep_str; - const expected: []const u8 = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n"; - try testing.expectEqualStrings(expected, result.stderr); -} - -fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void { - _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" }); - - const unformatted_code = " // no reason for indent"; - - const fmt1_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt1.zig" }); - try fs.cwd().writeFile(fmt1_zig_path, unformatted_code); - - const run_result1 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", fmt1_zig_path }); - // stderr should be file path + \n - try testing.expect(std.mem.startsWith(u8, run_result1.stdout, fmt1_zig_path)); - try testing.expect(run_result1.stdout.len == fmt1_zig_path.len + 1 and run_result1.stdout[run_result1.stdout.len - 1] == '\n'); - - const fmt2_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt2.zig" }); - try fs.cwd().writeFile(fmt2_zig_path, unformatted_code); - - const run_result2 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); - // running it on the dir, only the new file should be changed - try testing.expect(std.mem.startsWith(u8, run_result2.stdout, fmt2_zig_path)); - try testing.expect(run_result2.stdout.len == fmt2_zig_path.len + 1 and run_result2.stdout[run_result2.stdout.len - 1] == '\n'); - - const run_result3 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); - // both files have been formatted, nothing should change now - try testing.expect(run_result3.stdout.len == 0); - - // Check UTF-16 decoding - const fmt4_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt4.zig" }); - var unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; - try fs.cwd().writeFile(fmt4_zig_path, unformatted_code_utf16); - - const run_result4 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path }); - try testing.expect(std.mem.startsWith(u8, run_result4.stdout, fmt4_zig_path)); - try testing.expect(run_result4.stdout.len == fmt4_zig_path.len + 1 and run_result4.stdout[run_result4.stdout.len - 1] == '\n'); -} diff --git a/test/tests.zig b/test/tests.zig index e21e652cba..665ad023fd 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -635,19 +635,189 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co _ = optimize_modes; const step = b.step("test-cli", "Test the command line interface"); - const exe = b.addExecutable(.{ - .name = "test-cli", - .root_source_file = .{ .path = "test/cli.zig" }, - .target = .{}, - .optimize = .Debug, - }); - const run_cmd = exe.run(); - run_cmd.addArgs(&[_][]const u8{ - fs.realpathAlloc(b.allocator, b.zig_exe) catch @panic("OOM"), - b.pathFromRoot(b.cache_root.path orelse "."), - }); + { + // Test `zig init-lib`. + const tmp_path = b.makeTempPath(); + const init_lib = b.addSystemCommand(&.{ b.zig_exe, "init-lib" }); + init_lib.cwd = tmp_path; + init_lib.setName("zig init-lib"); + init_lib.expectStdOutEqual(""); + init_lib.expectStdErrEqual( + \\info: Created build.zig + \\info: Created src/main.zig + \\info: Next, try `zig build --help` or `zig build test` + \\ + ); + + const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" }); + run_test.cwd = tmp_path; + run_test.setName("zig build test"); + run_test.expectStdOutEqual(""); + run_test.step.dependOn(&init_lib.step); + + const cleanup = b.addRemoveDirTree(tmp_path); + cleanup.step.dependOn(&run_test.step); + + step.dependOn(&cleanup.step); + } + + { + // Test `zig init-exe`. + const tmp_path = b.makeTempPath(); + const init_exe = b.addSystemCommand(&.{ b.zig_exe, "init-exe" }); + init_exe.cwd = tmp_path; + init_exe.setName("zig init-exe"); + init_exe.expectStdOutEqual(""); + init_exe.expectStdErrEqual( + \\info: Created build.zig + \\info: Created src/main.zig + \\info: Next, try `zig build --help` or `zig build run` + \\ + ); + + // Test missing output path. + const s = std.fs.path.sep_str; + const bad_out_arg = "-femit-bin=does" ++ s ++ "not" ++ s ++ "exist" ++ s ++ "foo.exe"; + const ok_src_arg = "src" ++ s ++ "main.zig"; + const expected = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n"; + const run_bad = b.addSystemCommand(&.{ b.zig_exe, "build-exe", ok_src_arg, bad_out_arg }); + run_bad.setName("zig build-exe error message for bad -femit-bin arg"); + run_bad.expectExitCode(1); + run_bad.expectStdErrEqual(expected); + run_bad.expectStdOutEqual(""); + run_bad.step.dependOn(&init_exe.step); + + const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" }); + run_test.cwd = tmp_path; + run_test.setName("zig build test"); + run_test.expectStdOutEqual(""); + run_test.step.dependOn(&init_exe.step); + + const run_run = b.addSystemCommand(&.{ b.zig_exe, "build", "run" }); + run_run.cwd = tmp_path; + run_run.setName("zig build run"); + run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n"); + run_run.expectStdErrEqual("All your codebase are belong to us.\n"); + run_run.step.dependOn(&init_exe.step); + + const cleanup = b.addRemoveDirTree(tmp_path); + cleanup.step.dependOn(&run_test.step); + cleanup.step.dependOn(&run_run.step); + cleanup.step.dependOn(&run_bad.step); + + step.dependOn(&cleanup.step); + } + + // Test Godbolt API + if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) { + const tmp_path = b.makeTempPath(); + + const writefile = b.addWriteFile("example.zig", + \\// Type your code here, or load an example. + \\export fn square(num: i32) i32 { + \\ return num * num; + \\} + \\extern fn zig_panic() noreturn; + \\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn { + \\ _ = msg; + \\ _ = error_return_trace; + \\ zig_panic(); + \\} + ); + + // This is intended to be the exact CLI usage used by godbolt.org. + const run = b.addSystemCommand(&.{ + b.zig_exe, "build-obj", + "--cache-dir", tmp_path, + "--name", "example", + "-fno-emit-bin", "-fno-emit-h", + "-fstrip", "-OReleaseFast", + }); + run.addFileSourceArg(writefile.getFileSource("example.zig").?); + const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s"); + + const checkfile = b.addCheckFile(example_s, .{ + .expected_matches = &.{ + "square:", + "mov\teax, edi", + "imul\teax, edi", + }, + }); + checkfile.setName("check godbolt.org CLI usage generating valid asm"); + + const cleanup = b.addRemoveDirTree(tmp_path); + cleanup.step.dependOn(&checkfile.step); + + step.dependOn(&cleanup.step); + } + + { + // Test `zig fmt`. + // This test must use a temporary directory rather than a cache + // directory because this test will be mutating the files. The cache + // system relies on cache directories being mutated only by their + // owners. + const tmp_path = b.makeTempPath(); + const unformatted_code = " // no reason for indent"; + const s = std.fs.path.sep_str; + + var dir = fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); + defer dir.close(); + dir.writeFile("fmt1.zig", unformatted_code) catch @panic("unhandled"); + dir.writeFile("fmt2.zig", unformatted_code) catch @panic("unhandled"); + + // Test zig fmt affecting only the appropriate files. + const run1 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "fmt1.zig" }); + run1.setName("run zig fmt one file"); + run1.cwd = tmp_path; + run1.has_side_effects = true; + // stdout should be file path + \n + run1.expectStdOutEqual("fmt1.zig\n"); + + // running it on the dir, only the new file should be changed + const run2 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." }); + run2.setName("run zig fmt the directory"); + run2.cwd = tmp_path; + run2.has_side_effects = true; + run2.expectStdOutEqual("." ++ s ++ "fmt2.zig\n"); + run2.step.dependOn(&run1.step); + + // both files have been formatted, nothing should change now + const run3 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." }); + run3.setName("run zig fmt with nothing to do"); + run3.cwd = tmp_path; + run3.has_side_effects = true; + run3.expectStdOutEqual(""); + run3.step.dependOn(&run2.step); + + const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; + const fmt4_path = fs.path.join(b.allocator, &.{ tmp_path, "fmt4.zig" }) catch @panic("OOM"); + const write4 = b.addWriteFiles(); + write4.addBytesToSource(unformatted_code_utf16, fmt4_path); + write4.step.dependOn(&run3.step); + + // Test `zig fmt` handling UTF-16 decoding. + const run4 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." }); + run4.setName("run zig fmt convert UTF-16 to UTF-8"); + run4.cwd = tmp_path; + run4.has_side_effects = true; + run4.expectStdOutEqual("." ++ s ++ "fmt4.zig\n"); + run4.step.dependOn(&write4.step); + + // TODO change this to an exact match + const check4 = b.addCheckFile(.{ .path = fmt4_path }, .{ + .expected_matches = &.{ + "// no reason", + }, + }); + check4.step.dependOn(&run4.step); + + const cleanup = b.addRemoveDirTree(tmp_path); + cleanup.step.dependOn(&check4.step); + + step.dependOn(&cleanup.step); + } - step.dependOn(&run_cmd.step); return step; } From 8b871ae27584c2433683ca064dbd3e7127d2a184 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 22:57:53 -0700 Subject: [PATCH 210/294] re-enable C ABI tests These were mostly already using the correct build API. I cleaned up the code a bit and unconditionally disabled LTO for these tests since that actually tests the intended behavior better. --- build.zig | 2 +- test/tests.zig | 56 +++++++++++++++++++++++++------------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/build.zig b/build.zig index 7c22016bb7..b97ea3c24c 100644 --- a/build.zig +++ b/build.zig @@ -460,7 +460,7 @@ pub fn build(b: *std.Build) !void { // b.enable_wine, // enable_symlinks_windows, //)); - //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); + test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); diff --git a/test/tests.zig b/test/tests.zig index 665ad023fd..1e99e5c2a8 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -985,37 +985,37 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S const optimize_modes: [2]OptimizeMode = .{ .Debug, .ReleaseFast }; - for (optimize_modes[0 .. @as(u8, 1) + @boolToInt(!skip_release)]) |optimize_mode| for (c_abi_targets) |c_abi_target| { - if (skip_non_native and !c_abi_target.isNative()) - continue; + for (optimize_modes) |optimize_mode| { + if (optimize_mode != .Debug and skip_release) continue; - const test_step = b.addTest(.{ - .root_source_file = .{ .path = "test/c_abi/main.zig" }, - .optimize = optimize_mode, - .target = c_abi_target, - }); - if (c_abi_target.abi != null and c_abi_target.abi.?.isMusl()) { - // TODO NativeTargetInfo insists on dynamically linking musl - // for some reason? - test_step.target_info.dynamic_linker.max_byte = null; - } - test_step.linkLibC(); - test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"}); + for (c_abi_targets) |c_abi_target| { + if (skip_non_native and !c_abi_target.isNative()) continue; - if (c_abi_target.isWindows() and (c_abi_target.getCpuArch() == .x86 or builtin.target.os.tag == .linux)) { - // LTO currently incorrectly strips stdcall name-mangled functions - // LLD crashes in LTO here when cross compiling for windows on linux + const test_step = b.addTest(.{ + .root_source_file = .{ .path = "test/c_abi/main.zig" }, + .optimize = optimize_mode, + .target = c_abi_target, + }); + if (c_abi_target.abi != null and c_abi_target.abi.?.isMusl()) { + // TODO NativeTargetInfo insists on dynamically linking musl + // for some reason? + test_step.target_info.dynamic_linker.max_byte = null; + } + test_step.linkLibC(); + test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"}); + // This test is intentionally trying to check if the external ABI is + // done properly. LTO would be a hindrance to this. test_step.want_lto = false; + + const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM"); + test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{ + "test-c-abi", + triple_prefix, + @tagName(optimize_mode), + })); + + step.dependOn(&test_step.step); } - - const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM"); - test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{ - "test-c-abi", - triple_prefix, - @tagName(optimize_mode), - })); - - step.dependOn(&test_step.step); - }; + } return step; } From 263aaf0e66a1e2cdf5cebdbfc5e2761dc5a67181 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Mar 2023 23:00:32 -0700 Subject: [PATCH 211/294] re-enable asm-and-link tests These already looked pretty good. I deleted two unnecessary calls to expectStdErrEqual. --- build.zig | 2 +- test/src/CompareOutput.zig | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build.zig b/build.zig index b97ea3c24c..b494a4ad7a 100644 --- a/build.zig +++ b/build.zig @@ -464,7 +464,7 @@ pub fn build(b: *std.Build) !void { //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); - //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); + test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); if (!skip_run_translated_c) { test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target)); diff --git a/test/src/CompareOutput.zig b/test/src/CompareOutput.zig index 68cf942684..854bd11f9c 100644 --- a/test/src/CompareOutput.zig +++ b/test/src/CompareOutput.zig @@ -104,7 +104,6 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void { const run = exe.run(); run.setName(annotated_case_name); run.addArgs(case.cli_args); - run.expectStdErrEqual(""); run.expectStdOutEqual(case.expected_output); self.step.dependOn(&run.step); @@ -132,7 +131,6 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void { const run = exe.run(); run.setName(annotated_case_name); run.addArgs(case.cli_args); - run.expectStdErrEqual(""); run.expectStdOutEqual(case.expected_output); self.step.dependOn(&run.step); From 8510f9e2bc1c1da6b3d12a07ae613cb3ca888d63 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 00:38:59 -0700 Subject: [PATCH 212/294] std.Build: add addAnonymousDependency This is for bypassing the package manager and directly depending on another package via the build system. For this to work the anonymous package must be found on the file system relative to the current package's build.zig. --- lib/std/Build.zig | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 68e80435f8..c091079b38 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1457,7 +1457,26 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency { process.exit(1); } -fn dependencyInner( +pub fn anonymousDependency( + b: *Build, + /// The path to the directory containing the dependency's build.zig file, + /// relative to the current package's build.zig. + relative_build_root: []const u8, + /// A direct `@import` of the build.zig of the dependency. + comptime build_zig: type, + args: anytype, +) *Dependency { + const arena = b.allocator; + const build_root = b.build_root.join(arena, &.{relative_build_root}) catch @panic("OOM"); + const name = arena.dupe(u8, relative_build_root) catch @panic("OOM"); + for (name) |*byte| switch (byte.*) { + '/', '\\' => byte.* = '.', + else => continue, + }; + return dependencyInner(b, name, build_root, build_zig, args); +} + +pub fn dependencyInner( b: *Build, name: []const u8, build_root_string: []const u8, From 3b069907305497af5ef6cdb1d6adddd05636aa51 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 00:40:32 -0700 Subject: [PATCH 213/294] std.Build.CompileStep: tweak the default step name --- lib/std/Build/CompileStep.zig | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index df9abfbc6d..9b9fc02e74 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -310,8 +310,20 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); } - const step_name = owner.fmt("compile {s} {s} {s}", .{ - name, + // Avoid the common case of the step name looking like "zig test test". + const name_adjusted = if (options.kind == .@"test" and mem.eql(u8, name, "test")) + "" + else + owner.fmt("{s} ", .{name}); + + const step_name = owner.fmt("{s} {s}{s} {s}", .{ + switch (options.kind) { + .exe => "zig build-exe", + .lib => "zig build-lib", + .obj => "zig build-obj", + .test_exe, .@"test" => "zig test", + }, + name_adjusted, @tagName(options.optimize), options.target.zigTriple(owner.allocator) catch @panic("OOM"), }); From e122cd6312606068eb14987ed4d7527341484d26 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 00:40:55 -0700 Subject: [PATCH 214/294] new linker test harness It's simpler and it takes advantage of `std.Build.addAnonymousDependency`, which has a number of benefits, including concurrenc and preventing extra zig-cache and zig-out directories being created. 4 tests are ported over as an example. --- build.zig | 9 +- test/link.zig | 429 +++++++++--------- test/link/bss/build.zig | 6 +- test/link/bss/main.zig | 2 +- test/link/common_symbols/build.zig | 11 +- test/link/common_symbols_alignment/build.zig | 15 +- .../interdependent_static_c_libs/build.zig | 17 +- test/link/static_lib_as_system_lib/build.zig | 19 +- test/tests.zig | 58 +-- 9 files changed, 287 insertions(+), 279 deletions(-) diff --git a/build.zig b/build.zig index b494a4ad7a..83aabb6c41 100644 --- a/build.zig +++ b/build.zig @@ -442,8 +442,6 @@ pub fn build(b: *std.Build) !void { .skip_stage2 = true, // TODO get all these passing })); - _ = enable_symlinks_windows; - _ = enable_macos_sdk; test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); //test_step.dependOn(tests.addStandaloneTests( // b, @@ -453,15 +451,10 @@ pub fn build(b: *std.Build) !void { // enable_macos_sdk, // target, // skip_stage2_tests, - // b.enable_darling, - // b.enable_qemu, - // b.enable_rosetta, - // b.enable_wasmtime, - // b.enable_wine, // enable_symlinks_windows, //)); test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); - //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); + test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); diff --git a/test/link.zig b/test/link.zig index c787e8b1ae..d6d42a53e8 100644 --- a/test/link.zig +++ b/test/link.zig @@ -1,213 +1,220 @@ +pub const Case = struct { + build_root: []const u8, + import: type, +}; + +pub const cases = [_]Case{ + .{ + .build_root = "test/link/bss", + .import = @import("link/bss/build.zig"), + }, + .{ + .build_root = "test/link/common_symbols", + .import = @import("link/common_symbols/build.zig"), + }, + .{ + .build_root = "test/link/common_symbols_alignment", + .import = @import("link/common_symbols_alignment/build.zig"), + }, + .{ + .build_root = "test/link/interdependent_static_c_libs", + .import = @import("link/interdependent_static_c_libs/build.zig"), + }, + //.{ + // .build_root = "test/link/static_lib_as_system_lib", + // .import = @import("link/static_lib_as_system_lib/build.zig"), + //}, +}; + +//pub fn addCases(cases: *Standalone) void { +// addWasmCases(cases); +// addMachOCases(cases); +//} +// +//fn addWasmCases(cases: *Standalone) void { +// cases.addBuildFile("test/link/wasm/archive/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/basic-features/build.zig", .{ +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/bss/build.zig", .{ +// .build_modes = false, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/export/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// // TODO: Fix open handle in wasm-linker refraining rename from working on Windows. +// if (builtin.os.tag != .windows) { +// cases.addBuildFile("test/link/wasm/export-data/build.zig", .{}); +// } +// +// cases.addBuildFile("test/link/wasm/extern/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// .use_emulation = true, +// }); +// +// cases.addBuildFile("test/link/wasm/extern-mangle/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/function-table/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{ +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/producers/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/segments/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/stack_pointer/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +// +// cases.addBuildFile("test/link/wasm/type/build.zig", .{ +// .build_modes = true, +// .requires_stage2 = true, +// }); +//} +// +//fn addMachOCases(cases: *tests.StandaloneContext) void { +// cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ +// .build_modes = false, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/dylib/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/empty/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/entry/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/linksection/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/objc/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ +// .build_modes = false, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/tls/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/unwind_info/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/uuid/build.zig", .{ +// .build_modes = false, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ +// .build_modes = true, +// .requires_symlinks = true, +// }); +// +// cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ +// .build_modes = true, +// .requires_macos_sdk = true, +// .requires_symlinks = true, +// }); +//} + const std = @import("std"); const builtin = @import("builtin"); -const tests = @import("tests.zig"); - -pub fn addCases(cases: *tests.StandaloneContext) void { - cases.addBuildFile("test/link/bss/build.zig", .{ - .build_modes = false, // we only guarantee zerofill for undefined in Debug - }); - - cases.addBuildFile("test/link/common_symbols/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/common_symbols_alignment/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/interdependent_static_c_libs/build.zig", .{ - .build_modes = true, - }); - - cases.addBuildFile("test/link/static_lib_as_system_lib/build.zig", .{ - .build_modes = true, - }); - - addWasmCases(cases); - addMachOCases(cases); -} - -fn addWasmCases(cases: *tests.StandaloneContext) void { - cases.addBuildFile("test/link/wasm/archive/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/basic-features/build.zig", .{ - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/bss/build.zig", .{ - .build_modes = false, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/export/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - // TODO: Fix open handle in wasm-linker refraining rename from working on Windows. - if (builtin.os.tag != .windows) { - cases.addBuildFile("test/link/wasm/export-data/build.zig", .{}); - } - - cases.addBuildFile("test/link/wasm/extern/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - .use_emulation = true, - }); - - cases.addBuildFile("test/link/wasm/extern-mangle/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/function-table/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{ - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/producers/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/segments/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/stack_pointer/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); - - cases.addBuildFile("test/link/wasm/type/build.zig", .{ - .build_modes = true, - .requires_stage2 = true, - }); -} - -fn addMachOCases(cases: *tests.StandaloneContext) void { - cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ - .build_modes = false, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/dylib/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/empty/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/entry/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/linksection/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/objc/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ - .build_modes = false, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/tls/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/unwind_info/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/uuid/build.zig", .{ - .build_modes = false, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ - .build_modes = true, - .requires_symlinks = true, - }); - - cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - .requires_symlinks = true, - }); -} diff --git a/test/link/bss/build.zig b/test/link/bss/build.zig index 0df9c1d323..86600b58f5 100644 --- a/test/link/bss/build.zig +++ b/test/link/bss/build.zig @@ -1,17 +1,17 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); const test_step = b.step("test", "Test"); + b.default_step = test_step; const exe = b.addExecutable(.{ .name = "bss", .root_source_file = .{ .path = "main.zig" }, - .optimize = optimize, + .optimize = .Debug, }); - b.default_step.dependOn(&exe.step); const run = exe.run(); run.expectStdOutEqual("0, 1, 0\n"); + test_step.dependOn(&run.step); } diff --git a/test/link/bss/main.zig b/test/link/bss/main.zig index c901f0bb27..d2ecffe982 100644 --- a/test/link/bss/main.zig +++ b/test/link/bss/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); // Stress test zerofill layout -var buffer: [0x1000000]u64 = undefined; +var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000; pub fn main() anyerror!void { buffer[0x10] = 1; diff --git a/test/link/common_symbols/build.zig b/test/link/common_symbols/build.zig index ee9dd94ebd..e3c302f0f7 100644 --- a/test/link/common_symbols/build.zig +++ b/test/link/common_symbols/build.zig @@ -1,8 +1,16 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib_a = b.addStaticLibrary(.{ .name = "a", .optimize = optimize, @@ -16,6 +24,5 @@ pub fn build(b: *std.Build) void { }); test_exe.linkLibrary(lib_a); - const test_step = b.step("test", "Test it"); test_step.dependOn(&test_exe.step); } diff --git a/test/link/common_symbols_alignment/build.zig b/test/link/common_symbols_alignment/build.zig index f6efdc784b..7d1d813447 100644 --- a/test/link/common_symbols_alignment/build.zig +++ b/test/link/common_symbols_alignment/build.zig @@ -1,23 +1,28 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib_a = b.addStaticLibrary(.{ .name = "a", .optimize = optimize, - .target = target, + .target = .{}, }); lib_a.addCSourceFiles(&.{"a.c"}, &.{"-fcommon"}); const test_exe = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, - .target = target, }); test_exe.linkLibrary(lib_a); - const test_step = b.step("test", "Test it"); test_step.dependOn(&test_exe.step); } diff --git a/test/link/interdependent_static_c_libs/build.zig b/test/link/interdependent_static_c_libs/build.zig index d8962a8e08..c4118c1ca4 100644 --- a/test/link/interdependent_static_c_libs/build.zig +++ b/test/link/interdependent_static_c_libs/build.zig @@ -1,13 +1,20 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib_a = b.addStaticLibrary(.{ .name = "a", .optimize = optimize, - .target = target, + .target = .{}, }); lib_a.addCSourceFile("a.c", &[_][]const u8{}); lib_a.addIncludePath("."); @@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void { const lib_b = b.addStaticLibrary(.{ .name = "b", .optimize = optimize, - .target = target, + .target = .{}, }); lib_b.addCSourceFile("b.c", &[_][]const u8{}); lib_b.addIncludePath("."); @@ -23,12 +30,10 @@ pub fn build(b: *std.Build) void { const test_exe = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, - .target = target, }); test_exe.linkLibrary(lib_a); test_exe.linkLibrary(lib_b); test_exe.addIncludePath("."); - const test_step = b.step("test", "Test it"); test_step.dependOn(&test_exe.step); } diff --git a/test/link/static_lib_as_system_lib/build.zig b/test/link/static_lib_as_system_lib/build.zig index b6cf32d711..1957d2e134 100644 --- a/test/link/static_lib_as_system_lib/build.zig +++ b/test/link/static_lib_as_system_lib/build.zig @@ -1,13 +1,20 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib_a = b.addStaticLibrary(.{ .name = "a", .optimize = optimize, - .target = target, + .target = .{}, }); lib_a.addCSourceFile("a.c", &[_][]const u8{}); lib_a.addIncludePath("."); @@ -16,14 +23,12 @@ pub fn build(b: *std.Build) void { const test_exe = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, - .target = target, + .target = .{}, }); test_exe.linkSystemLibrary("a"); // force linking liba.a as -la test_exe.addSystemIncludePath("."); - const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch unreachable; + const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch @panic("OOM"); test_exe.addLibraryPath(search_path); - const test_step = b.step("test", "Test it"); - test_step.dependOn(b.getInstallStep()); test_step.dependOn(&test_exe.step); } diff --git a/test/tests.zig b/test/tests.zig index 1e99e5c2a8..d5ee83b447 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,16 +1,9 @@ const std = @import("std"); const builtin = @import("builtin"); -const debug = std.debug; +const assert = std.debug.assert; const CrossTarget = std.zig.CrossTarget; -const io = std.io; -const fs = std.fs; const mem = std.mem; -const fmt = std.fmt; -const ArrayList = std.ArrayList; const OptimizeMode = std.builtin.OptimizeMode; -const CompileStep = std.Build.CompileStep; -const Allocator = mem.Allocator; -const ExecError = std.Build.ExecError; const Step = std.Build.Step; // Cases @@ -574,15 +567,10 @@ pub fn addStandaloneTests( enable_macos_sdk: bool, target: std.zig.CrossTarget, omit_stage2: bool, - enable_darling: bool, - enable_qemu: bool, - enable_rosetta: bool, - enable_wasmtime: bool, - enable_wine: bool, enable_symlinks_windows: bool, ) *Step { const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); - cases.* = StandaloneContext{ + cases.* = .{ .b = b, .step = b.step("test-standalone", "Run the standalone tests"), .test_index = 0, @@ -592,11 +580,6 @@ pub fn addStandaloneTests( .enable_macos_sdk = enable_macos_sdk, .target = target, .omit_stage2 = omit_stage2, - .enable_darling = enable_darling, - .enable_qemu = enable_qemu, - .enable_rosetta = enable_rosetta, - .enable_wasmtime = enable_wasmtime, - .enable_wine = enable_wine, .enable_symlinks_windows = enable_symlinks_windows, }; @@ -613,21 +596,24 @@ pub fn addLinkTests( omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); - cases.* = StandaloneContext{ - .b = b, - .step = b.step("test-link", "Run the linker tests"), - .test_index = 0, - .test_filter = test_filter, - .optimize_modes = optimize_modes, - .skip_non_native = true, - .enable_macos_sdk = enable_macos_sdk, - .target = .{}, - .omit_stage2 = omit_stage2, - .enable_symlinks_windows = enable_symlinks_windows, - }; - link.addCases(cases); - return cases.step; + _ = test_filter; + _ = optimize_modes; + _ = enable_macos_sdk; + _ = omit_stage2; + _ = enable_symlinks_windows; + + const step = b.step("test-link", "Run the linker tests"); + + inline for (link.cases) |link_test| { + const dep = b.anonymousDependency(link_test.build_root, link_test.import, .{}); + const dep_step = dep.builder.default_step; + assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); + const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; + dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name }); + step.dependOn(dep_step); + } + + return step; } pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { @@ -761,7 +747,7 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co const unformatted_code = " // no reason for indent"; const s = std.fs.path.sep_str; - var dir = fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); + var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); defer dir.close(); dir.writeFile("fmt1.zig", unformatted_code) catch @panic("unhandled"); dir.writeFile("fmt2.zig", unformatted_code) catch @panic("unhandled"); @@ -791,7 +777,7 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co run3.step.dependOn(&run2.step); const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00"; - const fmt4_path = fs.path.join(b.allocator, &.{ tmp_path, "fmt4.zig" }) catch @panic("OOM"); + const fmt4_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt4.zig" }) catch @panic("OOM"); const write4 = b.addWriteFiles(); write4.addBytesToSource(unformatted_code_utf16, fmt4_path); write4.step.dependOn(&run3.step); From 4efeeaac881c5583321e8b385e90f004e99bc3d1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 00:44:10 -0700 Subject: [PATCH 215/294] delete link test "static_lib_as_system_lib" I disagree with this behavior and will be reverting the changes corresponding to this test case. Also this test case unnecessarily uses a .c file when a .zig file would be preferred, and has a problematic dependency on the install step, preventing this test case from playing nicely with the cache. --- test/link.zig | 4 --- test/link/static_lib_as_system_lib/a.c | 4 --- test/link/static_lib_as_system_lib/a.h | 2 -- test/link/static_lib_as_system_lib/build.zig | 34 -------------------- test/link/static_lib_as_system_lib/main.zig | 8 ----- 5 files changed, 52 deletions(-) delete mode 100644 test/link/static_lib_as_system_lib/a.c delete mode 100644 test/link/static_lib_as_system_lib/a.h delete mode 100644 test/link/static_lib_as_system_lib/build.zig delete mode 100644 test/link/static_lib_as_system_lib/main.zig diff --git a/test/link.zig b/test/link.zig index d6d42a53e8..34862e0286 100644 --- a/test/link.zig +++ b/test/link.zig @@ -20,10 +20,6 @@ pub const cases = [_]Case{ .build_root = "test/link/interdependent_static_c_libs", .import = @import("link/interdependent_static_c_libs/build.zig"), }, - //.{ - // .build_root = "test/link/static_lib_as_system_lib", - // .import = @import("link/static_lib_as_system_lib/build.zig"), - //}, }; //pub fn addCases(cases: *Standalone) void { diff --git a/test/link/static_lib_as_system_lib/a.c b/test/link/static_lib_as_system_lib/a.c deleted file mode 100644 index ee9da97a3a..0000000000 --- a/test/link/static_lib_as_system_lib/a.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "a.h" -int32_t add(int32_t a, int32_t b) { - return a + b; -} diff --git a/test/link/static_lib_as_system_lib/a.h b/test/link/static_lib_as_system_lib/a.h deleted file mode 100644 index 7b45d54d56..0000000000 --- a/test/link/static_lib_as_system_lib/a.h +++ /dev/null @@ -1,2 +0,0 @@ -#include -int32_t add(int32_t a, int32_t b); diff --git a/test/link/static_lib_as_system_lib/build.zig b/test/link/static_lib_as_system_lib/build.zig deleted file mode 100644 index 1957d2e134..0000000000 --- a/test/link/static_lib_as_system_lib/build.zig +++ /dev/null @@ -1,34 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test it"); - b.default_step = test_step; - - add(b, test_step, .Debug); - add(b, test_step, .ReleaseFast); - add(b, test_step, .ReleaseSmall); - add(b, test_step, .ReleaseSafe); -} - -fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { - const lib_a = b.addStaticLibrary(.{ - .name = "a", - .optimize = optimize, - .target = .{}, - }); - lib_a.addCSourceFile("a.c", &[_][]const u8{}); - lib_a.addIncludePath("."); - lib_a.install(); - - const test_exe = b.addTest(.{ - .root_source_file = .{ .path = "main.zig" }, - .optimize = optimize, - .target = .{}, - }); - test_exe.linkSystemLibrary("a"); // force linking liba.a as -la - test_exe.addSystemIncludePath("."); - const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch @panic("OOM"); - test_exe.addLibraryPath(search_path); - - test_step.dependOn(&test_exe.step); -} diff --git a/test/link/static_lib_as_system_lib/main.zig b/test/link/static_lib_as_system_lib/main.zig deleted file mode 100644 index 0b9c46217f..0000000000 --- a/test/link/static_lib_as_system_lib/main.zig +++ /dev/null @@ -1,8 +0,0 @@ -const std = @import("std"); -const expect = std.testing.expect; -const c = @cImport(@cInclude("a.h")); - -test "import C add" { - const result = c.add(2, 1); - try expect(result == 3); -} From f558c835a41052b7609f0f0b27c5bbc4fe6b024d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 16:59:53 -0700 Subject: [PATCH 216/294] std.Build.CheckObjectStep: better error message when reading the file fails --- lib/std/Build/CheckObjectStep.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig index 2a58850fab..7cac2d04ec 100644 --- a/lib/std/Build/CheckObjectStep.zig +++ b/lib/std/Build/CheckObjectStep.zig @@ -314,14 +314,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const self = @fieldParentPtr(CheckObjectStep, "step", step); const src_path = self.source.getPath(b); - const contents = try fs.cwd().readFileAllocOptions( + const contents = fs.cwd().readFileAllocOptions( gpa, src_path, self.max_bytes, null, @alignOf(u64), null, - ); + ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) }); const output = switch (self.obj_format) { .macho => try MachODumper.parseAndDump(step, contents, .{ From cdf0a2af58ccd4ca4250435f258b35735e953aaf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 17:00:35 -0700 Subject: [PATCH 217/294] re-enable wasm linker tests --- test/link.zig | 362 +++++++++++------------- test/link/wasm/archive/build.zig | 17 +- test/link/wasm/basic-features/build.zig | 5 +- test/link/wasm/bss/build.zig | 7 +- test/link/wasm/export-data/build.zig | 7 +- test/link/wasm/export/build.zig | 15 +- test/link/wasm/extern-mangle/build.zig | 14 +- test/link/wasm/extern/build.zig | 15 +- test/link/wasm/function-table/build.zig | 19 +- test/link/wasm/infer-features/build.zig | 9 +- test/link/wasm/producers/build.zig | 21 +- test/link/wasm/segments/build.zig | 17 +- test/link/wasm/stack_pointer/build.zig | 17 +- test/link/wasm/type/build.zig | 17 +- test/tests.zig | 17 +- 15 files changed, 316 insertions(+), 243 deletions(-) diff --git a/test/link.zig b/test/link.zig index 34862e0286..ae970beaba 100644 --- a/test/link.zig +++ b/test/link.zig @@ -20,197 +20,179 @@ pub const cases = [_]Case{ .build_root = "test/link/interdependent_static_c_libs", .import = @import("link/interdependent_static_c_libs/build.zig"), }, -}; -//pub fn addCases(cases: *Standalone) void { -// addWasmCases(cases); -// addMachOCases(cases); -//} -// -//fn addWasmCases(cases: *Standalone) void { -// cases.addBuildFile("test/link/wasm/archive/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/basic-features/build.zig", .{ -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/bss/build.zig", .{ -// .build_modes = false, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/export/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// // TODO: Fix open handle in wasm-linker refraining rename from working on Windows. -// if (builtin.os.tag != .windows) { -// cases.addBuildFile("test/link/wasm/export-data/build.zig", .{}); -// } -// -// cases.addBuildFile("test/link/wasm/extern/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// .use_emulation = true, -// }); -// -// cases.addBuildFile("test/link/wasm/extern-mangle/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/function-table/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{ -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/producers/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/segments/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/stack_pointer/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -// -// cases.addBuildFile("test/link/wasm/type/build.zig", .{ -// .build_modes = true, -// .requires_stage2 = true, -// }); -//} -// -//fn addMachOCases(cases: *tests.StandaloneContext) void { -// cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ -// .build_modes = false, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/dylib/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/empty/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/entry/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/linksection/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/objc/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ -// .build_modes = false, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/tls/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/unwind_info/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/uuid/build.zig", .{ -// .build_modes = false, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ -// .build_modes = true, -// .requires_symlinks = true, -// }); -// -// cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ -// .build_modes = true, -// .requires_macos_sdk = true, -// .requires_symlinks = true, -// }); -//} + // WASM Cases + .{ + .build_root = "test/link/wasm/archive", + .import = @import("link/wasm/archive/build.zig"), + }, + .{ + .build_root = "test/link/wasm/basic-features", + .import = @import("link/wasm/basic-features/build.zig"), + }, + .{ + .build_root = "test/link/wasm/bss", + .import = @import("link/wasm/bss/build.zig"), + }, + .{ + .build_root = "test/link/wasm/export", + .import = @import("link/wasm/export/build.zig"), + }, + .{ + .build_root = "test/link/wasm/export-data", + .import = @import("link/wasm/export-data/build.zig"), + }, + .{ + .build_root = "test/link/wasm/extern", + .import = @import("link/wasm/extern/build.zig"), + }, + .{ + .build_root = "test/link/wasm/extern-mangle", + .import = @import("link/wasm/extern-mangle/build.zig"), + }, + .{ + .build_root = "test/link/wasm/function-table", + .import = @import("link/wasm/function-table/build.zig"), + }, + .{ + .build_root = "test/link/wasm/infer-features", + .import = @import("link/wasm/infer-features/build.zig"), + }, + .{ + .build_root = "test/link/wasm/producers", + .import = @import("link/wasm/producers/build.zig"), + }, + .{ + .build_root = "test/link/wasm/segments", + .import = @import("link/wasm/segments/build.zig"), + }, + .{ + .build_root = "test/link/wasm/stack_pointer", + .import = @import("link/wasm/stack_pointer/build.zig"), + }, + .{ + .build_root = "test/link/wasm/type", + .import = @import("link/wasm/type/build.zig"), + }, + + // Mach-O Cases + // cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ + // .build_modes = false, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/dylib/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/empty/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/entry/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/linksection/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/objc/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ + // .build_modes = false, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/tls/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/unwind_info/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/uuid/build.zig", .{ + // .build_modes = false, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ + // .build_modes = true, + // .requires_symlinks = true, + // }); + // + // cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ + // .build_modes = true, + // .requires_macos_sdk = true, + // .requires_symlinks = true, + // }); +}; const std = @import("std"); const builtin = @import("builtin"); diff --git a/test/link/wasm/archive/build.zig b/test/link/wasm/archive/build.zig index 342c4c08d1..f586187105 100644 --- a/test/link/wasm/archive/build.zig +++ b/test/link/wasm/archive/build.zig @@ -1,15 +1,24 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { // The code in question will pull-in compiler-rt, // and therefore link with its archive file. const lib = b.addSharedLibrary(.{ .name = "main", .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, }); lib.use_llvm = false; diff --git a/test/link/wasm/basic-features/build.zig b/test/link/wasm/basic-features/build.zig index 9f57066518..f3f5320e54 100644 --- a/test/link/wasm/basic-features/build.zig +++ b/test/link/wasm/basic-features/build.zig @@ -1,11 +1,13 @@ const std = @import("std"); +pub const requires_stage2 = true; + pub fn build(b: *std.Build) void { // Library with explicitly set cpu features const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = .Debug, .target = .{ .cpu_arch = .wasm32, .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, @@ -24,4 +26,5 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run linker test"); test_step.dependOn(&check.step); + b.default_step = test_step; } diff --git a/test/link/wasm/bss/build.zig b/test/link/wasm/bss/build.zig index 1017e70a71..8e6b19c7be 100644 --- a/test/link/wasm/bss/build.zig +++ b/test/link/wasm/bss/build.zig @@ -1,14 +1,16 @@ const std = @import("std"); +pub const requires_stage2 = true; + pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + b.default_step = test_step; const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = .Debug, }); lib.use_llvm = false; lib.use_lld = false; @@ -36,5 +38,6 @@ pub fn build(b: *std.Build) void { check_lib.checkNext("name .rodata"); check_lib.checkNext("index 1"); // bss section always last check_lib.checkNext("name .bss"); + test_step.dependOn(&check_lib.step); } diff --git a/test/link/wasm/export-data/build.zig b/test/link/wasm/export-data/build.zig index c989153e47..0bd10921a2 100644 --- a/test/link/wasm/export-data/build.zig +++ b/test/link/wasm/export-data/build.zig @@ -2,7 +2,12 @@ const std = @import("std"); pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + b.default_step = test_step; + + if (@import("builtin").os.tag == .windows) { + // TODO: Fix open handle in wasm-linker refraining rename from working on Windows. + return; + } const lib = b.addSharedLibrary(.{ .name = "lib", diff --git a/test/link/wasm/export/build.zig b/test/link/wasm/export/build.zig index 69c34a320e..03c4baabe3 100644 --- a/test/link/wasm/export/build.zig +++ b/test/link/wasm/export/build.zig @@ -1,8 +1,18 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const no_export = b.addSharedLibrary(.{ .name = "no-export", .root_source_file = .{ .path = "main.zig" }, @@ -50,7 +60,6 @@ pub fn build(b: *std.Build) void { check_force_export.checkNext("name foo"); check_force_export.checkNext("kind function"); - const test_step = b.step("test", "Run linker test"); test_step.dependOn(&check_no_export.step); test_step.dependOn(&check_dynamic_export.step); test_step.dependOn(&check_force_export.step); diff --git a/test/link/wasm/extern-mangle/build.zig b/test/link/wasm/extern-mangle/build.zig index 19913e6eca..b0655cbc1f 100644 --- a/test/link/wasm/extern-mangle/build.zig +++ b/test/link/wasm/extern-mangle/build.zig @@ -1,18 +1,24 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); lib.import_symbols = true; // import `a` and `b` lib.rdynamic = true; // export `foo` - lib.install(); const check_lib = lib.checkObject(.wasm); check_lib.checkStart("Section import"); diff --git a/test/link/wasm/extern/build.zig b/test/link/wasm/extern/build.zig index fede2b18ab..55562143c2 100644 --- a/test/link/wasm/extern/build.zig +++ b/test/link/wasm/extern/build.zig @@ -1,10 +1,22 @@ const std = @import("std"); +pub const requires_stage2 = true; + pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const exe = b.addExecutable(.{ .name = "extern", .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, .target = .{ .cpu_arch = .wasm32, .os_tag = .wasi }, }); exe.addCSourceFile("foo.c", &.{}); @@ -15,6 +27,5 @@ pub fn build(b: *std.Build) void { run.skip_foreign_checks = true; run.expectStdOutEqual("Result: 30"); - const test_step = b.step("test", "Run linker test"); test_step.dependOn(&run.step); } diff --git a/test/link/wasm/function-table/build.zig b/test/link/wasm/function-table/build.zig index 4c25d0d860..e1921caee3 100644 --- a/test/link/wasm/function-table/build.zig +++ b/test/link/wasm/function-table/build.zig @@ -1,13 +1,20 @@ const std = @import("std"); +pub const requires_stage2 = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const import_table = b.addSharedLibrary(.{ - .name = "lib", + .name = "import_table", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, .optimize = optimize, @@ -17,7 +24,7 @@ pub fn build(b: *std.Build) void { import_table.import_table = true; const export_table = b.addSharedLibrary(.{ - .name = "lib", + .name = "export_table", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, .optimize = optimize, @@ -27,7 +34,7 @@ pub fn build(b: *std.Build) void { export_table.export_table = true; const regular_table = b.addSharedLibrary(.{ - .name = "lib", + .name = "regular_table", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, .optimize = optimize, diff --git a/test/link/wasm/infer-features/build.zig b/test/link/wasm/infer-features/build.zig index d6d706a33d..a7cfa985d5 100644 --- a/test/link/wasm/infer-features/build.zig +++ b/test/link/wasm/infer-features/build.zig @@ -1,12 +1,12 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { // Wasm Object file which we will use to infer the features from const c_obj = b.addObject(.{ .name = "c_obj", - .optimize = optimize, + .optimize = .Debug, .target = .{ .cpu_arch = .wasm32, .cpu_model = .{ .explicit = &std.Target.wasm.cpu.bleeding_edge }, @@ -20,7 +20,7 @@ pub fn build(b: *std.Build) void { const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "main.zig" }, - .optimize = optimize, + .optimize = .Debug, .target = .{ .cpu_arch = .wasm32, .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, @@ -45,4 +45,5 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run linker test"); test_step.dependOn(&check.step); + b.default_step = test_step; } diff --git a/test/link/wasm/producers/build.zig b/test/link/wasm/producers/build.zig index 2589b0dfcf..41952b0adb 100644 --- a/test/link/wasm/producers/build.zig +++ b/test/link/wasm/producers/build.zig @@ -1,24 +1,31 @@ const std = @import("std"); const builtin = @import("builtin"); -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); lib.use_llvm = false; lib.use_lld = false; lib.strip = false; lib.install(); - const zig_version = builtin.zig_version; - var version_buf: [100]u8 = undefined; - const version_fmt = std.fmt.bufPrint(&version_buf, "version {}", .{zig_version}) catch unreachable; + const version_fmt = "version " ++ builtin.zig_version_string; const check_lib = lib.checkObject(.wasm); check_lib.checkStart("name producers"); diff --git a/test/link/wasm/segments/build.zig b/test/link/wasm/segments/build.zig index 76160e905f..f8e16eee24 100644 --- a/test/link/wasm/segments/build.zig +++ b/test/link/wasm/segments/build.zig @@ -1,14 +1,23 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); lib.use_llvm = false; lib.use_lld = false; diff --git a/test/link/wasm/stack_pointer/build.zig b/test/link/wasm/stack_pointer/build.zig index 95c7643880..41bdd828f2 100644 --- a/test/link/wasm/stack_pointer/build.zig +++ b/test/link/wasm/stack_pointer/build.zig @@ -1,14 +1,23 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); lib.use_llvm = false; lib.use_lld = false; diff --git a/test/link/wasm/type/build.zig b/test/link/wasm/type/build.zig index 816b57ccab..df8cbad021 100644 --- a/test/link/wasm/type/build.zig +++ b/test/link/wasm/type/build.zig @@ -1,14 +1,23 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub const requires_stage2 = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const lib = b.addSharedLibrary(.{ .name = "lib", .root_source_file = .{ .path = "lib.zig" }, .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); lib.use_llvm = false; lib.use_lld = false; diff --git a/test/tests.zig b/test/tests.zig index d5ee83b447..a23870b79e 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -599,18 +599,21 @@ pub fn addLinkTests( _ = test_filter; _ = optimize_modes; _ = enable_macos_sdk; - _ = omit_stage2; _ = enable_symlinks_windows; const step = b.step("test-link", "Run the linker tests"); inline for (link.cases) |link_test| { - const dep = b.anonymousDependency(link_test.build_root, link_test.import, .{}); - const dep_step = dep.builder.default_step; - assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); - const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; - dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name }); - step.dependOn(dep_step); + const requires_stage2 = @hasDecl(link_test.import, "requires_stage2") and + link_test.import.requires_stage2; + if (!requires_stage2 or !omit_stage2) { + const dep = b.anonymousDependency(link_test.build_root, link_test.import, .{}); + const dep_step = dep.builder.default_step; + assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); + const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; + dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name }); + step.dependOn(dep_step); + } } return step; From 9a9b0083009f62dda9500f978544380a17b1f325 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 19:17:32 -0700 Subject: [PATCH 218/294] std.Build.CompileStep: add FileSource support to some paths Library paths, RPaths, and framework paths now support being fulfilled by FileSource arguments. --- lib/std/Build/CompileStep.zig | 79 +++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 9b9fc02e74..42cc63e8b4 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -46,9 +46,9 @@ strip: ?bool, unwind_tables: ?bool, // keep in sync with src/link.zig:CompressDebugSections compress_debug_sections: enum { none, zlib } = .none, -lib_paths: ArrayList([]const u8), -rpaths: ArrayList([]const u8), -framework_dirs: ArrayList([]const u8), +lib_paths: ArrayList(FileSource), +rpaths: ArrayList(FileSource), +framework_dirs: ArrayList(FileSource), frameworks: StringHashMap(FrameworkLinkInfo), verbose_link: bool, verbose_cc: bool, @@ -211,6 +211,7 @@ output_path_source: GeneratedFile, output_lib_path_source: GeneratedFile, output_h_path_source: GeneratedFile, output_pdb_path_source: GeneratedFile, +output_dirname_source: GeneratedFile, pub const CSourceFiles = struct { files: []const []const u8, @@ -359,9 +360,9 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .include_dirs = ArrayList(IncludeDir).init(owner.allocator), .link_objects = ArrayList(LinkObject).init(owner.allocator), .c_macros = ArrayList([]const u8).init(owner.allocator), - .lib_paths = ArrayList([]const u8).init(owner.allocator), - .rpaths = ArrayList([]const u8).init(owner.allocator), - .framework_dirs = ArrayList([]const u8).init(owner.allocator), + .lib_paths = ArrayList(FileSource).init(owner.allocator), + .rpaths = ArrayList(FileSource).init(owner.allocator), + .framework_dirs = ArrayList(FileSource).init(owner.allocator), .installed_headers = ArrayList(*Step).init(owner.allocator), .object_src = undefined, .c_std = std.Build.CStd.C99, @@ -384,6 +385,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .output_lib_path_source = GeneratedFile{ .step = &self.step }, .output_h_path_source = GeneratedFile{ .step = &self.step }, .output_pdb_path_source = GeneratedFile{ .step = &self.step }, + .output_dirname_source = GeneratedFile{ .step = &self.step }, .target_info = NativeTargetInfo.detect(self.target) catch @panic("unhandled error"), }; @@ -914,13 +916,17 @@ pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void { /// Returns the generated executable, library or object file. /// To run an executable built with zig build, use `run`, or create an install step and invoke it. pub fn getOutputSource(self: *CompileStep) FileSource { - return FileSource{ .generated = &self.output_path_source }; + return .{ .generated = &self.output_path_source }; +} + +pub fn getOutputDirectorySource(self: *CompileStep) FileSource { + return .{ .generated = &self.output_dirname_source }; } /// Returns the generated import library. This function can only be called for libraries. pub fn getOutputLibSource(self: *CompileStep) FileSource { assert(self.kind == .lib); - return FileSource{ .generated = &self.output_lib_path_source }; + return .{ .generated = &self.output_lib_path_source }; } /// Returns the generated header file. @@ -928,14 +934,14 @@ pub fn getOutputLibSource(self: *CompileStep) FileSource { pub fn getOutputHSource(self: *CompileStep) FileSource { assert(self.kind != .exe and self.kind != .test_exe and self.kind != .@"test"); assert(self.emit_h); - return FileSource{ .generated = &self.output_h_path_source }; + return .{ .generated = &self.output_h_path_source }; } /// Returns the generated PDB file. This function can only be called for Windows and UEFI. pub fn getOutputPdbSource(self: *CompileStep) FileSource { // TODO: Is this right? Isn't PDB for *any* PE/COFF file? assert(self.target.isWindows() or self.target.isUefi()); - return FileSource{ .generated = &self.output_pdb_path_source }; + return .{ .generated = &self.output_pdb_path_source }; } pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void { @@ -989,17 +995,32 @@ pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) voi pub fn addLibraryPath(self: *CompileStep, path: []const u8) void { const b = self.step.owner; - self.lib_paths.append(b.dupe(path)) catch @panic("OOM"); + self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); +} + +pub fn addLibraryPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { + self.lib_paths.append(directory_source) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); } pub fn addRPath(self: *CompileStep, path: []const u8) void { const b = self.step.owner; - self.rpaths.append(b.dupe(path)) catch @panic("OOM"); + self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); +} + +pub fn addRPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { + self.rpaths.append(directory_source) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); } pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void { const b = self.step.owner; - self.framework_dirs.append(b.dupe(dir_path)) catch @panic("OOM"); + self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM"); +} + +pub fn addFrameworkPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { + self.framework_dirs.append(directory_source) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); } /// Adds a module to be used with `@import` and exposing it in the current @@ -1065,7 +1086,7 @@ pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); - try self.lib_paths.append(lib_path); + try self.lib_paths.append(.{ .path = lib_path }); self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" }); }, @@ -1768,30 +1789,32 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } } - for (self.lib_paths.items) |lib_path| { - try zig_args.append("-L"); - try zig_args.append(lib_path); - } - - for (self.rpaths.items) |rpath| { - try zig_args.append("-rpath"); - try zig_args.append(rpath); - } - for (self.c_macros.items) |c_macro| { try zig_args.append("-D"); try zig_args.append(c_macro); } - for (self.framework_dirs.items) |dir| { + try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len); + for (self.lib_paths.items) |lib_path| { + zig_args.appendAssumeCapacity("-L"); + zig_args.appendAssumeCapacity(lib_path.getPath2(b, step)); + } + + try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len); + for (self.rpaths.items) |rpath| { + zig_args.appendAssumeCapacity("-rpath"); + zig_args.appendAssumeCapacity(rpath.getPath2(b, step)); + } + + for (self.framework_dirs.items) |directory_source| { if (b.sysroot != null) { try zig_args.append("-iframeworkwithsysroot"); } else { try zig_args.append("-iframework"); } - try zig_args.append(dir); + try zig_args.append(directory_source.getPath2(b, step)); try zig_args.append("-F"); - try zig_args.append(dir); + try zig_args.append(directory_source.getPath2(b, step)); } { @@ -1954,6 +1977,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { // Update generated files if (self.output_dir != null) { + self.output_dirname_source.path = self.output_dir.?; + self.output_path_source.path = b.pathJoin( &.{ self.output_dir.?, self.out_filename }, ); From 1142e0534351a57b81bd4a127f29745c62cc59c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 20:23:11 -0700 Subject: [PATCH 219/294] re-enable macho linker tests --- build.zig | 2 +- test/link.zig | 213 +++++++++----------- test/link/macho/bugs/13056/build.zig | 18 +- test/link/macho/bugs/13457/build.zig | 18 +- test/link/macho/dead_strip/build.zig | 13 +- test/link/macho/dead_strip_dylibs/build.zig | 30 ++- test/link/macho/dylib/build.zig | 29 ++- test/link/macho/empty/build.zig | 18 +- test/link/macho/entry/build.zig | 13 +- test/link/macho/headerpad/build.zig | 14 +- test/link/macho/linksection/build.zig | 18 +- test/link/macho/needed_framework/build.zig | 14 +- test/link/macho/needed_library/build.zig | 23 ++- test/link/macho/objc/build.zig | 13 +- test/link/macho/objcpp/build.zig | 13 +- test/link/macho/pagezero/build.zig | 12 +- test/link/macho/search_strategy/build.zig | 43 ++-- test/link/macho/stack_size/build.zig | 18 +- test/link/macho/strict_validation/build.zig | 18 +- test/link/macho/tls/build.zig | 14 +- test/link/macho/unwind_info/build.zig | 25 ++- test/link/macho/uuid/build.zig | 7 +- test/link/macho/weak_framework/build.zig | 14 +- test/link/macho/weak_library/build.zig | 22 +- test/tests.zig | 18 +- 25 files changed, 396 insertions(+), 244 deletions(-) diff --git a/build.zig b/build.zig index 83aabb6c41..b181eaefae 100644 --- a/build.zig +++ b/build.zig @@ -454,7 +454,7 @@ pub fn build(b: *std.Build) !void { // enable_symlinks_windows, //)); test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); - test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); + test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); diff --git a/test/link.zig b/test/link.zig index ae970beaba..fb18e27c22 100644 --- a/test/link.zig +++ b/test/link.zig @@ -76,122 +76,103 @@ pub const cases = [_]Case{ }, // Mach-O Cases - // cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{ - // .build_modes = false, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/dylib/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/empty/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/entry/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/linksection/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/objc/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ - // .build_modes = false, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/tls/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/unwind_info/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/uuid/build.zig", .{ - // .build_modes = false, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ - // .build_modes = true, - // .requires_symlinks = true, - // }); - // - // cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ - // .build_modes = true, - // .requires_macos_sdk = true, - // .requires_symlinks = true, - // }); + .{ + .build_root = "test/link/macho/bugs/13056", + .import = @import("link/macho/bugs/13056/build.zig"), + }, + .{ + .build_root = "test/link/macho/bugs/13457", + .import = @import("link/macho/bugs/13457/build.zig"), + }, + .{ + .build_root = "test/link/macho/bugs/13457", + .import = @import("link/macho/bugs/13457/build.zig"), + }, + .{ + .build_root = "test/link/macho/dead_strip", + .import = @import("link/macho/dead_strip/build.zig"), + }, + .{ + .build_root = "test/link/macho/dead_strip_dylibs", + .import = @import("link/macho/dead_strip_dylibs/build.zig"), + }, + .{ + .build_root = "test/link/macho/dylib", + .import = @import("link/macho/dylib/build.zig"), + }, + .{ + .build_root = "test/link/macho/empty", + .import = @import("link/macho/empty/build.zig"), + }, + .{ + .build_root = "test/link/macho/entry", + .import = @import("link/macho/entry/build.zig"), + }, + .{ + .build_root = "test/link/macho/headerpad", + .import = @import("link/macho/headerpad/build.zig"), + }, + .{ + .build_root = "test/link/macho/linksection", + .import = @import("link/macho/linksection/build.zig"), + }, + .{ + .build_root = "test/link/macho/needed_framework", + .import = @import("link/macho/needed_framework/build.zig"), + }, + .{ + .build_root = "test/link/macho/needed_library", + .import = @import("link/macho/needed_library/build.zig"), + }, + .{ + .build_root = "test/link/macho/objc", + .import = @import("link/macho/objc/build.zig"), + }, + .{ + .build_root = "test/link/macho/objcpp", + .import = @import("link/macho/objcpp/build.zig"), + }, + .{ + .build_root = "test/link/macho/pagezero", + .import = @import("link/macho/pagezero/build.zig"), + }, + .{ + .build_root = "test/link/macho/search_strategy", + .import = @import("link/macho/search_strategy/build.zig"), + }, + .{ + .build_root = "test/link/macho/stack_size", + .import = @import("link/macho/stack_size/build.zig"), + }, + .{ + .build_root = "test/link/macho/strict_validation", + .import = @import("link/macho/strict_validation/build.zig"), + }, + .{ + .build_root = "test/link/macho/tls", + .import = @import("link/macho/tls/build.zig"), + }, + .{ + .build_root = "test/link/macho/unwind_info", + .import = @import("link/macho/unwind_info/build.zig"), + }, + // TODO: re-enable this test. It currently has some incompatibilities with + // the new build system API. In particular, it depends on installing the build + // artifacts, which should be unnecessary, and it has a custom build step that + // prints directly to stderr instead of failing the step with an error message. + //.{ + // .build_root = "test/link/macho/uuid", + // .import = @import("link/macho/uuid/build.zig"), + //}, + + .{ + .build_root = "test/link/macho/weak_library", + .import = @import("link/macho/weak_library/build.zig"), + }, + .{ + .build_root = "test/link/macho/weak_framework", + .import = @import("link/macho/weak_framework/build.zig"), + }, }; const std = @import("std"); diff --git a/test/link/macho/bugs/13056/build.zig b/test/link/macho/bugs/13056/build.zig index 662fd25c92..db7c129cbf 100644 --- a/test/link/macho/bugs/13056/build.zig +++ b/test/link/macho/bugs/13056/build.zig @@ -1,20 +1,28 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); +pub const requires_macos_sdk = true; +pub const requires_symlinks = true; +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const target_info = std.zig.system.NativeTargetInfo.detect(target) catch unreachable; const sdk = std.zig.system.darwin.getDarwinSDK(b.allocator, target_info.target) orelse @panic("macOS SDK is required to run the test"); - const test_step = b.step("test", "Test the program"); - const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, }); - b.default_step.dependOn(&exe.step); exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include" }) catch unreachable); exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include/c++/v1" }) catch unreachable); exe.addCSourceFile("test.cpp", &.{ diff --git a/test/link/macho/bugs/13457/build.zig b/test/link/macho/bugs/13457/build.zig index 7ac1435015..89096bba38 100644 --- a/test/link/macho/bugs/13457/build.zig +++ b/test/link/macho/bugs/13457/build.zig @@ -1,10 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test the program"); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const exe = b.addExecutable(.{ .name = "test", @@ -16,5 +25,6 @@ pub fn build(b: *std.Build) void { const run = b.addRunArtifact(exe); run.skip_foreign_checks = true; run.expectStdOutEqual(""); + test_step.dependOn(&run.step); } diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig index d82c81edca..4131d7baf9 100644 --- a/test/link/macho/dead_strip/build.zig +++ b/test/link/macho/dead_strip/build.zig @@ -1,15 +1,17 @@ const std = @import("std"); +pub const requires_symlinks = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const optimize: std.builtin.OptimizeMode = .Debug; const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); + b.default_step = test_step; { // Without -dead_strip, we expect `iAmUnused` symbol present - const exe = createScenario(b, optimize, target); + const exe = createScenario(b, optimize, target, "no-gc"); const check = exe.checkObject(.macho); check.checkInSymtab(); @@ -22,7 +24,7 @@ pub fn build(b: *std.Build) void { { // With -dead_strip, no `iAmUnused` symbol should be present - const exe = createScenario(b, optimize, target); + const exe = createScenario(b, optimize, target, "yes-gc"); exe.link_gc_sections = true; const check = exe.checkObject(.macho); @@ -39,9 +41,10 @@ fn createScenario( b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, + name: []const u8, ) *std.Build.CompileStep { const exe = b.addExecutable(.{ - .name = "test", + .name = name, .optimize = optimize, .target = target, }); diff --git a/test/link/macho/dead_strip_dylibs/build.zig b/test/link/macho/dead_strip_dylibs/build.zig index af2f5cf0dc..4a4dda7b7f 100644 --- a/test/link/macho/dead_strip_dylibs/build.zig +++ b/test/link/macho/dead_strip_dylibs/build.zig @@ -1,14 +1,22 @@ const std = @import("std"); +pub const requires_macos_sdk = true; +pub const requires_symlinks = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { { // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable - const exe = createScenario(b, optimize); + const exe = createScenario(b, optimize, "no-dead-strip"); const check = exe.checkObject(.macho); check.checkStart("cmd LOAD_DYLIB"); @@ -25,18 +33,22 @@ pub fn build(b: *std.Build) void { { // With -dead_strip_dylibs, we should include liba.dylib as it's unreachable - const exe = createScenario(b, optimize); + const exe = createScenario(b, optimize, "yes-dead-strip"); exe.dead_strip_dylibs = true; - const run_cmd = exe.run(); - run_cmd.expected_term = .{ .Exited = @bitCast(u8, @as(i8, -2)) }; // should fail + const run_cmd = b.addRunArtifact(exe); + run_cmd.expectExitCode(@bitCast(u8, @as(i8, -2))); // should fail test_step.dependOn(&run_cmd.step); } } -fn createScenario(b: *std.Build, optimize: std.builtin.OptimizeMode) *std.Build.CompileStep { +fn createScenario( + b: *std.Build, + optimize: std.builtin.OptimizeMode, + name: []const u8, +) *std.Build.CompileStep { const exe = b.addExecutable(.{ - .name = "test", + .name = name, .optimize = optimize, }); exe.addCSourceFile("main.c", &[0][]const u8{}); diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index 7a1e2d862c..bf4a49f50b 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const dylib = b.addSharedLibrary(.{ .name = "a", @@ -15,7 +23,6 @@ pub fn build(b: *std.Build) void { }); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); - dylib.install(); const check_dylib = dylib.checkObject(.macho); check_dylib.checkStart("cmd ID_DYLIB"); @@ -33,9 +40,9 @@ pub fn build(b: *std.Build) void { }); exe.addCSourceFile("main.c", &.{}); exe.linkSystemLibrary("a"); + exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource()); + exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); exe.linkLibC(); - exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); - exe.addRPath(b.pathFromRoot("zig-out/lib")); const check_exe = exe.checkObject(.macho); check_exe.checkStart("cmd LOAD_DYLIB"); @@ -45,10 +52,12 @@ pub fn build(b: *std.Build) void { check_exe.checkNext("compatibility version 10000"); check_exe.checkStart("cmd RPATH"); - check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable); + // TODO check this (perhaps with `checkNextFileSource(dylib.getOutputDirectorySource())`) + //check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{ + // b.pathFromRoot("zig-out/lib"), + //}) catch unreachable); const run = check_exe.runAndCompare(); - run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); } diff --git a/test/link/macho/empty/build.zig b/test/link/macho/empty/build.zig index 12eb67d9c8..9933746d53 100644 --- a/test/link/macho/empty/build.zig +++ b/test/link/macho/empty/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const exe = b.addExecutable(.{ .name = "test", diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 4504da9c6c..b2c41ef6dc 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -1,11 +1,18 @@ const std = @import("std"); +pub const requires_symlinks = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const exe = b.addExecutable(.{ .name = "main", .optimize = optimize, diff --git a/test/link/macho/headerpad/build.zig b/test/link/macho/headerpad/build.zig index 3ef17573f8..07d30ee6f2 100644 --- a/test/link/macho/headerpad/build.zig +++ b/test/link/macho/headerpad/build.zig @@ -1,12 +1,20 @@ const std = @import("std"); const builtin = @import("builtin"); +pub const requires_symlinks = true; +pub const requires_macos_sdk = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { { // Test -headerpad_max_install_names const exe = simpleExe(b, optimize); diff --git a/test/link/macho/linksection/build.zig b/test/link/macho/linksection/build.zig index 227d4eeb63..37e0b7cbef 100644 --- a/test/link/macho/linksection/build.zig +++ b/test/link/macho/linksection/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = std.zig.CrossTarget{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target = std.zig.CrossTarget{ .os_tag = .macos }; const obj = b.addObject(.{ .name = "test", diff --git a/test/link/macho/needed_framework/build.zig b/test/link/macho/needed_framework/build.zig index 8b6e3dd87f..cd53a5d212 100644 --- a/test/link/macho/needed_framework/build.zig +++ b/test/link/macho/needed_framework/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); +pub const requires_symlinks = true; +pub const requires_macos_sdk = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { // -dead_strip_dylibs // -needed_framework Cocoa const exe = b.addExecutable(.{ diff --git a/test/link/macho/needed_library/build.zig b/test/link/macho/needed_library/build.zig index e8a58d5381..6e24a12c65 100644 --- a/test/link/macho/needed_library/build.zig +++ b/test/link/macho/needed_library/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const dylib = b.addSharedLibrary(.{ .name = "a", @@ -15,7 +23,6 @@ pub fn build(b: *std.Build) void { }); dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); - dylib.install(); // -dead_strip_dylibs // -needed-la @@ -27,8 +34,8 @@ pub fn build(b: *std.Build) void { exe.addCSourceFile("main.c", &[0][]const u8{}); exe.linkLibC(); exe.linkSystemLibraryNeeded("a"); - exe.addLibraryPath(b.pathFromRoot("zig-out/lib")); - exe.addRPath(b.pathFromRoot("zig-out/lib")); + exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource()); + exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); exe.dead_strip_dylibs = true; const check = exe.checkObject(.macho); diff --git a/test/link/macho/objc/build.zig b/test/link/macho/objc/build.zig index 9398e28a80..4cd12f786f 100644 --- a/test/link/macho/objc/build.zig +++ b/test/link/macho/objc/build.zig @@ -1,10 +1,19 @@ const std = @import("std"); +pub const requires_symlinks = true; +pub const requires_macos_sdk = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test the program"); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, diff --git a/test/link/macho/objcpp/build.zig b/test/link/macho/objcpp/build.zig index 2a3459be50..06876247a9 100644 --- a/test/link/macho/objcpp/build.zig +++ b/test/link/macho/objcpp/build.zig @@ -1,10 +1,19 @@ const std = @import("std"); +pub const requires_symlinks = true; +pub const requires_macos_sdk = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test the program"); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index 0a8471b919..e7b002a389 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -1,11 +1,13 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; { const exe = b.addExecutable(.{ diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index a8a4167865..ae1037c2d7 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -1,35 +1,41 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; { // -search_dylibs_first - const exe = createScenario(b, optimize, target); + const exe = createScenario(b, optimize, target, "search_dylibs_first"); exe.search_strategy = .dylibs_first; const check = exe.checkObject(.macho); check.checkStart("cmd LOAD_DYLIB"); - check.checkNext("name @rpath/liba.dylib"); + check.checkNext("name @rpath/libsearch_dylibs_first.dylib"); const run = check.runAndCompare(); - run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); } { // -search_paths_first - const exe = createScenario(b, optimize, target); + const exe = createScenario(b, optimize, target, "search_paths_first"); exe.search_strategy = .paths_first; const run = b.addRunArtifact(exe); run.skip_foreign_checks = true; - run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); test_step.dependOn(&run.step); } @@ -39,9 +45,10 @@ fn createScenario( b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, + name: []const u8, ) *std.Build.CompileStep { const static = b.addStaticLibrary(.{ - .name = "a", + .name = name, .optimize = optimize, .target = target, }); @@ -50,10 +57,9 @@ fn createScenario( static.override_dest_dir = std.Build.InstallDir{ .custom = "static", }; - static.install(); const dylib = b.addSharedLibrary(.{ - .name = "a", + .name = name, .version = .{ .major = 1, .minor = 0 }, .optimize = optimize, .target = target, @@ -63,18 +69,17 @@ fn createScenario( dylib.override_dest_dir = std.Build.InstallDir{ .custom = "dynamic", }; - dylib.install(); const exe = b.addExecutable(.{ - .name = "main", + .name = name, .optimize = optimize, .target = target, }); exe.addCSourceFile("main.c", &.{}); - exe.linkSystemLibraryName("a"); + exe.linkSystemLibraryName(name); exe.linkLibC(); - exe.addLibraryPath(b.pathFromRoot("zig-out/static")); - exe.addLibraryPath(b.pathFromRoot("zig-out/dynamic")); - exe.addRPath(b.pathFromRoot("zig-out/dynamic")); + exe.addLibraryPathDirectorySource(static.getOutputDirectorySource()); + exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource()); + exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); return exe; } diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 874f53fbff..51efed4c34 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const exe = b.addExecutable(.{ .name = "main", diff --git a/test/link/macho/strict_validation/build.zig b/test/link/macho/strict_validation/build.zig index 408076657b..a95793234b 100644 --- a/test/link/macho/strict_validation/build.zig +++ b/test/link/macho/strict_validation/build.zig @@ -1,12 +1,20 @@ const std = @import("std"); const builtin = @import("builtin"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const exe = b.addExecutable(.{ .name = "main", diff --git a/test/link/macho/tls/build.zig b/test/link/macho/tls/build.zig index c77588cb5d..e91f40bd59 100644 --- a/test/link/macho/tls/build.zig +++ b/test/link/macho/tls/build.zig @@ -1,7 +1,18 @@ const std = @import("std"); +pub const requires_symlinks = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const lib = b.addSharedLibrary(.{ @@ -21,6 +32,5 @@ pub fn build(b: *std.Build) void { test_exe.linkLibrary(lib); test_exe.linkLibC(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&test_exe.step); } diff --git a/test/link/macho/unwind_info/build.zig b/test/link/macho/unwind_info/build.zig index 408f762f5d..b9bcea8358 100644 --- a/test/link/macho/unwind_info/build.zig +++ b/test/link/macho/unwind_info/build.zig @@ -1,14 +1,23 @@ const std = @import("std"); const builtin = @import("builtin"); +pub const requires_symlinks = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const target: std.zig.CrossTarget = .{ .os_tag = .macos }; - const test_step = b.step("test", "Test the program"); - - testUnwindInfo(b, test_step, optimize, target, false); - testUnwindInfo(b, test_step, optimize, target, true); + testUnwindInfo(b, test_step, optimize, target, false, "no-dead-strip"); + testUnwindInfo(b, test_step, optimize, target, true, "yes-dead-strip"); } fn testUnwindInfo( @@ -17,8 +26,9 @@ fn testUnwindInfo( optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, dead_strip: bool, + name: []const u8, ) void { - const exe = createScenario(b, optimize, target); + const exe = createScenario(b, optimize, target, name); exe.link_gc_sections = dead_strip; const check = exe.checkObject(.macho); @@ -54,9 +64,10 @@ fn createScenario( b: *std.Build, optimize: std.builtin.OptimizeMode, target: std.zig.CrossTarget, + name: []const u8, ) *std.Build.CompileStep { const exe = b.addExecutable(.{ - .name = "test", + .name = name, .optimize = optimize, .target = target, }); diff --git a/test/link/macho/uuid/build.zig b/test/link/macho/uuid/build.zig index 6ff45e1175..df58aeacb7 100644 --- a/test/link/macho/uuid/build.zig +++ b/test/link/macho/uuid/build.zig @@ -3,11 +3,14 @@ const CompileStep = std.Build.CompileStep; const FileSource = std.Build.FileSource; const Step = std.Build.Step; +pub const requires_symlinks = true; + pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); + b.default_step = test_step; - // We force cross-compilation to ensure we always pick a generic CPU with constant set of CPU features. + // We force cross-compilation to ensure we always pick a generic CPU with + // constant set of CPU features. const aarch64_macos = std.zig.CrossTarget{ .cpu_arch = .aarch64, .os_tag = .macos, diff --git a/test/link/macho/weak_framework/build.zig b/test/link/macho/weak_framework/build.zig index ca28458d77..a4e1d6e5d9 100644 --- a/test/link/macho/weak_framework/build.zig +++ b/test/link/macho/weak_framework/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); +pub const requires_symlinks = true; +pub const requires_macos_sdk = true; + pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig index de5aa45e30..ac265689e3 100644 --- a/test/link/macho/weak_library/build.zig +++ b/test/link/macho/weak_library/build.zig @@ -1,11 +1,19 @@ const std = @import("std"); -pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target: std.zig.CrossTarget = .{ .os_tag = .macos }; +pub const requires_symlinks = true; - const test_step = b.step("test", "Test the program"); - test_step.dependOn(b.getInstallStep()); +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{ .os_tag = .macos }; const dylib = b.addSharedLibrary(.{ .name = "a", @@ -25,8 +33,8 @@ pub fn build(b: *std.Build) void { exe.addCSourceFile("main.c", &[0][]const u8{}); exe.linkLibC(); exe.linkSystemLibraryWeak("a"); - exe.addLibraryPath(b.pathFromRoot("zig-out/lib")); - exe.addRPath(b.pathFromRoot("zig-out/lib")); + exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource()); + exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); const check = exe.checkObject(.macho); check.checkStart("cmd LOAD_WEAK_DYLIB"); diff --git a/test/tests.zig b/test/tests.zig index a23870b79e..a1945246a3 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -590,23 +590,25 @@ pub fn addStandaloneTests( pub fn addLinkTests( b: *std.Build, - test_filter: ?[]const u8, - optimize_modes: []const OptimizeMode, enable_macos_sdk: bool, omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { - _ = test_filter; - _ = optimize_modes; - _ = enable_macos_sdk; - _ = enable_symlinks_windows; - const step = b.step("test-link", "Run the linker tests"); + const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows; inline for (link.cases) |link_test| { const requires_stage2 = @hasDecl(link_test.import, "requires_stage2") and link_test.import.requires_stage2; - if (!requires_stage2 or !omit_stage2) { + const requires_symlinks = @hasDecl(link_test.import, "requires_symlinks") and + link_test.import.requires_symlinks; + const requires_macos_sdk = @hasDecl(link_test.import, "requires_macos_sdk") and + link_test.import.requires_macos_sdk; + const bad = + (requires_stage2 and omit_stage2) or + (requires_symlinks and omit_symlinks) or + (requires_macos_sdk and !enable_macos_sdk); + if (!bad) { const dep = b.anonymousDependency(link_test.build_root, link_test.import, .{}); const dep_step = dep.builder.default_step; assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); From 15c4fae1c93d970d02a9eede96371fb7c053837f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Mar 2023 22:40:53 -0700 Subject: [PATCH 220/294] re-enable the simple standalone tests --- build.zig | 19 ++-- test/link.zig | 3 - test/src/Standalone.zig | 141 ----------------------- test/standalone.zig | 243 ++++++++++++++++++++++------------------ test/tests.zig | 40 ++++--- 5 files changed, 167 insertions(+), 279 deletions(-) delete mode 100644 test/src/Standalone.zig diff --git a/build.zig b/build.zig index b181eaefae..c64015fe88 100644 --- a/build.zig +++ b/build.zig @@ -443,16 +443,15 @@ pub fn build(b: *std.Build) !void { })); test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); - //test_step.dependOn(tests.addStandaloneTests( - // b, - // test_filter, - // optimization_modes, - // skip_non_native, - // enable_macos_sdk, - // target, - // skip_stage2_tests, - // enable_symlinks_windows, - //)); + test_step.dependOn(tests.addStandaloneTests( + b, + test_filter, + optimization_modes, + skip_non_native, + enable_macos_sdk, + skip_stage2_tests, + enable_symlinks_windows, + )); test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); diff --git a/test/link.zig b/test/link.zig index fb18e27c22..610cbd4a3d 100644 --- a/test/link.zig +++ b/test/link.zig @@ -174,6 +174,3 @@ pub const cases = [_]Case{ .import = @import("link/macho/weak_framework/build.zig"), }, }; - -const std = @import("std"); -const builtin = @import("builtin"); diff --git a/test/src/Standalone.zig b/test/src/Standalone.zig deleted file mode 100644 index c07bb511c5..0000000000 --- a/test/src/Standalone.zig +++ /dev/null @@ -1,141 +0,0 @@ -b: *std.Build, -step: *Step, -test_index: usize, -test_filter: ?[]const u8, -optimize_modes: []const OptimizeMode, -skip_non_native: bool, -enable_macos_sdk: bool, -target: std.zig.CrossTarget, -omit_stage2: bool, -enable_darling: bool = false, -enable_qemu: bool = false, -enable_rosetta: bool = false, -enable_wasmtime: bool = false, -enable_wine: bool = false, -enable_symlinks_windows: bool, - -pub fn addC(self: *Standalone, root_src: []const u8) void { - self.addAllArgs(root_src, true); -} - -pub fn add(self: *Standalone, root_src: []const u8) void { - self.addAllArgs(root_src, false); -} - -pub fn addBuildFile(self: *Standalone, build_file: []const u8, features: struct { - build_modes: bool = false, - cross_targets: bool = false, - requires_macos_sdk: bool = false, - requires_stage2: bool = false, - use_emulation: bool = false, - requires_symlinks: bool = false, - extra_argv: []const []const u8 = &.{}, -}) void { - const b = self.b; - - if (features.requires_macos_sdk and !self.enable_macos_sdk) return; - if (features.requires_stage2 and self.omit_stage2) return; - if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return; - - const annotated_case_name = b.fmt("build {s}", .{build_file}); - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) return; - } - - var zig_args = ArrayList([]const u8).init(b.allocator); - const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable; - zig_args.append(rel_zig_exe) catch unreachable; - zig_args.append("build") catch unreachable; - - // TODO: fix the various non-concurrency-safe issues in zig's standalone tests, - // and then remove this! - zig_args.append("-j1") catch @panic("OOM"); - - zig_args.append("--build-file") catch unreachable; - zig_args.append(b.pathFromRoot(build_file)) catch unreachable; - - zig_args.appendSlice(features.extra_argv) catch unreachable; - - zig_args.append("test") catch unreachable; - - if (b.verbose) { - zig_args.append("--verbose") catch unreachable; - } - - if (features.cross_targets and !self.target.isNative()) { - const target_triple = self.target.zigTriple(b.allocator) catch unreachable; - const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; - zig_args.append(target_arg) catch unreachable; - } - - if (features.use_emulation) { - if (self.enable_darling) { - zig_args.append("-fdarling") catch unreachable; - } - if (self.enable_qemu) { - zig_args.append("-fqemu") catch unreachable; - } - if (self.enable_rosetta) { - zig_args.append("-frosetta") catch unreachable; - } - if (self.enable_wasmtime) { - zig_args.append("-fwasmtime") catch unreachable; - } - if (self.enable_wine) { - zig_args.append("-fwine") catch unreachable; - } - } - - const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug}; - for (optimize_modes) |optimize_mode| { - const arg = switch (optimize_mode) { - .Debug => "", - .ReleaseFast => "-Doptimize=ReleaseFast", - .ReleaseSafe => "-Doptimize=ReleaseSafe", - .ReleaseSmall => "-Doptimize=ReleaseSmall", - }; - const zig_args_base_len = zig_args.items.len; - if (arg.len > 0) - zig_args.append(arg) catch unreachable; - defer zig_args.resize(zig_args_base_len) catch unreachable; - - const run_cmd = b.addSystemCommand(zig_args.items); - self.step.dependOn(&run_cmd.step); - } -} - -pub fn addAllArgs(self: *Standalone, root_src: []const u8, link_libc: bool) void { - const b = self.b; - - for (self.optimize_modes) |optimize| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{ - root_src, - @tagName(optimize), - }) catch unreachable; - if (self.test_filter) |filter| { - if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; - } - - const exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = root_src }, - .optimize = optimize, - .target = .{}, - }); - if (link_libc) { - exe.linkSystemLibrary("c"); - } - - self.step.dependOn(&exe.step); - } -} - -const Standalone = @This(); -const std = @import("std"); -const builtin = @import("builtin"); -const Step = std.Build.Step; -const OptimizeMode = std.builtin.OptimizeMode; -const fmt = std.fmt; -const mem = std.mem; -const ArrayList = std.ArrayList; -const fs = std.fs; diff --git a/test/standalone.zig b/test/standalone.zig index 7aa4d81f97..fa67d6a14d 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -1,117 +1,144 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const tests = @import("tests.zig"); +pub const SimpleCase = struct { + src_path: []const u8, + link_libc: bool = false, + all_modes: bool = false, + target: std.zig.CrossTarget = .{}, +}; -pub fn addCases(cases: *tests.StandaloneContext) void { - cases.add("test/standalone/hello_world/hello.zig"); - cases.addC("test/standalone/hello_world/hello_libc.zig"); +pub const BuildCase = struct { + build_root: []const u8, + import: type, +}; - cases.addBuildFile("test/standalone/options/build.zig", .{ - .extra_argv = &.{ - "-Dbool_true", - "-Dbool_false=false", - "-Dint=1234", - "-De=two", - "-Dstring=hello", - }, - }); +pub const simple_cases = [_]SimpleCase{ + .{ + .src_path = "test/standalone/hello_world/hello.zig", + .all_modes = true, + }, + .{ + .src_path = "test/standalone/hello_world/hello_libc.zig", + .link_libc = true, + .all_modes = true, + }, + .{ + .src_path = "test/standalone/cat/main.zig", + }, + // https://github.com/ziglang/zig/issues/6025 + //.{ + // .src_path = "test/standalone/issue_9693/main.zig", + //}, - cases.add("test/standalone/cat/main.zig"); - if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/6025 - cases.add("test/standalone/issue_9693/main.zig"); - } - cases.add("test/standalone/issue_12471/main.zig"); - cases.add("test/standalone/guess_number/main.zig"); - cases.add("test/standalone/main_return_error/error_u8.zig"); - cases.add("test/standalone/main_return_error/error_u8_non_zero.zig"); - cases.add("test/standalone/noreturn_call/inline.zig"); - cases.add("test/standalone/noreturn_call/as_arg.zig"); - cases.addBuildFile("test/standalone/test_runner_path/build.zig", .{ .requires_stage2 = true }); - cases.addBuildFile("test/standalone/issue_13970/build.zig", .{}); - cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); - cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); - cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); - cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ - .build_modes = true, - .cross_targets = true, - }); - cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); - cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_5825/build.zig", .{}); - cases.addBuildFile("test/standalone/pkg_import/build.zig", .{}); - cases.addBuildFile("test/standalone/use_alias/build.zig", .{}); - cases.addBuildFile("test/standalone/brace_expansion/build.zig", .{}); - if (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64) { - // https://github.com/ziglang/zig/issues/13685 - cases.addBuildFile("test/standalone/empty_env/build.zig", .{}); - } - cases.addBuildFile("test/standalone/issue_7030/build.zig", .{}); - cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{}); - if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12194 - cases.addBuildFile("test/standalone/issue_9812/build.zig", .{}); - } - if (builtin.os.tag != .windows) { - // https://github.com/ziglang/zig/issues/12419 - cases.addBuildFile("test/standalone/issue_11595/build.zig", .{}); - } + .{ .src_path = "test/standalone/issue_12471/main.zig" }, + .{ .src_path = "test/standalone/guess_number/main.zig" }, + .{ .src_path = "test/standalone/main_return_error/error_u8.zig" }, + .{ .src_path = "test/standalone/main_return_error/error_u8_non_zero.zig" }, + .{ .src_path = "test/standalone/noreturn_call/inline.zig" }, + .{ .src_path = "test/standalone/noreturn_call/as_arg.zig" }, - if (builtin.os.tag != .wasi and - // https://github.com/ziglang/zig/issues/13550 - (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and - // https://github.com/ziglang/zig/issues/13686 - (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64)) - { - cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{}); - } - - if (builtin.os.tag == .windows) { - cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{}); - } - - cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ - .build_modes = true, - .cross_targets = true, - }); - - if (builtin.os.tag == .windows) { - cases.addC("test/standalone/issue_9402/main.zig"); - } - // Try to build and run a PIE executable. - if (builtin.os.tag == .linux) { - cases.addBuildFile("test/standalone/pie/build.zig", .{}); - } - cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); - if (std.os.have_sigpipe_support) { - cases.addBuildFile("test/standalone/sigpipe/build.zig", .{}); - } + .{ + .src_path = "test/standalone/issue_9402/main.zig", + .target = .{ .os_tag = .windows }, + .link_libc = true, + }, // Ensure the development tools are buildable. Alphabetically sorted. // No need to build `tools/spirv/grammar.zig`. - cases.add("tools/extract-grammar.zig"); - cases.add("tools/gen_outline_atomics.zig"); - cases.add("tools/gen_spirv_spec.zig"); - cases.add("tools/gen_stubs.zig"); - cases.add("tools/generate_linux_syscalls.zig"); - cases.add("tools/process_headers.zig"); - cases.add("tools/update-license-headers.zig"); - cases.add("tools/update-linux-headers.zig"); - cases.add("tools/update_clang_options.zig"); - cases.add("tools/update_cpu_features.zig"); - cases.add("tools/update_glibc.zig"); - cases.add("tools/update_spirv_features.zig"); + .{ .src_path = "tools/extract-grammar.zig" }, + .{ .src_path = "tools/gen_outline_atomics.zig" }, + .{ .src_path = "tools/gen_spirv_spec.zig" }, + .{ .src_path = "tools/gen_stubs.zig" }, + .{ .src_path = "tools/generate_linux_syscalls.zig" }, + .{ .src_path = "tools/process_headers.zig" }, + .{ .src_path = "tools/update-license-headers.zig" }, + .{ .src_path = "tools/update-linux-headers.zig" }, + .{ .src_path = "tools/update_clang_options.zig" }, + .{ .src_path = "tools/update_cpu_features.zig" }, + .{ .src_path = "tools/update_glibc.zig" }, + .{ .src_path = "tools/update_spirv_features.zig" }, +}; - cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true }); - cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{}); - cases.addBuildFile("test/standalone/issue_12588/build.zig", .{}); - cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{}); - cases.addBuildFile("test/standalone/extern/build.zig", .{}); +pub const build_cases = [_]BuildCase{}; - cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_recursive/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_mutually_recursive/build.zig", .{}); - cases.addBuildFile("test/standalone/dep_shared_builtin/build.zig", .{}); -} +//pub fn addCases(cases: *tests.StandaloneContext) void { +// cases.addBuildFile("test/standalone/options/build.zig", .{ +// .extra_argv = &.{ +// "-Dbool_true", +// "-Dbool_false=false", +// "-Dint=1234", +// "-De=two", +// "-Dstring=hello", +// }, +// }); +// +// cases.addBuildFile("test/standalone/test_runner_path/build.zig", .{ .requires_stage2 = true }); +// cases.addBuildFile("test/standalone/issue_13970/build.zig", .{}); +// cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); +// cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); +// cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); +// cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ +// .build_modes = true, +// .cross_targets = true, +// }); +// cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); +// cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_5825/build.zig", .{}); +// cases.addBuildFile("test/standalone/pkg_import/build.zig", .{}); +// cases.addBuildFile("test/standalone/use_alias/build.zig", .{}); +// cases.addBuildFile("test/standalone/brace_expansion/build.zig", .{}); +// if (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64) { +// // https://github.com/ziglang/zig/issues/13685 +// cases.addBuildFile("test/standalone/empty_env/build.zig", .{}); +// } +// cases.addBuildFile("test/standalone/issue_7030/build.zig", .{}); +// cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{}); +// if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12194 +// cases.addBuildFile("test/standalone/issue_9812/build.zig", .{}); +// } +// if (builtin.os.tag != .windows) { +// // https://github.com/ziglang/zig/issues/12419 +// cases.addBuildFile("test/standalone/issue_11595/build.zig", .{}); +// } +// +// if (builtin.os.tag != .wasi and +// // https://github.com/ziglang/zig/issues/13550 +// (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and +// // https://github.com/ziglang/zig/issues/13686 +// (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64)) +// { +// cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{}); +// } +// +// if (builtin.os.tag == .windows) { +// cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{}); +// } +// +// cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ +// .build_modes = true, +// .cross_targets = true, +// }); +// +// // Try to build and run a PIE executable. +// if (builtin.os.tag == .linux) { +// cases.addBuildFile("test/standalone/pie/build.zig", .{}); +// } +// cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); +// if (std.os.have_sigpipe_support) { +// cases.addBuildFile("test/standalone/sigpipe/build.zig", .{}); +// } +// +// cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true }); +// cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{}); +// cases.addBuildFile("test/standalone/issue_12588/build.zig", .{}); +// cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{}); +// +// cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_recursive/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_mutually_recursive/build.zig", .{}); +// cases.addBuildFile("test/standalone/dep_shared_builtin/build.zig", .{}); +//} + +const std = @import("std"); diff --git a/test/tests.zig b/test/tests.zig index a1945246a3..038111136a 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -20,7 +20,6 @@ pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext; pub const CompareOutputContext = @import("src/CompareOutput.zig"); pub const StackTracesContext = @import("src/StackTrace.zig"); -pub const StandaloneContext = @import("src/Standalone.zig"); const TestTarget = struct { target: CrossTarget = @as(CrossTarget, .{}), @@ -565,27 +564,34 @@ pub fn addStandaloneTests( optimize_modes: []const OptimizeMode, skip_non_native: bool, enable_macos_sdk: bool, - target: std.zig.CrossTarget, omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { - const cases = b.allocator.create(StandaloneContext) catch @panic("OOM"); - cases.* = .{ - .b = b, - .step = b.step("test-standalone", "Run the standalone tests"), - .test_index = 0, - .test_filter = test_filter, - .optimize_modes = optimize_modes, - .skip_non_native = skip_non_native, - .enable_macos_sdk = enable_macos_sdk, - .target = target, - .omit_stage2 = omit_stage2, - .enable_symlinks_windows = enable_symlinks_windows, - }; + const step = b.step("test-standalone", "Run the standalone tests"); - standalone.addCases(cases); + _ = test_filter; + _ = skip_non_native; + _ = enable_macos_sdk; + _ = omit_stage2; + _ = enable_symlinks_windows; - return cases.step; + for (standalone.simple_cases) |case| { + for (optimize_modes) |optimize| { + if (!case.all_modes and optimize != .Debug) continue; + + const exe = b.addExecutable(.{ + .name = std.fs.path.stem(case.src_path), + .root_source_file = .{ .path = case.src_path }, + .optimize = optimize, + .target = case.target, + }); + if (case.link_libc) exe.linkLibC(); + + step.dependOn(&exe.step); + } + } + + return step; } pub fn addLinkTests( From 030742f1f73d0c9a237d7512d894e88b05ad53af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Mar 2023 00:16:16 -0700 Subject: [PATCH 221/294] re-enable standalone tests based on build.zig --- build.zig | 4 +- test/standalone.zig | 234 +++++++++++------- .../main.zig => brace_expansion.zig} | 4 +- test/standalone/brace_expansion/build.zig | 11 - test/standalone/c_compiler/build.zig | 41 ++- test/standalone/dep_diamond/build.zig | 6 +- .../dep_mutually_recursive/build.zig | 6 +- test/standalone/dep_recursive/build.zig | 6 +- test/standalone/dep_shared_builtin/build.zig | 6 +- test/standalone/dep_triangle/build.zig | 6 +- .../standalone/embed_generated_file/build.zig | 8 +- test/standalone/emit_asm_and_bin/build.zig | 4 +- test/standalone/empty_env/build.zig | 16 +- test/standalone/global_linkage/build.zig | 11 +- test/standalone/install_raw_hex/build.zig | 6 +- test/standalone/issue_11595/build.zig | 33 ++- test/standalone/issue_12588/build.zig | 8 +- test/standalone/issue_12706/build.zig | 30 +-- test/standalone/issue_13030/build.zig | 15 +- test/standalone/issue_13970/build.zig | 4 +- test/standalone/issue_339/build.zig | 11 +- test/standalone/issue_5825/build.zig | 6 +- .../{issue_7030/main.zig => issue_7030.zig} | 0 test/standalone/issue_7030/build.zig | 17 -- test/standalone/issue_794/build.zig | 6 +- test/standalone/issue_8550/build.zig | 7 +- test/standalone/issue_9812/build.zig | 7 +- .../standalone/load_dynamic_library/build.zig | 20 +- test/standalone/load_dynamic_library/main.zig | 6 +- test/standalone/main_pkg_path/build.zig | 4 +- test/standalone/mix_c_files/build.zig | 34 ++- test/standalone/mix_o_files/build.zig | 10 +- test/standalone/pie/build.zig | 15 +- test/standalone/pkg_import/build.zig | 6 +- test/standalone/shared_library/build.zig | 10 +- test/standalone/sigpipe/build.zig | 11 +- test/standalone/static_c_lib/build.zig | 6 +- test/standalone/test_runner_path/build.zig | 6 +- .../test_runner_path/test_runner.zig | 40 +-- test/standalone/use_alias/build.zig | 8 +- test/standalone/windows_spawn/build.zig | 17 +- test/tests.zig | 93 +++++-- 42 files changed, 459 insertions(+), 340 deletions(-) rename test/standalone/{brace_expansion/main.zig => brace_expansion.zig} (98%) delete mode 100644 test/standalone/brace_expansion/build.zig rename test/standalone/{issue_7030/main.zig => issue_7030.zig} (100%) delete mode 100644 test/standalone/issue_7030/build.zig diff --git a/build.zig b/build.zig index c64015fe88..ba12ae5ae0 100644 --- a/build.zig +++ b/build.zig @@ -445,9 +445,7 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addStandaloneTests( b, - test_filter, optimization_modes, - skip_non_native, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows, @@ -455,7 +453,7 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes)); - test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes)); + test_step.dependOn(tests.addCliTests(b)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); if (!skip_run_translated_c) { diff --git a/test/standalone.zig b/test/standalone.zig index fa67d6a14d..6e0adcaa00 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -3,6 +3,8 @@ pub const SimpleCase = struct { link_libc: bool = false, all_modes: bool = false, target: std.zig.CrossTarget = .{}, + is_test: bool = false, + is_exe: bool = true, }; pub const BuildCase = struct { @@ -27,6 +29,17 @@ pub const simple_cases = [_]SimpleCase{ //.{ // .src_path = "test/standalone/issue_9693/main.zig", //}, + .{ + .src_path = "test/standalone/brace_expansion.zig", + .is_test = true, + }, + .{ + .src_path = "test/standalone/issue_7030.zig", + .target = .{ + .cpu_arch = .wasm32, + .os_tag = .freestanding, + }, + }, .{ .src_path = "test/standalone/issue_12471/main.zig" }, .{ .src_path = "test/standalone/guess_number/main.zig" }, @@ -57,88 +70,143 @@ pub const simple_cases = [_]SimpleCase{ .{ .src_path = "tools/update_spirv_features.zig" }, }; -pub const build_cases = [_]BuildCase{}; - -//pub fn addCases(cases: *tests.StandaloneContext) void { -// cases.addBuildFile("test/standalone/options/build.zig", .{ -// .extra_argv = &.{ -// "-Dbool_true", -// "-Dbool_false=false", -// "-Dint=1234", -// "-De=two", -// "-Dstring=hello", -// }, -// }); -// -// cases.addBuildFile("test/standalone/test_runner_path/build.zig", .{ .requires_stage2 = true }); -// cases.addBuildFile("test/standalone/issue_13970/build.zig", .{}); -// cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); -// cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); -// cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); -// cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ -// .build_modes = true, -// .cross_targets = true, -// }); -// cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); -// cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); -// cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); -// cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); -// cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); -// cases.addBuildFile("test/standalone/issue_5825/build.zig", .{}); -// cases.addBuildFile("test/standalone/pkg_import/build.zig", .{}); -// cases.addBuildFile("test/standalone/use_alias/build.zig", .{}); -// cases.addBuildFile("test/standalone/brace_expansion/build.zig", .{}); -// if (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64) { -// // https://github.com/ziglang/zig/issues/13685 -// cases.addBuildFile("test/standalone/empty_env/build.zig", .{}); -// } -// cases.addBuildFile("test/standalone/issue_7030/build.zig", .{}); -// cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{}); -// if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12194 -// cases.addBuildFile("test/standalone/issue_9812/build.zig", .{}); -// } -// if (builtin.os.tag != .windows) { -// // https://github.com/ziglang/zig/issues/12419 -// cases.addBuildFile("test/standalone/issue_11595/build.zig", .{}); -// } -// -// if (builtin.os.tag != .wasi and -// // https://github.com/ziglang/zig/issues/13550 -// (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and -// // https://github.com/ziglang/zig/issues/13686 -// (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64)) -// { -// cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{}); -// } -// -// if (builtin.os.tag == .windows) { -// cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{}); -// } -// -// cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ -// .build_modes = true, -// .cross_targets = true, -// }); -// -// // Try to build and run a PIE executable. -// if (builtin.os.tag == .linux) { -// cases.addBuildFile("test/standalone/pie/build.zig", .{}); -// } -// cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); -// if (std.os.have_sigpipe_support) { -// cases.addBuildFile("test/standalone/sigpipe/build.zig", .{}); -// } -// -// cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true }); -// cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{}); -// cases.addBuildFile("test/standalone/issue_12588/build.zig", .{}); -// cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{}); -// -// cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{}); -// cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{}); -// cases.addBuildFile("test/standalone/dep_recursive/build.zig", .{}); -// cases.addBuildFile("test/standalone/dep_mutually_recursive/build.zig", .{}); -// cases.addBuildFile("test/standalone/dep_shared_builtin/build.zig", .{}); -//} +pub const build_cases = [_]BuildCase{ + .{ + .build_root = "test/standalone/test_runner_path", + .import = @import("standalone/test_runner_path/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_13970", + .import = @import("standalone/issue_13970/build.zig"), + }, + .{ + .build_root = "test/standalone/main_pkg_path", + .import = @import("standalone/main_pkg_path/build.zig"), + }, + .{ + .build_root = "test/standalone/shared_library", + .import = @import("standalone/shared_library/build.zig"), + }, + .{ + .build_root = "test/standalone/mix_o_files", + .import = @import("standalone/mix_o_files/build.zig"), + }, + .{ + .build_root = "test/standalone/mix_c_files", + .import = @import("standalone/mix_c_files/build.zig"), + }, + .{ + .build_root = "test/standalone/global_linkage", + .import = @import("standalone/global_linkage/build.zig"), + }, + .{ + .build_root = "test/standalone/static_c_lib", + .import = @import("standalone/static_c_lib/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_339", + .import = @import("standalone/issue_339/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_8550", + .import = @import("standalone/issue_8550/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_794", + .import = @import("standalone/issue_794/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_5825", + .import = @import("standalone/issue_5825/build.zig"), + }, + .{ + .build_root = "test/standalone/pkg_import", + .import = @import("standalone/pkg_import/build.zig"), + }, + .{ + .build_root = "test/standalone/use_alias", + .import = @import("standalone/use_alias/build.zig"), + }, + .{ + .build_root = "test/standalone/install_raw_hex", + .import = @import("standalone/install_raw_hex/build.zig"), + }, + // TODO take away EmitOption.emit_to option and make it give a FileSource + //.{ + // .build_root = "test/standalone/emit_asm_and_bin", + // .import = @import("standalone/emit_asm_and_bin/build.zig"), + //}, + // TODO take away EmitOption.emit_to option and make it give a FileSource + //.{ + // .build_root = "test/standalone/issue_12588", + // .import = @import("standalone/issue_12588/build.zig"), + //}, + .{ + .build_root = "test/standalone/embed_generated_file", + .import = @import("standalone/embed_generated_file/build.zig"), + }, + .{ + .build_root = "test/standalone/dep_diamond", + .import = @import("standalone/dep_diamond/build.zig"), + }, + .{ + .build_root = "test/standalone/dep_triangle", + .import = @import("standalone/dep_triangle/build.zig"), + }, + .{ + .build_root = "test/standalone/dep_recursive", + .import = @import("standalone/dep_recursive/build.zig"), + }, + .{ + .build_root = "test/standalone/dep_mutually_recursive", + .import = @import("standalone/dep_mutually_recursive/build.zig"), + }, + .{ + .build_root = "test/standalone/dep_shared_builtin", + .import = @import("standalone/dep_shared_builtin/build.zig"), + }, + .{ + .build_root = "test/standalone/empty_env", + .import = @import("standalone/empty_env/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_9812", + .import = @import("standalone/issue_9812/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_11595", + .import = @import("standalone/issue_11595/build.zig"), + }, + .{ + .build_root = "test/standalone/load_dynamic_library", + .import = @import("standalone/load_dynamic_library/build.zig"), + }, + .{ + .build_root = "test/standalone/windows_spawn", + .import = @import("standalone/windows_spawn/build.zig"), + }, + .{ + .build_root = "test/standalone/c_compiler", + .import = @import("standalone/c_compiler/build.zig"), + }, + .{ + .build_root = "test/standalone/pie", + .import = @import("standalone/pie/build.zig"), + }, + .{ + .build_root = "test/standalone/issue_12706", + .import = @import("standalone/issue_12706/build.zig"), + }, + // TODO This test is disabled for doing naughty things in the build script. + // The logic needs to get moved to a child process instead of build.zig. + //.{ + // .build_root = "test/standalone/sigpipe", + // .import = @import("standalone/sigpipe/build.zig"), + //}, + .{ + .build_root = "test/standalone/issue_13030", + .import = @import("standalone/issue_13030/build.zig"), + }, +}; const std = @import("std"); diff --git a/test/standalone/brace_expansion/main.zig b/test/standalone/brace_expansion.zig similarity index 98% rename from test/standalone/brace_expansion/main.zig rename to test/standalone/brace_expansion.zig index dcdcad3865..7a769f6af7 100644 --- a/test/standalone/brace_expansion/main.zig +++ b/test/standalone/brace_expansion.zig @@ -234,8 +234,8 @@ pub fn main() !void { var result_buf = ArrayList(u8).init(global_allocator); defer result_buf.deinit(); - try expandString(stdin.items, &result_buf); - try stdout_file.write(result_buf.items); + try expandString(stdin, &result_buf); + try stdout_file.writeAll(result_buf.items); } test "invalid inputs" { diff --git a/test/standalone/brace_expansion/build.zig b/test/standalone/brace_expansion/build.zig deleted file mode 100644 index 7c32a09bef..0000000000 --- a/test/standalone/brace_expansion/build.zig +++ /dev/null @@ -1,11 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const main = b.addTest(.{ - .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), - }); - - const test_step = b.step("test", "Test it"); - test_step.dependOn(&main.step); -} diff --git a/test/standalone/c_compiler/build.zig b/test/standalone/c_compiler/build.zig index dce999d4a2..6c5f2b4db6 100644 --- a/test/standalone/c_compiler/build.zig +++ b/test/standalone/c_compiler/build.zig @@ -1,27 +1,24 @@ const std = @import("std"); const builtin = @import("builtin"); -const CrossTarget = std.zig.CrossTarget; - -// TODO integrate this with the std.Build executor API -fn isRunnableTarget(t: CrossTarget) bool { - if (t.isNative()) return true; - - return (t.getOsTag() == builtin.os.tag and - t.getCpuArch() == builtin.cpu.arch); -} pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; - const test_step = b.step("test", "Test the program"); + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const target: std.zig.CrossTarget = .{}; const exe_c = b.addExecutable(.{ .name = "test_c", .optimize = optimize, .target = target, }); - b.default_step.dependOn(&exe_c.step); exe_c.addCSourceFile("test.c", &[0][]const u8{}); exe_c.linkLibC(); @@ -47,13 +44,13 @@ pub fn build(b: *std.Build) void { else => {}, } - if (isRunnableTarget(target)) { - const run_c_cmd = exe_c.run(); - test_step.dependOn(&run_c_cmd.step); - const run_cpp_cmd = exe_cpp.run(); - test_step.dependOn(&run_cpp_cmd.step); - } else { - test_step.dependOn(&exe_c.step); - test_step.dependOn(&exe_cpp.step); - } + const run_c_cmd = b.addRunArtifact(exe_c); + run_c_cmd.expectExitCode(0); + run_c_cmd.skip_foreign_checks = true; + test_step.dependOn(&run_c_cmd.step); + + const run_cpp_cmd = b.addRunArtifact(exe_cpp); + run_cpp_cmd.expectExitCode(0); + run_cpp_cmd.skip_foreign_checks = true; + test_step.dependOn(&run_cpp_cmd.step); } diff --git a/test/standalone/dep_diamond/build.zig b/test/standalone/dep_diamond/build.zig index b60f898f0b..12eda4ec5d 100644 --- a/test/standalone/dep_diamond/build.zig +++ b/test/standalone/dep_diamond/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const shared = b.createModule(.{ .source_file = .{ .path = "shared.zig" }, @@ -23,6 +26,5 @@ pub fn build(b: *std.Build) void { const run = exe.run(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/dep_mutually_recursive/build.zig b/test/standalone/dep_mutually_recursive/build.zig index 0123646a9a..1a6bff8501 100644 --- a/test/standalone/dep_mutually_recursive/build.zig +++ b/test/standalone/dep_mutually_recursive/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const foo = b.createModule(.{ .source_file = .{ .path = "foo.zig" }, @@ -21,6 +24,5 @@ pub fn build(b: *std.Build) void { const run = exe.run(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/dep_recursive/build.zig b/test/standalone/dep_recursive/build.zig index 32d546e283..35b9f3cc47 100644 --- a/test/standalone/dep_recursive/build.zig +++ b/test/standalone/dep_recursive/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const foo = b.createModule(.{ .source_file = .{ .path = "foo.zig" }, @@ -17,6 +20,5 @@ pub fn build(b: *std.Build) void { const run = exe.run(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/dep_shared_builtin/build.zig b/test/standalone/dep_shared_builtin/build.zig index 6c029b654b..776794f95e 100644 --- a/test/standalone/dep_shared_builtin/build.zig +++ b/test/standalone/dep_shared_builtin/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const exe = b.addExecutable(.{ .name = "test", @@ -14,6 +17,5 @@ pub fn build(b: *std.Build) void { const run = exe.run(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/dep_triangle/build.zig b/test/standalone/dep_triangle/build.zig index f3b73aaf35..14163df84c 100644 --- a/test/standalone/dep_triangle/build.zig +++ b/test/standalone/dep_triangle/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const shared = b.createModule(.{ .source_file = .{ .path = "shared.zig" }, @@ -20,6 +23,5 @@ pub fn build(b: *std.Build) void { const run = exe.run(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/embed_generated_file/build.zig b/test/standalone/embed_generated_file/build.zig index 3b17ff0b8f..af1ae0a00f 100644 --- a/test/standalone/embed_generated_file/build.zig +++ b/test/standalone/embed_generated_file/build.zig @@ -1,8 +1,8 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; const bootloader = b.addExecutable(.{ .name = "bootloader", @@ -16,13 +16,11 @@ pub fn build(b: *std.Build) void { const exe = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, - .target = target, - .optimize = optimize, + .optimize = .Debug, }); exe.addAnonymousModule("bootloader.elf", .{ .source_file = bootloader.getOutputSource(), }); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&exe.step); } diff --git a/test/standalone/emit_asm_and_bin/build.zig b/test/standalone/emit_asm_and_bin/build.zig index 5345f0f538..9bdfadc33d 100644 --- a/test/standalone/emit_asm_and_bin/build.zig +++ b/test/standalone/emit_asm_and_bin/build.zig @@ -1,6 +1,9 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = b.standardOptimizeOption(.{}), @@ -8,6 +11,5 @@ pub fn build(b: *std.Build) void { main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") }; main.emit_bin = .{ .emit_to = b.pathFromRoot("main") }; - const test_step = b.step("test", "Run test"); test_step.dependOn(&main.step); } diff --git a/test/standalone/empty_env/build.zig b/test/standalone/empty_env/build.zig index c4b4846141..27ec75be22 100644 --- a/test/standalone/empty_env/build.zig +++ b/test/standalone/empty_env/build.zig @@ -1,15 +1,25 @@ const std = @import("std"); +const builtin = @import("builtin"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + + if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) { + // https://github.com/ziglang/zig/issues/13685 + return; + } + const main = b.addExecutable(.{ .name = "main", .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); - const run = main.run(); + const run = b.addRunArtifact(main); run.clearEnvironment(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/global_linkage/build.zig b/test/standalone/global_linkage/build.zig index 9f79c80fcf..2cf1b248a5 100644 --- a/test/standalone/global_linkage/build.zig +++ b/test/standalone/global_linkage/build.zig @@ -1,20 +1,24 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test the program"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; const obj1 = b.addStaticLibrary(.{ .name = "obj1", .root_source_file = .{ .path = "obj1.zig" }, .optimize = optimize, - .target = .{}, + .target = target, }); const obj2 = b.addStaticLibrary(.{ .name = "obj2", .root_source_file = .{ .path = "obj2.zig" }, .optimize = optimize, - .target = .{}, + .target = target, }); const main = b.addTest(.{ @@ -24,6 +28,5 @@ pub fn build(b: *std.Build) void { main.linkLibrary(obj1); main.linkLibrary(obj2); - const test_step = b.step("test", "Test it"); test_step.dependOn(&main.step); } diff --git a/test/standalone/install_raw_hex/build.zig b/test/standalone/install_raw_hex/build.zig index 6ed515e381..b34bb01378 100644 --- a/test/standalone/install_raw_hex/build.zig +++ b/test/standalone/install_raw_hex/build.zig @@ -3,8 +3,8 @@ const std = @import("std"); const CheckFileStep = std.Build.CheckFileStep; pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test the program"); - b.default_step.dependOn(test_step); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; const target = .{ .cpu_arch = .thumb, @@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void { .abi = .gnueabihf, }; - const optimize = b.standardOptimizeOption(.{}); + const optimize: std.builtin.OptimizeMode = .Debug; const elf = b.addExecutable(.{ .name = "zig-nrf52-blink.elf", diff --git a/test/standalone/issue_11595/build.zig b/test/standalone/issue_11595/build.zig index c335fb73da..7d9530c690 100644 --- a/test/standalone/issue_11595/build.zig +++ b/test/standalone/issue_11595/build.zig @@ -1,18 +1,17 @@ const std = @import("std"); const builtin = @import("builtin"); -const CrossTarget = std.zig.CrossTarget; - -// TODO integrate this with the std.Build executor API -fn isRunnableTarget(t: CrossTarget) bool { - if (t.isNative()) return true; - - return (t.getOsTag() == builtin.os.tag and - t.getCpuArch() == builtin.cpu.arch); -} pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; + + if (builtin.os.tag == .windows) { + // https://github.com/ziglang/zig/issues/12419 + return; + } const exe = b.addExecutable(.{ .name = "zigtest", @@ -44,11 +43,9 @@ pub fn build(b: *std.Build) void { b.default_step.dependOn(&exe.step); - const test_step = b.step("test", "Test the program"); - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } else { - test_step.dependOn(&exe.step); - } + const run_cmd = b.addRunArtifact(exe); + run_cmd.skip_foreign_checks = true; + run_cmd.expectExitCode(0); + + test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/issue_12588/build.zig b/test/standalone/issue_12588/build.zig index 9f14c53e38..fa22252fcc 100644 --- a/test/standalone/issue_12588/build.zig +++ b/test/standalone/issue_12588/build.zig @@ -1,8 +1,11 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; const obj = b.addObject(.{ .name = "main", @@ -15,6 +18,5 @@ pub fn build(b: *std.Build) void { obj.emit_bin = .no_emit; b.default_step.dependOn(&obj.step); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&obj.step); } diff --git a/test/standalone/issue_12706/build.zig b/test/standalone/issue_12706/build.zig index 9d616477a2..04eb826b44 100644 --- a/test/standalone/issue_12706/build.zig +++ b/test/standalone/issue_12706/build.zig @@ -2,17 +2,12 @@ const std = @import("std"); const builtin = @import("builtin"); const CrossTarget = std.zig.CrossTarget; -// TODO integrate this with the std.Build executor API -fn isRunnableTarget(t: CrossTarget) bool { - if (t.isNative()) return true; - - return (t.getOsTag() == builtin.os.tag and - t.getCpuArch() == builtin.cpu.arch); -} - pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; const exe = b.addExecutable(.{ .name = "main", @@ -20,22 +15,15 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .target = target, }); - exe.install(); const c_sources = [_][]const u8{ "test.c", }; - exe.addCSourceFiles(&c_sources, &.{}); exe.linkLibC(); - b.default_step.dependOn(&exe.step); - - const test_step = b.step("test", "Test the program"); - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } else { - test_step.dependOn(&exe.step); - } + const run_cmd = b.addRunArtifact(exe); + run_cmd.expectExitCode(0); + run_cmd.skip_foreign_checks = true; + test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/issue_13030/build.zig b/test/standalone/issue_13030/build.zig index 258d9b7db8..e31863fee2 100644 --- a/test/standalone/issue_13030/build.zig +++ b/test/standalone/issue_13030/build.zig @@ -3,17 +3,22 @@ const builtin = @import("builtin"); const CrossTarget = std.zig.CrossTarget; pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const obj = b.addObject(.{ .name = "main", .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, - .target = target, + .target = .{}, }); - b.default_step.dependOn(&obj.step); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&obj.step); } diff --git a/test/standalone/issue_13970/build.zig b/test/standalone/issue_13970/build.zig index f5e07d8903..bbaaac5886 100644 --- a/test/standalone/issue_13970/build.zig +++ b/test/standalone/issue_13970/build.zig @@ -1,6 +1,9 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + const test1 = b.addTest(.{ .root_source_file = .{ .path = "test_root/empty.zig" }, }); @@ -14,7 +17,6 @@ pub fn build(b: *std.Build) void { test2.setTestRunner("src/main.zig"); test3.setTestRunner("src/main.zig"); - const test_step = b.step("test", "Test package path resolution of custom test runner"); test_step.dependOn(&test1.step); test_step.dependOn(&test2.step); test_step.dependOn(&test3.step); diff --git a/test/standalone/issue_339/build.zig b/test/standalone/issue_339/build.zig index 62ac128aab..f4215dbb8b 100644 --- a/test/standalone/issue_339/build.zig +++ b/test/standalone/issue_339/build.zig @@ -1,13 +1,18 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; + const obj = b.addObject(.{ .name = "test", .root_source_file = .{ .path = "test.zig" }, - .target = b.standardTargetOptions(.{}), - .optimize = b.standardOptimizeOption(.{}), + .target = target, + .optimize = optimize, }); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&obj.step); } diff --git a/test/standalone/issue_5825/build.zig b/test/standalone/issue_5825/build.zig index 89272280d4..e8e8d48772 100644 --- a/test/standalone/issue_5825/build.zig +++ b/test/standalone/issue_5825/build.zig @@ -1,12 +1,15 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + const target = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .msvc, }; - const optimize = b.standardOptimizeOption(.{}); + const optimize: std.builtin.OptimizeMode = .Debug; const obj = b.addObject(.{ .name = "issue_5825", .root_source_file = .{ .path = "main.zig" }, @@ -24,6 +27,5 @@ pub fn build(b: *std.Build) void { exe.linkSystemLibrary("ntdll"); exe.addObject(obj); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&exe.step); } diff --git a/test/standalone/issue_7030/main.zig b/test/standalone/issue_7030.zig similarity index 100% rename from test/standalone/issue_7030/main.zig rename to test/standalone/issue_7030.zig diff --git a/test/standalone/issue_7030/build.zig b/test/standalone/issue_7030/build.zig deleted file mode 100644 index dc535318cc..0000000000 --- a/test/standalone/issue_7030/build.zig +++ /dev/null @@ -1,17 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const exe = b.addExecutable(.{ - .name = "issue_7030", - .root_source_file = .{ .path = "main.zig" }, - .target = .{ - .cpu_arch = .wasm32, - .os_tag = .freestanding, - }, - }); - exe.install(); - b.default_step.dependOn(&exe.step); - - const test_step = b.step("test", "Test the program"); - test_step.dependOn(&exe.step); -} diff --git a/test/standalone/issue_794/build.zig b/test/standalone/issue_794/build.zig index 3089a28fd0..8527f4af2c 100644 --- a/test/standalone/issue_794/build.zig +++ b/test/standalone/issue_794/build.zig @@ -1,13 +1,13 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + const test_artifact = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, }); test_artifact.addIncludePath("a_directory"); - b.default_step.dependOn(&test_artifact.step); - - const test_step = b.step("test", "Test the program"); test_step.dependOn(&test_artifact.step); } diff --git a/test/standalone/issue_8550/build.zig b/test/standalone/issue_8550/build.zig index c3303d55db..8f7631e68f 100644 --- a/test/standalone/issue_8550/build.zig +++ b/test/standalone/issue_8550/build.zig @@ -1,6 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) !void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const target = std.zig.CrossTarget{ .os_tag = .freestanding, .cpu_arch = .arm, @@ -8,7 +12,7 @@ pub fn build(b: *std.Build) !void { .explicit = &std.Target.arm.cpu.arm1176jz_s, }, }; - const optimize = b.standardOptimizeOption(.{}); + const kernel = b.addExecutable(.{ .name = "kernel", .root_source_file = .{ .path = "./main.zig" }, @@ -19,6 +23,5 @@ pub fn build(b: *std.Build) !void { kernel.setLinkerScriptPath(.{ .path = "./linker.ld" }); kernel.install(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&kernel.step); } diff --git a/test/standalone/issue_9812/build.zig b/test/standalone/issue_9812/build.zig index 4ca55ce999..71104b903c 100644 --- a/test/standalone/issue_9812/build.zig +++ b/test/standalone/issue_9812/build.zig @@ -1,7 +1,11 @@ const std = @import("std"); pub fn build(b: *std.Build) !void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const zip_add = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, @@ -13,6 +17,5 @@ pub fn build(b: *std.Build) !void { zip_add.addIncludePath("vendor/kuba-zip"); zip_add.linkLibC(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&zip_add.step); } diff --git a/test/standalone/load_dynamic_library/build.zig b/test/standalone/load_dynamic_library/build.zig index 44fc37893c..06a5424a8d 100644 --- a/test/standalone/load_dynamic_library/build.zig +++ b/test/standalone/load_dynamic_library/build.zig @@ -1,8 +1,19 @@ const std = @import("std"); +const builtin = @import("builtin"); pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; + + const ok = (builtin.os.tag != .wasi and + // https://github.com/ziglang/zig/issues/13550 + (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and + // https://github.com/ziglang/zig/issues/13686 + (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64)); + if (!ok) return; const lib = b.addSharedLibrary(.{ .name = "add", @@ -19,9 +30,10 @@ pub fn build(b: *std.Build) void { .target = target, }); - const run = main.run(); + const run = b.addRunArtifact(main); run.addArtifactArg(lib); + run.skip_foreign_checks = true; + run.expectExitCode(0); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&run.step); } diff --git a/test/standalone/load_dynamic_library/main.zig b/test/standalone/load_dynamic_library/main.zig index baf47c23ad..b47ea8a81f 100644 --- a/test/standalone/load_dynamic_library/main.zig +++ b/test/standalone/load_dynamic_library/main.zig @@ -11,11 +11,7 @@ pub fn main() !void { var lib = try std.DynLib.open(dynlib_name); defer lib.close(); - const Add = switch (@import("builtin").zig_backend) { - .stage1 => fn (i32, i32) callconv(.C) i32, - else => *const fn (i32, i32) callconv(.C) i32, - }; - + const Add = *const fn (i32, i32) callconv(.C) i32; const addFn = lib.lookup(Add, "add") orelse return error.SymbolNotFound; const result = addFn(12, 34); diff --git a/test/standalone/main_pkg_path/build.zig b/test/standalone/main_pkg_path/build.zig index f9919d5ab5..cd49573692 100644 --- a/test/standalone/main_pkg_path/build.zig +++ b/test/standalone/main_pkg_path/build.zig @@ -1,11 +1,13 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + const test_exe = b.addTest(.{ .root_source_file = .{ .path = "a/test.zig" }, }); test_exe.setMainPkgPath("."); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&test_exe.step); } diff --git a/test/standalone/mix_c_files/build.zig b/test/standalone/mix_c_files/build.zig index f2dfb2093f..0ea585e4e0 100644 --- a/test/standalone/mix_c_files/build.zig +++ b/test/standalone/mix_c_files/build.zig @@ -1,34 +1,28 @@ const std = @import("std"); -const builtin = @import("builtin"); -const CrossTarget = std.zig.CrossTarget; - -// TODO integrate this with the std.Build executor API -fn isRunnableTarget(t: CrossTarget) bool { - if (t.isNative()) return true; - - return (t.getOsTag() == builtin.os.tag and - t.getCpuArch() == builtin.cpu.arch); -} pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const exe = b.addExecutable(.{ .name = "test", .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, - .target = target, }); exe.addCSourceFile("test.c", &[_][]const u8{"-std=c11"}); exe.linkLibC(); b.default_step.dependOn(&exe.step); - const test_step = b.step("test", "Test the program"); - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } else { - test_step.dependOn(&exe.step); - } + const run_cmd = b.addRunArtifact(exe); + run_cmd.skip_foreign_checks = true; + run_cmd.expectExitCode(0); + + test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/mix_o_files/build.zig b/test/standalone/mix_o_files/build.zig index 2708343aa5..17ce55a8aa 100644 --- a/test/standalone/mix_o_files/build.zig +++ b/test/standalone/mix_o_files/build.zig @@ -1,18 +1,23 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; const obj = b.addObject(.{ .name = "base64", .root_source_file = .{ .path = "base64.zig" }, .optimize = optimize, - .target = .{}, + .target = target, }); const exe = b.addExecutable(.{ .name = "test", .optimize = optimize, + .target = target, }); exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"}); exe.addObject(obj); @@ -22,6 +27,5 @@ pub fn build(b: *std.Build) void { const run_cmd = exe.run(); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/pie/build.zig b/test/standalone/pie/build.zig index d51ea27328..615111b6c2 100644 --- a/test/standalone/pie/build.zig +++ b/test/standalone/pie/build.zig @@ -1,14 +1,21 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{ + .os_tag = .linux, + .cpu_arch = .x86_64, + }; + const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, + .target = target, }); main.pie = true; - const test_step = b.step("test", "Test the program"); test_step.dependOn(&main.step); - - b.default_step.dependOn(test_step); } diff --git a/test/standalone/pkg_import/build.zig b/test/standalone/pkg_import/build.zig index 5ea6c90af7..42799ab896 100644 --- a/test/standalone/pkg_import/build.zig +++ b/test/standalone/pkg_import/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const exe = b.addExecutable(.{ .name = "test", @@ -12,6 +15,5 @@ pub fn build(b: *std.Build) void { const run = exe.run(); - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig index 91f7c8a06a..63370af0cc 100644 --- a/test/standalone/shared_library/build.zig +++ b/test/standalone/shared_library/build.zig @@ -1,8 +1,11 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); - const target = b.standardTargetOptions(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{}; const lib = b.addSharedLibrary(.{ .name = "mathtest", .root_source_file = .{ .path = "mathtest.zig" }, @@ -20,10 +23,7 @@ pub fn build(b: *std.Build) void { exe.linkLibrary(lib); exe.linkSystemLibrary("c"); - b.default_step.dependOn(&exe.step); - const run_cmd = exe.run(); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/sigpipe/build.zig b/test/standalone/sigpipe/build.zig index 400f1a970d..6f50a86d68 100644 --- a/test/standalone/sigpipe/build.zig +++ b/test/standalone/sigpipe/build.zig @@ -2,7 +2,16 @@ const std = @import("std"); const os = std.os; pub fn build(b: *std.build.Builder) !void { - const test_step = b.step("test", "Run the tests"); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + // TODO signal handling code has no business being in a build script. + // this logic needs to move to a file called parent.zig which is + // added as an executable. + + //if (!std.os.have_sigpipe_support) { + // return; + //} // This test runs "breakpipe" as a child process and that process // depends on inheriting a SIGPIPE disposition of "default". diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig index 9937888843..5996c978d8 100644 --- a/test/standalone/static_c_lib/build.zig +++ b/test/standalone/static_c_lib/build.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; const foo = b.addStaticLibrary(.{ .name = "foo", @@ -18,6 +21,5 @@ pub fn build(b: *std.Build) void { test_exe.linkLibrary(foo); test_exe.addIncludePath("."); - const test_step = b.step("test", "Test it"); test_step.dependOn(&test_exe.step); } diff --git a/test/standalone/test_runner_path/build.zig b/test/standalone/test_runner_path/build.zig index f073c55d4a..40aad42b21 100644 --- a/test/standalone/test_runner_path/build.zig +++ b/test/standalone/test_runner_path/build.zig @@ -1,6 +1,11 @@ const std = @import("std"); +pub const requires_stage2 = true; + pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test the program"); + b.default_step = test_step; + const test_exe = b.addTest(.{ .root_source_file = .{ .path = "test.zig" }, .kind = .test_exe, @@ -9,6 +14,5 @@ pub fn build(b: *std.Build) void { const test_run = test_exe.run(); - const test_step = b.step("test", "Test the program"); test_step.dependOn(&test_run.step); } diff --git a/test/standalone/test_runner_path/test_runner.zig b/test/standalone/test_runner_path/test_runner.zig index f49ff55cae..2139ea8f68 100644 --- a/test/standalone/test_runner_path/test_runner.zig +++ b/test/standalone/test_runner_path/test_runner.zig @@ -1,51 +1,19 @@ const std = @import("std"); -const io = std.io; const builtin = @import("builtin"); -pub const io_mode: io.Mode = builtin.test_io_mode; - pub fn main() void { - const test_fn_list = builtin.test_functions; var ok_count: usize = 0; var skip_count: usize = 0; var fail_count: usize = 0; - var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined; - // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly - // ignores the alignment of the slice. - async_frame_buffer = &[_]u8{}; - - for (test_fn_list) |test_fn| { - const result = if (test_fn.async_frame_size) |size| switch (io_mode) { - .evented => blk: { - if (async_frame_buffer.len < size) { - std.heap.page_allocator.free(async_frame_buffer); - async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory"); - } - const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func); - break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{}); - }, - .blocking => { - skip_count += 1; - continue; - }, - } else test_fn.func(); - if (result) |_| { + for (builtin.test_functions) |test_fn| { + if (test_fn.func()) |_| { ok_count += 1; } else |err| switch (err) { - error.SkipZigTest => { - skip_count += 1; - }, - else => { - fail_count += 1; - }, + error.SkipZigTest => skip_count += 1, + else => fail_count += 1, } } - if (ok_count == test_fn_list.len) { - std.debug.print("All {d} tests passed.\n", .{ok_count}); - } else { - std.debug.print("{d} passed; {d} skipped; {d} failed.\n", .{ ok_count, skip_count, fail_count }); - } if (ok_count != 1 or skip_count != 1 or fail_count != 1) { std.process.exit(1); } diff --git a/test/standalone/use_alias/build.zig b/test/standalone/use_alias/build.zig index 89e07efb22..947db0828d 100644 --- a/test/standalone/use_alias/build.zig +++ b/test/standalone/use_alias/build.zig @@ -1,12 +1,16 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const main = b.addTest(.{ .root_source_file = .{ .path = "main.zig" }, - .optimize = b.standardOptimizeOption(.{}), + .optimize = optimize, }); main.addIncludePath("."); - const test_step = b.step("test", "Test it"); test_step.dependOn(&main.step); } diff --git a/test/standalone/windows_spawn/build.zig b/test/standalone/windows_spawn/build.zig index 3ebde5a50c..8cc6e18599 100644 --- a/test/standalone/windows_spawn/build.zig +++ b/test/standalone/windows_spawn/build.zig @@ -1,23 +1,34 @@ const std = @import("std"); +const builtin = @import("builtin"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const optimize: std.builtin.OptimizeMode = .Debug; + const target: std.zig.CrossTarget = .{ + .os_tag = .windows, + .cpu_arch = .x86_64, + }; const hello = b.addExecutable(.{ .name = "hello", .root_source_file = .{ .path = "hello.zig" }, .optimize = optimize, + .target = target, }); const main = b.addExecutable(.{ .name = "main", .root_source_file = .{ .path = "main.zig" }, .optimize = optimize, + .target = target, }); - const run = main.run(); + const run = b.addRunArtifact(main); run.addArtifactArg(hello); + run.expectExitCode(0); + run.skip_foreign_checks = true; - const test_step = b.step("test", "Test it"); test_step.dependOn(&run.step); } diff --git a/test/tests.zig b/test/tests.zig index 038111136a..2cd06b18b9 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -560,34 +560,62 @@ pub fn addStackTraceTests( pub fn addStandaloneTests( b: *std.Build, - test_filter: ?[]const u8, optimize_modes: []const OptimizeMode, - skip_non_native: bool, enable_macos_sdk: bool, omit_stage2: bool, enable_symlinks_windows: bool, ) *Step { const step = b.step("test-standalone", "Run the standalone tests"); - - _ = test_filter; - _ = skip_non_native; - _ = enable_macos_sdk; - _ = omit_stage2; - _ = enable_symlinks_windows; + const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows; for (standalone.simple_cases) |case| { for (optimize_modes) |optimize| { if (!case.all_modes and optimize != .Debug) continue; - const exe = b.addExecutable(.{ - .name = std.fs.path.stem(case.src_path), - .root_source_file = .{ .path = case.src_path }, - .optimize = optimize, - .target = case.target, - }); - if (case.link_libc) exe.linkLibC(); + if (case.is_exe) { + const exe = b.addExecutable(.{ + .name = std.fs.path.stem(case.src_path), + .root_source_file = .{ .path = case.src_path }, + .optimize = optimize, + .target = case.target, + }); + if (case.link_libc) exe.linkLibC(); - step.dependOn(&exe.step); + step.dependOn(&exe.step); + } + + if (case.is_test) { + const exe = b.addTest(.{ + .name = std.fs.path.stem(case.src_path), + .root_source_file = .{ .path = case.src_path }, + .optimize = optimize, + .target = case.target, + }); + if (case.link_libc) exe.linkLibC(); + + step.dependOn(&exe.step); + } + } + } + + inline for (standalone.build_cases) |case| { + const requires_stage2 = @hasDecl(case.import, "requires_stage2") and + case.import.requires_stage2; + const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and + case.import.requires_symlinks; + const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and + case.import.requires_macos_sdk; + const bad = + (requires_stage2 and omit_stage2) or + (requires_symlinks and omit_symlinks) or + (requires_macos_sdk and !enable_macos_sdk); + if (!bad) { + const dep = b.anonymousDependency(case.build_root, case.import, .{}); + const dep_step = dep.builder.default_step; + assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); + const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; + dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name }); + step.dependOn(dep_step); } } @@ -603,19 +631,19 @@ pub fn addLinkTests( const step = b.step("test-link", "Run the linker tests"); const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows; - inline for (link.cases) |link_test| { - const requires_stage2 = @hasDecl(link_test.import, "requires_stage2") and - link_test.import.requires_stage2; - const requires_symlinks = @hasDecl(link_test.import, "requires_symlinks") and - link_test.import.requires_symlinks; - const requires_macos_sdk = @hasDecl(link_test.import, "requires_macos_sdk") and - link_test.import.requires_macos_sdk; + inline for (link.cases) |case| { + const requires_stage2 = @hasDecl(case.import, "requires_stage2") and + case.import.requires_stage2; + const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and + case.import.requires_symlinks; + const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and + case.import.requires_macos_sdk; const bad = (requires_stage2 and omit_stage2) or (requires_symlinks and omit_symlinks) or (requires_macos_sdk and !enable_macos_sdk); if (!bad) { - const dep = b.anonymousDependency(link_test.build_root, link_test.import, .{}); + const dep = b.anonymousDependency(case.build_root, case.import, .{}); const dep_step = dep.builder.default_step; assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; @@ -627,9 +655,7 @@ pub fn addLinkTests( return step; } -pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step { - _ = test_filter; - _ = optimize_modes; +pub fn addCliTests(b: *std.Build) *Step { const step = b.step("test-cli", "Test the command line interface"); { @@ -815,6 +841,19 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co step.dependOn(&cleanup.step); } + { + // TODO this should move to become a CLI test rather than standalone + // cases.addBuildFile("test/standalone/options/build.zig", .{ + // .extra_argv = &.{ + // "-Dbool_true", + // "-Dbool_false=false", + // "-Dint=1234", + // "-De=two", + // "-Dstring=hello", + // }, + // }); + } + return step; } From 8d4067e7a37c13c0761b2685f46375b32f01037a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Mar 2023 00:19:55 -0700 Subject: [PATCH 222/294] CI: take advantage of zig build concurrency I ain't afraid of no ghost. This reverts commit 14a176b9b16e07a66a2f9cd485aaf80fed0f5a12. --- ci/aarch64-linux-debug.sh | 1 - ci/aarch64-linux-release.sh | 1 - ci/aarch64-macos.sh | 1 - ci/x86_64-linux-debug.sh | 1 - ci/x86_64-linux-release.sh | 1 - ci/x86_64-macos-debug.sh | 1 - ci/x86_64-macos-release.sh | 1 - 7 files changed, 7 deletions(-) diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index 90ed8bbc35..94f40c557b 100644 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -60,7 +60,6 @@ stage3-debug/bin/zig build -Dtarget=arm-linux-musleabihf # TODO: add -fqemu back to this line stage3-debug/bin/zig build test docs \ - -j1 \ -fwasmtime \ -Dstatic-llvm \ -Dtarget=native-native-musl \ diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index e99e3e1c08..65d6063f25 100644 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -60,7 +60,6 @@ stage3-release/bin/zig build -Dtarget=arm-linux-musleabihf # TODO: add -fqemu back to this line stage3-release/bin/zig build test docs \ - -j1 \ -fwasmtime \ -Dstatic-llvm \ -Dtarget=native-native-musl \ diff --git a/ci/aarch64-macos.sh b/ci/aarch64-macos.sh index 634a60b600..b4533e149f 100755 --- a/ci/aarch64-macos.sh +++ b/ci/aarch64-macos.sh @@ -44,7 +44,6 @@ PATH="$HOME/local/bin:$PATH" cmake .. \ $HOME/local/bin/ninja install stage3-release/bin/zig build test docs \ - -j1 \ --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index 1267a2d753..7f2382f04a 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -58,7 +58,6 @@ stage3-debug/bin/zig fmt --check .. \ stage3-debug/bin/zig build -Dtarget=arm-linux-musleabihf stage3-debug/bin/zig build test docs \ - -j1 \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index 3305f5e951..cdb24e4a6f 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -59,7 +59,6 @@ stage3-release/bin/zig fmt --check .. \ stage3-release/bin/zig build -Dtarget=arm-linux-musleabihf stage3-release/bin/zig build test docs \ - -j1 \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ diff --git a/ci/x86_64-macos-debug.sh b/ci/x86_64-macos-debug.sh index edd35970b8..c24ff5d295 100755 --- a/ci/x86_64-macos-debug.sh +++ b/ci/x86_64-macos-debug.sh @@ -48,7 +48,6 @@ cmake .. \ make $JOBS install stage3/bin/zig build test docs \ - -j1 \ --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ diff --git a/ci/x86_64-macos-release.sh b/ci/x86_64-macos-release.sh index 65dea3afcb..a4dedb4446 100755 --- a/ci/x86_64-macos-release.sh +++ b/ci/x86_64-macos-release.sh @@ -48,7 +48,6 @@ cmake .. \ make $JOBS install stage3/bin/zig build test docs \ - -j1 \ --zig-lib-dir "$(pwd)/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ From 974a6fe757fd53873e7dace177ad3ae3c425b504 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Mar 2023 14:34:17 -0700 Subject: [PATCH 223/294] std.Build.RunStep: support -fqemu solving bad dynamic linker --- lib/std/Build/RunStep.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 9ab9972c2e..9a1c887d7d 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -555,7 +555,9 @@ fn runCommand( try Step.handleVerbose(step.owner, self.cwd, argv); const result = spawnChildAndCollect(self, argv, has_side_effects) catch |err| term: { - if (err == error.InvalidExe) interpret: { + // InvalidExe: cpu arch mismatch + // FileNotFound: can happen with a wrong dynamic linker path + if (err == error.InvalidExe or err == error.FileNotFound) interpret: { // TODO: learn the target from the binary directly rather than from // relying on it being a CompileStep. This will make this logic // work even for the edge case that the binary was produced by a From 23295f64ca8b93f32e75cef42cc1293ec334e890 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 14:17:25 -0700 Subject: [PATCH 224/294] fix ZIR decoding of error notes --- src/AstGen.zig | 4 ++-- src/Compilation.zig | 2 +- src/Zir.zig | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 8ac67a7107..6d7b8156c8 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -10382,7 +10382,7 @@ fn appendErrorTok( comptime format: []const u8, args: anytype, ) !void { - try astgen.appendErrorTokNotes(token, format, args, &[0]u32{}); + try astgen.appendErrorTokNotesOff(token, 0, format, args, &[0]u32{}); } fn failTokNotes( @@ -10392,7 +10392,7 @@ fn failTokNotes( args: anytype, notes: []const u32, ) InnerError { - try appendErrorTokNotes(astgen, token, format, args, notes); + try appendErrorTokNotesOff(astgen, token, 0, format, args, notes); return error.AnalysisFail; } diff --git a/src/Compilation.zig b/src/Compilation.zig index b542c86511..ec21d2c483 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2909,7 +2909,7 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void { .column = @intCast(u32, err_loc.column), .source_line = try eb.addString(err_loc.source_line), }), - .notes_len = item.data.notes, + .notes_len = item.data.notesLen(file.zir), }); } diff --git a/src/Zir.zig b/src/Zir.zig index 65e2f21cc9..001c4e8101 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -3594,6 +3594,12 @@ pub const Inst = struct { /// 0 or a payload index of a `Block`, each is a payload /// index of another `Item`. notes: u32, + + pub fn notesLen(item: Item, zir: Zir) u32 { + if (item.notes == 0) return 0; + const block = zir.extraData(Block, item.notes); + return block.data.body_len; + } }; }; From 7106a91b097c5ac0feb13f328bec5e6788f8c8ef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 14:18:25 -0700 Subject: [PATCH 225/294] CLI: fix ast-check printing ZIR errors twice --- src/main.zig | 67 ++++++++++++---------------------------------------- 1 file changed, 15 insertions(+), 52 deletions(-) diff --git a/src/main.zig b/src/main.zig index 699122d26f..669f1afd0c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4082,12 +4082,7 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void defer errors.deinit(comp.gpa); if (errors.errorMessageCount() > 0) { - const ttyconf: std.debug.TTY.Config = switch (comp.color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; - errors.renderToStdErr(ttyconf); + errors.renderToStdErr(get_tty_conf(comp.color)); const log_text = comp.getCompileLogOutput(); if (log_text.len != 0) { std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); @@ -4714,14 +4709,9 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi &all_modules, ); if (wip_errors.root_list.items.len > 0) { - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; var errors = try wip_errors.toOwnedBundle(); defer errors.deinit(gpa); - errors.renderToStdErr(ttyconf); + errors.renderToStdErr(get_tty_conf(color)); process.exit(1); } try fetch_result; @@ -4767,7 +4757,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi defer comp.destroy(); updateModule(gpa, comp, .none) catch |err| switch (err) { - error.SemanticAnalyzeFail => process.exit(1), + error.SemanticAnalyzeFail => process.exit(2), else => |e| return e, }; try comp.makeBinFileExecutable(); @@ -4982,14 +4972,9 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(get_tty_conf(color)); process.exit(2); } } else if (tree.errors.len != 0) { @@ -5193,14 +5178,9 @@ fn fmtPathFile( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf: std.debug.TTY.Config = switch (fmt.color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(get_tty_conf(fmt.color)); fmt.any_error = true; } } @@ -5235,14 +5215,9 @@ fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Co try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors); - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(get_tty_conf(color)); } pub fn putAstErrorsIntoBundle( @@ -5848,11 +5823,6 @@ pub fn cmdAstCheck( file.tree_loaded = true; defer file.tree.deinit(gpa); - try printAstErrorsToStderr(gpa, file.tree, file.sub_file_path, color); - if (file.tree.errors.len != 0) { - process.exit(1); - } - file.zir = try AstGen.generate(gpa, file.tree); file.zir_loaded = true; defer file.zir.deinit(gpa); @@ -5862,14 +5832,9 @@ pub fn cmdAstCheck( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), - .on => .escape_codes, - .off => .no_color, - }; var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(get_tty_conf(color)); process.exit(1); } @@ -5974,11 +5939,6 @@ pub fn cmdChangelist( file.tree_loaded = true; defer file.tree.deinit(gpa); - try printAstErrorsToStderr(gpa, file.tree, old_source_file, .auto); - if (file.tree.errors.len != 0) { - process.exit(1); - } - file.zir = try AstGen.generate(gpa, file.tree); file.zir_loaded = true; defer file.zir.deinit(gpa); @@ -6013,11 +5973,6 @@ pub fn cmdChangelist( var new_tree = try Ast.parse(gpa, new_source, .zig); defer new_tree.deinit(gpa); - try printAstErrorsToStderr(gpa, new_tree, new_source_file, .auto); - if (new_tree.errors.len != 0) { - process.exit(1); - } - var old_zir = file.zir; defer old_zir.deinit(gpa); file.zir_loaded = false; @@ -6293,3 +6248,11 @@ const ClangSearchSanitizer = struct { iframework: bool = false, }; }; + +fn get_tty_conf(color: Color) std.debug.TTY.Config { + return switch (color) { + .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .on => .escape_codes, + .off => .no_color, + }; +} From 7db74009db4a4dc820e3e32c805ab7e29394205b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:06:07 -0700 Subject: [PATCH 226/294] std.Progress.Node: add a setName method This can be used to update an existing node's label rather than indicate that more things have been accomplished. --- lib/std/Progress.zig | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 64084e761f..dba7166398 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -126,6 +126,21 @@ pub const Node = struct { } } + /// Thread-safe. + pub fn setName(self: *Node, name: []const u8) void { + const progress = self.context; + progress.update_mutex.lock(); + defer progress.update_mutex.unlock(); + self.name = name; + if (self.parent) |parent| { + @atomicStore(?*Node, &parent.recently_updated_child, self, .Release); + if (parent.parent) |grand_parent| { + @atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .Release); + } + if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer); + } + } + /// Thread-safe. 0 means unknown. pub fn setEstimatedTotalItems(self: *Node, count: usize) void { @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic); @@ -174,16 +189,20 @@ pub fn maybeRefresh(self: *Progress) void { if (self.timer) |*timer| { if (!self.update_mutex.tryLock()) return; defer self.update_mutex.unlock(); - const now = timer.read(); - if (now < self.initial_delay_ns) return; - // TODO I have observed this to happen sometimes. I think we need to follow Rust's - // lead and guarantee monotonically increasing times in the std lib itself. - if (now < self.prev_refresh_timestamp) return; - if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; - return self.refreshWithHeldLock(); + maybeRefreshWithHeldLock(self, timer); } } +fn maybeRefreshWithHeldLock(self: *Progress, timer: *std.time.Timer) void { + const now = timer.read(); + if (now < self.initial_delay_ns) return; + // TODO I have observed this to happen sometimes. I think we need to follow Rust's + // lead and guarantee monotonically increasing times in the std lib itself. + if (now < self.prev_refresh_timestamp) return; + if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; + return self.refreshWithHeldLock(); +} + /// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe. pub fn refresh(self: *Progress) void { if (!self.update_mutex.tryLock()) return; From 25c3878c00b92ffc884d89d32817ca9c244f7972 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:06:56 -0700 Subject: [PATCH 227/294] std.fs.File.readvAll: fix behavior for 0-length vectors The OS layer expects pointer addresses to be inside the application's address space even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer addresses when the length is zero. So this function now modifies the iov_base fields when the length is zero. --- lib/std/fs/file.zig | 21 ++++++++++++++++++--- lib/std/os.zig | 3 +++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index bf93a61239..f81841a662 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -1048,12 +1048,27 @@ pub const File = struct { /// Returns the number of bytes read. If the number read is smaller than the total bytes /// from all the buffers, it means the file reached the end. Reaching the end of a file /// is not an error condition. - /// The `iovecs` parameter is mutable because this function needs to mutate the fields in - /// order to handle partial reads from the underlying OS layer. - /// See https://github.com/ziglang/zig/issues/7699 + /// + /// The `iovecs` parameter is mutable because: + /// * This function needs to mutate the fields in order to handle partial + /// reads from the underlying OS layer. + /// * The OS layer expects pointer addresses to be inside the application's address space + /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer + /// addresses when the length is zero. So this function modifies the iov_base fields + /// when the length is zero. + /// + /// Related open issue: https://github.com/ziglang/zig/issues/7699 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { if (iovecs.len == 0) return 0; + // We use the address of this local variable for all zero-length + // vectors so that the OS does not complain that we are giving it + // addresses outside the application's address space. + var garbage: [1]u8 = undefined; + for (iovecs) |*v| { + if (v.iov_len == 0) v.iov_base = &garbage; + } + var i: usize = 0; var off: usize = 0; while (true) { diff --git a/lib/std/os.zig b/lib/std/os.zig index 9d5625c13e..b11aac493f 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -766,6 +766,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { /// This operation is non-atomic on the following systems: /// * Windows /// On these systems, the read races with concurrent writes to the same file descriptor. +/// +/// This function assumes that all zero-length vectors have a pointer within the address +/// space of the application. pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { if (builtin.os.tag == .windows) { // TODO improve this to use ReadFileScatter From 3b00e341fd8d1f12a3a14ee79a675a36549e716b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:08:54 -0700 Subject: [PATCH 228/294] AstGen: skip walking the AST when there are parse errors The AST -> ZIR lowering process assumes an AST that does not have any parse errors. --- src/AstGen.zig | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 6d7b8156c8..182a28084f 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -133,8 +133,6 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { try astgen.extra.ensureTotalCapacity(gpa, tree.nodes.len + reserved_count); astgen.extra.items.len += reserved_count; - try lowerAstErrors(&astgen); - var top_scope: Scope.Top = .{}; var gz_instructions: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; @@ -150,18 +148,24 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { }; defer gz_instructions.deinit(gpa); - if (AstGen.structDeclInner( - &gen_scope, - &gen_scope.base, - 0, - tree.containerDeclRoot(), - .Auto, - 0, - )) |struct_decl_ref| { - assert(refToIndex(struct_decl_ref).? == 0); - } else |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => {}, // Handled via compile_errors below. + // The AST -> ZIR lowering process assumes an AST that does not have any + // parse errors. + if (tree.errors.len == 0) { + if (AstGen.structDeclInner( + &gen_scope, + &gen_scope.base, + 0, + tree.containerDeclRoot(), + .Auto, + 0, + )) |struct_decl_ref| { + assert(refToIndex(struct_decl_ref).? == 0); + } else |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => {}, // Handled via compile_errors below. + } + } else { + try lowerAstErrors(&astgen); } const err_index = @enumToInt(Zir.ExtraIndex.compile_errors); @@ -12642,7 +12646,7 @@ fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void { fn lowerAstErrors(astgen: *AstGen) !void { const tree = astgen.tree; - if (tree.errors.len == 0) return; + assert(tree.errors.len > 0); const gpa = astgen.gpa; const parse_err = tree.errors[0]; From 3186658e602b13a98c388872bbdc0b5cc1eb9267 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:10:21 -0700 Subject: [PATCH 229/294] std.Build.CheckFileStep: add a way to expect exact This is done in a bit of a haphazard way. Eventually the API needs to break in favor of a "checks" system similar to how RunStep works. --- lib/std/Build/CheckFileStep.zig | 41 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig index a65810681b..1c2b6b7786 100644 --- a/lib/std/Build/CheckFileStep.zig +++ b/lib/std/Build/CheckFileStep.zig @@ -1,19 +1,19 @@ -const std = @import("../std.zig"); -const Step = std.Build.Step; -const fs = std.fs; -const mem = std.mem; - -const CheckFileStep = @This(); - -pub const base_id = .check_file; +//! Fail the build step if a file does not match certain checks. +//! TODO: make this more flexible, supporting more kinds of checks. +//! TODO: generalize the code in std.testing.expectEqualStrings and make this +//! CheckFileStep produce those helpful diagnostics when there is not a match. step: Step, expected_matches: []const []const u8, +expected_exact: ?[]const u8, source: std.Build.FileSource, max_bytes: usize = 20 * 1024 * 1024, +pub const base_id = .check_file; + pub const Options = struct { - expected_matches: []const []const u8, + expected_matches: []const []const u8 = &.{}, + expected_exact: ?[]const u8 = null, }; pub fn create( @@ -31,6 +31,7 @@ pub fn create( }), .source = source.dupe(owner), .expected_matches = owner.dupeStrings(options.expected_matches), + .expected_exact = options.expected_exact, }; self.source.addStepDependencies(&self.step); return self; @@ -60,8 +61,28 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { \\{s} \\========= but file does not contain it: ======= \\{s} - \\ + \\=============================================== , .{ expected_match, contents }); } } + + if (self.expected_exact) |expected_exact| { + if (!mem.eql(u8, expected_exact, contents)) { + return step.fail( + \\ + \\========= expected: ===================== + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following file: ====== + \\{s} + , .{ expected_exact, contents, src_path }); + } + } } + +const CheckFileStep = @This(); +const std = @import("../std.zig"); +const Step = std.Build.Step; +const fs = std.fs; +const mem = std.mem; From 7cc4a6965c28e427cbfba57a985f837734d6257e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:13:55 -0700 Subject: [PATCH 230/294] build runner enhancements in preparation for test-cases * std.zig.ErrorBundle: support rendering options for whether to include the reference trace, whether to include the source line, and TTY configuration. * build runner: don't print progress in dumb terminals * std.Build.CompileStep: - add a way to expect compilation errors via the new `expect_errors` field. This is an advanced setting that can change the intent of the CompileStep. If this slice has nonzero length, it means that the CompileStep exists to check for compile errors and return *success* if they match, and failure otherwise. - remove the object format parameter from `checkObject`. The object format is known based on the CompileStep's target. - Avoid passing -L and -I flags for nonexistent directories within search_prefixes. This prevents a warning, that should probably be upgraded to an error in Zig's CLI parsing code, when the linker sees an -L directory that does not exist. * std.Build.Step: - When spawning the zig compiler process, takes advantage of the new `std.Progress.Node.setName` API to avoid ticking up a meaningless number at every progress update. --- lib/build_runner.zig | 30 ++++++--- lib/std/Build/CompileStep.zig | 113 ++++++++++++++++++++++++++++++---- lib/std/Build/Step.zig | 20 ++++-- lib/std/zig/ErrorBundle.zig | 29 +++++---- src/Sema.zig | 2 +- src/main.zig | 28 ++++++--- 6 files changed, 173 insertions(+), 49 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index bb04b3e132..8e0ebb4558 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -84,8 +84,6 @@ pub fn main() !void { ); defer builder.destroy(); - const Color = enum { auto, off, on }; - var targets = ArrayList([]const u8).init(arena); var debug_log_scopes = ArrayList([]const u8).init(arena); var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena }; @@ -273,13 +271,9 @@ pub fn main() !void { } const stderr = std.io.getStdErr(); - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(stderr), - .on => .escape_codes, - .off => .no_color, - }; + const ttyconf = get_tty_conf(color, stderr); - var progress: std.Progress = .{}; + var progress: std.Progress = .{ .dont_print_on_dumb = true }; const main_progress_node = progress.start("", 0); builder.debug_log_scopes = debug_log_scopes.items; @@ -498,7 +492,7 @@ fn runStepNames( if (total_compile_errors > 0) { for (compile_error_steps.items) |s| { if (s.result_error_bundle.errorMessageCount() > 0) { - s.result_error_bundle.renderToStdErr(ttyconf); + s.result_error_bundle.renderToStdErr(renderOptions(ttyconf)); } } @@ -961,3 +955,21 @@ fn cleanExit() void { // of calling exit. process.exit(0); } + +const Color = enum { auto, off, on }; + +fn get_tty_conf(color: Color, stderr: std.fs.File) std.debug.TTY.Config { + return switch (color) { + .auto => std.debug.detectTTYConfig(stderr), + .on => .escape_codes, + .off => .no_color, + }; +} + +fn renderOptions(ttyconf: std.debug.TTY.Config) std.zig.ErrorBundle.RenderOptions { + return .{ + .ttyconf = ttyconf, + .include_source_line = ttyconf != .no_color, + .include_reference_trace = ttyconf != .no_color, + }; +} diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 42cc63e8b4..5c44ab82d3 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -207,6 +207,12 @@ want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, +/// This is an advanced setting that can change the intent of this CompileStep. +/// If this slice has nonzero length, it means that this CompileStep exists to +/// check for compile errors and return *success* if they match, and failure +/// otherwise. +expect_errors: []const []const u8 = &.{}, + output_path_source: GeneratedFile, output_lib_path_source: GeneratedFile, output_h_path_source: GeneratedFile, @@ -552,8 +558,8 @@ pub fn run(cs: *CompileStep) *RunStep { return cs.step.owner.addRunArtifact(cs); } -pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { - return CheckObjectStep.create(self.step.owner, self.getOutputSource(), obj_format); +pub fn checkObject(self: *CompileStep) *CheckObjectStep { + return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt); } pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { @@ -1838,14 +1844,38 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { } for (b.search_prefixes.items) |search_prefix| { - try zig_args.append("-L"); - try zig_args.append(b.pathJoin(&.{ - search_prefix, "lib", - })); - try zig_args.append("-I"); - try zig_args.append(b.pathJoin(&.{ - search_prefix, "include", - })); + var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { + return step.fail("unable to open prefix directory '{s}': {s}", .{ + search_prefix, @errorName(err), + }); + }; + defer prefix_dir.close(); + + // Avoid passing -L and -I flags for nonexistent directories. + // This prevents a warning, that should probably be upgraded to an error in Zig's + // CLI parsing code, when the linker sees an -L directory that does not exist. + + if (prefix_dir.accessZ("lib", .{})) |_| { + try zig_args.appendSlice(&.{ + "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ + search_prefix, @errorName(e), + }), + } + + if (prefix_dir.accessZ("include", .{})) |_| { + try zig_args.appendSlice(&.{ + "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ + search_prefix, @errorName(e), + }), + } } try addFlag(&zig_args, "valgrind", self.valgrind_support); @@ -1943,7 +1973,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(resolved_args_file); } - const output_bin_path = try step.evalZigProcess(zig_args.items, prog_node); + const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { + error.NeedCompileErrorCheck => { + assert(self.expect_errors.len != 0); + try checkCompileErrors(self); + return; + }, + else => |e| return e, + }; const build_output_dir = fs.path.dirname(output_bin_path).?; if (self.output_dir) |output_dir| { @@ -2178,3 +2215,57 @@ const TransitiveDeps = struct { } } }; + +fn checkCompileErrors(self: *CompileStep) !void { + // Clear this field so that it does not get printed by the build runner. + const actual_eb = self.step.result_error_bundle; + self.step.result_error_bundle = std.zig.ErrorBundle.empty; + + const arena = self.step.owner.allocator; + + var actual_stderr_list = std.ArrayList(u8).init(arena); + try actual_eb.renderToWriter(.{ + .ttyconf = .no_color, + .include_reference_trace = false, + .include_source_line = false, + }, actual_stderr_list.writer()); + const actual_stderr = try actual_stderr_list.toOwnedSlice(); + + // Render the expected lines into a string that we can compare verbatim. + var expected_generated = std.ArrayList(u8).init(arena); + + var actual_line_it = mem.split(u8, actual_stderr, "\n"); + for (self.expect_errors) |expect_line| { + const actual_line = actual_line_it.next() orelse { + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + continue; + }; + if (mem.endsWith(u8, actual_line, expect_line)) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + if (mem.startsWith(u8, expect_line, ":?:?: ")) { + if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + } + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + } + + if (mem.eql(u8, expected_generated.items, actual_stderr)) return; + + // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile + return self.step.fail( + \\ + \\========= expected: ===================== + \\{s} + \\========= but found: ==================== + \\{s} + \\========================================= + , .{ expected_generated.items, actual_stderr }); +} diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index f1edab5881..45aa635972 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -295,8 +295,8 @@ pub fn evalZigProcess( var node_name: std.ArrayListUnmanaged(u8) = .{}; defer node_name.deinit(gpa); - var sub_prog_node: ?std.Progress.Node = null; - defer if (sub_prog_node) |*n| n.end(); + var sub_prog_node = prog_node.start("", 0); + defer sub_prog_node.end(); const stdout = poller.fifo(.stdout); @@ -336,11 +336,9 @@ pub fn evalZigProcess( }; }, .progress => { - if (sub_prog_node) |*n| n.end(); node_name.clearRetainingCapacity(); try node_name.appendSlice(gpa, body); - sub_prog_node = prog_node.start(node_name.items, 0); - sub_prog_node.?.activate(); + sub_prog_node.setName(node_name.items); }, .emit_bin_path => { const EbpHdr = std.zig.Server.Message.EmitBinPath; @@ -371,6 +369,18 @@ pub fn evalZigProcess( s.result_duration_ns = timer.read(); s.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; + // Special handling for CompileStep that is expecting compile errors. + if (s.cast(Build.CompileStep)) |compile| switch (term) { + .Exited => { + // Note that the exit code may be 0 in this case due to the + // compiler server protocol. + if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) { + return error.NeedCompileErrorCheck; + } + }, + else => {}, + }; + try handleChildProcessTerm(s, term, null, argv); if (s.result_error_bundle.errorMessageCount() > 0) { diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 94f80797a8..845e9d8ff5 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -141,32 +141,35 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 { return string_bytes[index..end :0]; } -pub fn renderToStdErr(eb: ErrorBundle, ttyconf: std.debug.TTY.Config) void { +pub const RenderOptions = struct { + ttyconf: std.debug.TTY.Config, + include_reference_trace: bool = true, + include_source_line: bool = true, +}; + +pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void { std.debug.getStderrMutex().lock(); defer std.debug.getStderrMutex().unlock(); const stderr = std.io.getStdErr(); - return renderToWriter(eb, ttyconf, stderr.writer()) catch return; + return renderToWriter(eb, options, stderr.writer()) catch return; } -pub fn renderToWriter( - eb: ErrorBundle, - ttyconf: std.debug.TTY.Config, - writer: anytype, -) anyerror!void { +pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void { for (eb.getMessages()) |err_msg| { - try renderErrorMessageToWriter(eb, err_msg, ttyconf, writer, "error", .Red, 0); + try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0); } } fn renderErrorMessageToWriter( eb: ErrorBundle, + options: RenderOptions, err_msg_index: MessageIndex, - ttyconf: std.debug.TTY.Config, stderr: anytype, kind: []const u8, color: std.debug.TTY.Color, indent: usize, ) anyerror!void { + const ttyconf = options.ttyconf; var counting_writer = std.io.countingWriter(stderr); const counting_stderr = counting_writer.writer(); const err_msg = eb.getErrorMessage(err_msg_index); @@ -196,7 +199,7 @@ fn renderErrorMessageToWriter( try stderr.print(" ({d} times)\n", .{err_msg.count}); } try ttyconf.setColor(stderr, .Reset); - if (src.data.source_line != 0) { + if (src.data.source_line != 0 and options.include_source_line) { const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { '\t' => try stderr.writeByte(' '), @@ -216,9 +219,9 @@ fn renderErrorMessageToWriter( try ttyconf.setColor(stderr, .Reset); } for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent); + try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent); } - if (src.data.reference_trace_len > 0) { + if (src.data.reference_trace_len > 0 and options.include_reference_trace) { try ttyconf.setColor(stderr, .Reset); try ttyconf.setColor(stderr, .Dim); try stderr.print("referenced by:\n", .{}); @@ -266,7 +269,7 @@ fn renderErrorMessageToWriter( } try ttyconf.setColor(stderr, .Reset); for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, note, ttyconf, stderr, "note", .Cyan, indent + 4); + try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4); } } } diff --git a/src/Sema.zig b/src/Sema.zig index 03ebbbdbac..c9f4f27fe2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2220,7 +2220,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable; std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle() catch unreachable; - error_bundle.renderToStdErr(.no_color); + error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); crash_report.compilerPanic("unexpected compile error occurred", null, null); } diff --git a/src/main.zig b/src/main.zig index 669f1afd0c..03d746af0c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4082,7 +4082,7 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void defer errors.deinit(comp.gpa); if (errors.errorMessageCount() > 0) { - errors.renderToStdErr(get_tty_conf(comp.color)); + errors.renderToStdErr(renderOptions(comp.color)); const log_text = comp.getCompileLogOutput(); if (log_text.len != 0) { std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); @@ -4711,7 +4711,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi if (wip_errors.root_list.items.len > 0) { var errors = try wip_errors.toOwnedBundle(); defer errors.deinit(gpa); - errors.renderToStdErr(get_tty_conf(color)); + errors.renderToStdErr(renderOptions(color)); process.exit(1); } try fetch_result; @@ -4974,7 +4974,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void try Compilation.addZirErrorMessages(&wip_errors, &file); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(color)); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(2); } } else if (tree.errors.len != 0) { @@ -5180,7 +5180,7 @@ fn fmtPathFile( try Compilation.addZirErrorMessages(&wip_errors, &file); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(fmt.color)); + error_bundle.renderToStdErr(renderOptions(fmt.color)); fmt.any_error = true; } } @@ -5217,7 +5217,7 @@ fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Co var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(color)); + error_bundle.renderToStdErr(renderOptions(color)); } pub fn putAstErrorsIntoBundle( @@ -5834,7 +5834,7 @@ pub fn cmdAstCheck( try Compilation.addZirErrorMessages(&wip_errors, &file); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(get_tty_conf(color)); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); } @@ -5892,6 +5892,7 @@ pub fn cmdChangelist( arena: Allocator, args: []const []const u8, ) !void { + const color: Color = .auto; const Zir = @import("Zir.zig"); const old_source_file = args[0]; @@ -5948,10 +5949,9 @@ pub fn cmdChangelist( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); } @@ -5984,10 +5984,9 @@ pub fn cmdChangelist( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); var error_bundle = try wip_errors.toOwnedBundle(); defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(ttyconf); + error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); } @@ -6256,3 +6255,12 @@ fn get_tty_conf(color: Color) std.debug.TTY.Config { .off => .no_color, }; } + +fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions { + const ttyconf = get_tty_conf(color); + return .{ + .ttyconf = ttyconf, + .include_source_line = ttyconf != .no_color, + .include_reference_trace = ttyconf != .no_color, + }; +} From 29cfd47d6509fc6ee1a165b3bc03180f6cf351a5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 18:22:51 -0700 Subject: [PATCH 231/294] re-enable test-cases and get them all passing Instead of using `zig test` to build a special version of the compiler that runs all the test-cases, the zig build system is now used as much as possible - all with the basic steps found in the standard library. For incremental compilation tests (the ones that look like foo.0.zig, foo.1.zig, foo.2.zig, etc.), a special version of the compiler is compiled into a utility executable called "check-case" which checks exactly one sequence of incremental updates in an independent subprocess. Previously, all incremental and non-incremental test cases were done in the same test runner process. The compile error checking code is now simpler, but also a bit rudimentary, and so it additionally makes sure that the actual compile errors do not include *extra* messages, and it makes sure that the actual compile errors output in the same order as expected. It is also based on the "ends-with" property of each line rather than the previous logic, which frankly I didn't want to touch with a ten-meter pole. The compile error test cases have been updated to pass in light of these differences. Previously, 'error' mode with 0 compile errors was used to shoehorn in a different kind of test-case - one that only checks if a piece of code compiles without errors. Now there is a 'compile' mode of test-cases, and 'error' must be only used when there are greater than 0 errors. link test cases are updated to omit the target object format argument when calling checkObject since that is no longer needed. The test/stage2 directory is removed; the 2 files within are moved to be directly in the test/ directory. --- build.zig | 50 +- src/test.zig | 1968 ----------------- test/cases.zig | 10 +- .../access_inactive_union_field_comptime.zig | 1 + test/cases/compile_errors/bad_import.zig | 2 +- .../condition_comptime_reason_explained.zig | 2 + ...edding_opaque_type_in_struct_and_union.zig | 1 + ...xtern_function_with_comptime_parameter.zig | 2 +- .../function_parameter_is_opaque.zig | 1 + .../helpful_return_type_error_message.zig | 2 +- .../implicit_semicolon-block_expr.zig | 2 + .../implicit_semicolon-block_statement.zig | 2 + ...implicit_semicolon-comptime_expression.zig | 2 + .../implicit_semicolon-comptime_statement.zig | 2 + .../implicit_semicolon-defer.zig | 2 + .../implicit_semicolon-for_expression.zig | 3 + .../implicit_semicolon-for_statement.zig | 3 + ...t_semicolon-if-else-if-else_expression.zig | 2 + ...it_semicolon-if-else-if-else_statement.zig | 2 + ...plicit_semicolon-if-else-if_expression.zig | 2 + ...mplicit_semicolon-if-else-if_statement.zig | 2 + .../implicit_semicolon-if-else_expression.zig | 2 + .../implicit_semicolon-if-else_statement.zig | 2 + .../implicit_semicolon-if_expression.zig | 2 + .../implicit_semicolon-if_statement.zig | 2 + .../implicit_semicolon-test_expression.zig | 3 + .../implicit_semicolon-test_statement.zig | 3 + ...it_semicolon-while-continue_expression.zig | 2 + ...cit_semicolon-while-continue_statement.zig | 2 + .../implicit_semicolon-while_expression.zig | 2 + .../implicit_semicolon-while_statement.zig | 2 + .../invalid_member_of_builtin_enum.zig | 2 +- .../invalid_store_to_comptime_field.zig | 2 +- .../compile_errors/invalid_struct_field.zig | 1 + .../missing_main_fn_in_executable.zig | 6 +- test/cases/compile_errors/private_main_fn.zig | 6 +- ...runtime_index_into_comptime_type_slice.zig | 5 +- .../struct_type_mismatch_in_arg.zig | 2 +- ...nion_init_with_none_or_multiple_fields.zig | 3 +- ...ess_chaining_pointer_to_optional_array.zig | 2 +- ..._pointer_access_chaining_array_pointer.zig | 2 +- ...spaces_pointer_access_chaining_complex.zig | 2 +- ...pointer_access_chaining_struct_pointer.zig | 2 +- ..._multiple_pointers_with_address_spaces.zig | 2 +- .../llvm/pointer_keeps_address_space.zig | 2 +- ...ace_when_taking_address_of_dereference.zig | 2 +- ...ress_space_coerces_to_implicit_pointer.zig | 2 +- test/{stage2 => }/cbe.zig | 77 +- test/compile_errors.zig | 224 +- test/link/macho/dead_strip/build.zig | 4 +- test/link/macho/dead_strip_dylibs/build.zig | 2 +- test/link/macho/dylib/build.zig | 4 +- test/link/macho/entry/build.zig | 2 +- test/link/macho/headerpad/build.zig | 8 +- test/link/macho/linksection/build.zig | 2 +- test/link/macho/needed_framework/build.zig | 2 +- test/link/macho/needed_library/build.zig | 2 +- test/link/macho/pagezero/build.zig | 4 +- test/link/macho/search_strategy/build.zig | 2 +- test/link/macho/stack_size/build.zig | 2 +- test/link/macho/strict_validation/build.zig | 2 +- test/link/macho/unwind_info/build.zig | 2 +- test/link/macho/weak_framework/build.zig | 2 +- test/link/macho/weak_library/build.zig | 2 +- test/link/wasm/archive/build.zig | 2 +- test/link/wasm/basic-features/build.zig | 2 +- test/link/wasm/bss/build.zig | 2 +- test/link/wasm/export-data/build.zig | 2 +- test/link/wasm/export/build.zig | 6 +- test/link/wasm/extern-mangle/build.zig | 2 +- test/link/wasm/function-table/build.zig | 6 +- test/link/wasm/infer-features/build.zig | 2 +- test/link/wasm/producers/build.zig | 2 +- test/link/wasm/segments/build.zig | 2 +- test/link/wasm/stack_pointer/build.zig | 2 +- test/link/wasm/type/build.zig | 2 +- test/{stage2 => }/nvptx.zig | 31 +- test/src/Cases.zig | 1587 +++++++++++++ test/tests.zig | 27 + 79 files changed, 1805 insertions(+), 2343 deletions(-) delete mode 100644 src/test.zig rename test/{stage2 => }/cbe.zig (92%) rename test/{stage2 => }/nvptx.zig (76%) create mode 100644 test/src/Cases.zig diff --git a/build.zig b/build.zig index ba12ae5ae0..9ba2b1acef 100644 --- a/build.zig +++ b/build.zig @@ -53,13 +53,14 @@ pub fn build(b: *std.Build) !void { const docs_step = b.step("docs", "Build documentation"); docs_step.dependOn(&docgen_cmd.step); - const test_cases = b.addTest(.{ - .root_source_file = .{ .path = "src/test.zig" }, + const check_case_exe = b.addExecutable(.{ + .name = "check-case", + .root_source_file = .{ .path = "test/src/Cases.zig" }, .optimize = optimize, }); - test_cases.main_pkg_path = "."; - test_cases.stack_size = stack_size; - test_cases.single_threaded = single_threaded; + check_case_exe.main_pkg_path = "."; + check_case_exe.stack_size = stack_size; + check_case_exe.single_threaded = single_threaded; const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false; const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; @@ -178,7 +179,7 @@ pub fn build(b: *std.Build) !void { if (target.isWindows() and target.getAbi() == .gnu) { // LTO is currently broken on mingw, this can be removed when it's fixed. exe.want_lto = false; - test_cases.want_lto = false; + check_case_exe.want_lto = false; } const exe_options = b.addOptions(); @@ -196,7 +197,7 @@ pub fn build(b: *std.Build) !void { if (link_libc) { exe.linkLibC(); - test_cases.linkLibC(); + check_case_exe.linkLibC(); } const is_debug = optimize == .Debug; @@ -282,14 +283,14 @@ pub fn build(b: *std.Build) !void { } try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx); - try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx); + try addCmakeCfgOptionsToExe(b, cfg, check_case_exe, use_zig_libcxx); } else { // Here we are -Denable-llvm but no cmake integration. try addStaticLlvmOptionsToExe(exe); - try addStaticLlvmOptionsToExe(test_cases); + try addStaticLlvmOptionsToExe(check_case_exe); } if (target.isWindows()) { - inline for (.{ exe, test_cases }) |artifact| { + inline for (.{ exe, check_case_exe }) |artifact| { artifact.linkSystemLibrary("version"); artifact.linkSystemLibrary("uuid"); artifact.linkSystemLibrary("ole32"); @@ -334,8 +335,9 @@ pub fn build(b: *std.Build) !void { const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"); const test_cases_options = b.addOptions(); - test_cases.addOptions("build_options", test_cases_options); + check_case_exe.addOptions("build_options", test_cases_options); + test_cases_options.addOption(bool, "enable_tracy", false); test_cases_options.addOption(bool, "enable_logging", enable_logging); test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots); test_cases_options.addOption(bool, "skip_non_native", skip_non_native); @@ -358,12 +360,6 @@ pub fn build(b: *std.Build) !void { test_cases_options.addOption(std.SemanticVersion, "semver", semver); test_cases_options.addOption(?[]const u8, "test_filter", test_filter); - const test_cases_step = b.step("test-cases", "Run the main compiler test cases"); - test_cases_step.dependOn(&test_cases.step); - if (!skip_stage2_tests) { - test_step.dependOn(test_cases_step); - } - var chosen_opt_modes_buf: [4]builtin.Mode = undefined; var chosen_mode_index: usize = 0; if (!skip_debug) { @@ -386,21 +382,20 @@ pub fn build(b: *std.Build) !void { const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" }; const fmt_exclude_paths = &.{"test/cases"}; - const check_fmt = b.addFmt(.{ - .paths = fmt_include_paths, - .exclude_paths = fmt_exclude_paths, - .check = true, - }); const do_fmt = b.addFmt(.{ .paths = fmt_include_paths, .exclude_paths = fmt_exclude_paths, }); - const test_fmt_step = b.step("test-fmt", "Check whether source files have conforming formatting"); - test_fmt_step.dependOn(&check_fmt.step); + b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{ + .paths = fmt_include_paths, + .exclude_paths = fmt_exclude_paths, + .check = true, + }).step); - const do_fmt_step = b.step("fmt", "Modify source files in place to have conforming formatting"); - do_fmt_step.dependOn(&do_fmt.step); + const test_cases_step = b.step("test-cases", "Run the main compiler test cases"); + try tests.addCases(b, test_cases_step, test_filter, check_case_exe); + if (!skip_stage2_tests) test_step.dependOn(test_cases_step); test_step.dependOn(tests.addModuleTests(b, .{ .test_filter = test_filter, @@ -475,6 +470,9 @@ pub fn build(b: *std.Build) !void { })); try addWasiUpdateStep(b, version); + + b.step("fmt", "Modify source files in place to have conforming formatting") + .dependOn(&do_fmt.step); } fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { diff --git a/src/test.zig b/src/test.zig deleted file mode 100644 index 5b73e516c6..0000000000 --- a/src/test.zig +++ /dev/null @@ -1,1968 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const Allocator = std.mem.Allocator; -const CrossTarget = std.zig.CrossTarget; -const print = std.debug.print; -const assert = std.debug.assert; -const ThreadPool = std.Thread.Pool; -const WaitGroup = std.Thread.WaitGroup; - -const link = @import("link.zig"); -const Compilation = @import("Compilation.zig"); -const Package = @import("Package.zig"); -const introspect = @import("introspect.zig"); -const build_options = @import("build_options"); -const zig_h = link.File.C.zig_h; - -const enable_qemu: bool = build_options.enable_qemu; -const enable_wine: bool = build_options.enable_wine; -const enable_wasmtime: bool = build_options.enable_wasmtime; -const enable_darling: bool = build_options.enable_darling; -const enable_rosetta: bool = build_options.enable_rosetta; -const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; -const skip_stage1 = true; - -const hr = "=" ** 80; - -test { - const use_gpa = build_options.force_gpa or !builtin.link_libc; - const gpa = gpa: { - if (use_gpa) { - break :gpa std.testing.allocator; - } - // We would prefer to use raw libc allocator here, but cannot - // use it if it won't support the alignment we need. - if (@alignOf(std.c.max_align_t) < @alignOf(i128)) { - break :gpa std.heap.c_allocator; - } - break :gpa std.heap.raw_c_allocator; - }; - - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - var ctx = TestContext.init(gpa, arena); - defer ctx.deinit(); - - { - const dir_path = try std.fs.path.join(arena, &.{ - std.fs.path.dirname(@src().file).?, "..", "test", "cases", - }); - - var dir = try std.fs.cwd().openIterableDir(dir_path, .{}); - defer dir.close(); - - ctx.addTestCasesFromDir(dir); - } - - try @import("../test/cases.zig").addCases(&ctx); - - try ctx.run(); -} - -const ErrorMsg = union(enum) { - src: struct { - src_path: []const u8, - msg: []const u8, - // maxint means match anything - // this is a workaround for stage1 compiler bug I ran into when making it ?u32 - line: u32, - // maxint means match anything - // this is a workaround for stage1 compiler bug I ran into when making it ?u32 - column: u32, - kind: Kind, - count: u32, - }, - plain: struct { - msg: []const u8, - kind: Kind, - count: u32, - }, - - const Kind = enum { - @"error", - note, - }; - - fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg { - switch (other) { - .src => |src| return .{ - .src = .{ - .src_path = src.src_path, - .msg = src.msg, - .line = @intCast(u32, src.line), - .column = @intCast(u32, src.column), - .kind = kind, - .count = src.count, - }, - }, - .plain => |plain| return .{ - .plain = .{ - .msg = plain.msg, - .kind = kind, - .count = plain.count, - }, - }, - } - } - - pub fn format( - self: ErrorMsg, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - _ = options; - switch (self) { - .src => |src| { - if (!std.mem.eql(u8, src.src_path, "?") or - src.line != std.math.maxInt(u32) or - src.column != std.math.maxInt(u32)) - { - try writer.print("{s}:", .{src.src_path}); - if (src.line != std.math.maxInt(u32)) { - try writer.print("{d}:", .{src.line + 1}); - } else { - try writer.writeAll("?:"); - } - if (src.column != std.math.maxInt(u32)) { - try writer.print("{d}: ", .{src.column + 1}); - } else { - try writer.writeAll("?: "); - } - } - try writer.print("{s}: {s}", .{ @tagName(src.kind), src.msg }); - if (src.count != 1) { - try writer.print(" ({d} times)", .{src.count}); - } - }, - .plain => |plain| { - try writer.print("{s}: {s}", .{ @tagName(plain.kind), plain.msg }); - if (plain.count != 1) { - try writer.print(" ({d} times)", .{plain.count}); - } - }, - } - } -}; - -/// Default config values for known test manifest key-value pairings. -/// Currently handled defaults are: -/// * backend -/// * target -/// * output_mode -/// * is_test -const TestManifestConfigDefaults = struct { - /// Asserts if the key doesn't exist - yep, it's an oversight alright. - fn get(@"type": TestManifest.Type, key: []const u8) []const u8 { - if (std.mem.eql(u8, key, "backend")) { - return "stage2"; - } else if (std.mem.eql(u8, key, "target")) { - comptime { - var defaults: []const u8 = ""; - // TODO should we only return "mainstream" targets by default here? - // TODO we should also specify ABIs explicitly as the backends are - // getting more and more complete - // Linux - inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| { - defaults = defaults ++ arch ++ "-linux" ++ ","; - } - // macOS - inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| { - defaults = defaults ++ arch ++ "-macos" ++ ","; - } - // Windows - defaults = defaults ++ "x86_64-windows" ++ ","; - // Wasm - defaults = defaults ++ "wasm32-wasi"; - return defaults; - } - } else if (std.mem.eql(u8, key, "output_mode")) { - return switch (@"type") { - .@"error" => "Obj", - .run => "Exe", - .cli => @panic("TODO test harness for CLI tests"), - }; - } else if (std.mem.eql(u8, key, "is_test")) { - return "0"; - } else unreachable; - } -}; - -/// Manifest syntax example: -/// (see https://github.com/ziglang/zig/issues/11288) -/// -/// error -/// backend=stage1,stage2 -/// output_mode=exe -/// -/// :3:19: error: foo -/// -/// run -/// target=x86_64-linux,aarch64-macos -/// -/// I am expected stdout! Hello! -/// -/// cli -/// -/// build test -const TestManifest = struct { - type: Type, - config_map: std.StringHashMap([]const u8), - trailing_bytes: []const u8 = "", - - const Type = enum { - @"error", - run, - cli, - }; - - const TrailingIterator = struct { - inner: std.mem.TokenIterator(u8), - - fn next(self: *TrailingIterator) ?[]const u8 { - const next_inner = self.inner.next() orelse return null; - return std.mem.trim(u8, next_inner[2..], " \t"); - } - }; - - fn ConfigValueIterator(comptime T: type) type { - return struct { - inner: std.mem.SplitIterator(u8), - - fn next(self: *@This()) !?T { - const next_raw = self.inner.next() orelse return null; - const parseFn = getDefaultParser(T); - return try parseFn(next_raw); - } - }; - } - - fn parse(arena: Allocator, bytes: []const u8) !TestManifest { - // The manifest is the last contiguous block of comments in the file - // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" - var start: ?usize = null; - var end: usize = bytes.len; - if (bytes.len > 0) { - var cursor: usize = bytes.len - 1; - while (true) { - // Move to beginning of line - while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1; - - if (std.mem.startsWith(u8, bytes[cursor..], "//")) { - start = cursor; // Contiguous comment line, include in manifest - } else { - if (start != null) break; // Encountered non-comment line, end of manifest - - // We ignore all-whitespace lines following the comment block, but anything else - // means that there is no manifest present. - if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) { - end = cursor; - } else break; // If it's not whitespace, there is no manifest - } - - // Move to previous line - if (cursor != 0) cursor -= 1 else break; - } - } - - const actual_start = start orelse return error.MissingTestManifest; - const manifest_bytes = bytes[actual_start..end]; - - var it = std.mem.tokenize(u8, manifest_bytes, "\r\n"); - - // First line is the test type - const tt: Type = blk: { - const line = it.next() orelse return error.MissingTestCaseType; - const raw = std.mem.trim(u8, line[2..], " \t"); - if (std.mem.eql(u8, raw, "error")) { - break :blk .@"error"; - } else if (std.mem.eql(u8, raw, "run")) { - break :blk .run; - } else if (std.mem.eql(u8, raw, "cli")) { - break :blk .cli; - } else { - std.log.warn("unknown test case type requested: {s}", .{raw}); - return error.UnknownTestCaseType; - } - }; - - var manifest: TestManifest = .{ - .type = tt, - .config_map = std.StringHashMap([]const u8).init(arena), - }; - - // Any subsequent line until a blank comment line is key=value(s) pair - while (it.next()) |line| { - const trimmed = std.mem.trim(u8, line[2..], " \t"); - if (trimmed.len == 0) break; - - // Parse key=value(s) - var kv_it = std.mem.split(u8, trimmed, "="); - const key = kv_it.first(); - try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig); - } - - // Finally, trailing is expected output - manifest.trailing_bytes = manifest_bytes[it.index..]; - - return manifest; - } - - fn getConfigForKey( - self: TestManifest, - key: []const u8, - comptime T: type, - ) ConfigValueIterator(T) { - const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key); - return ConfigValueIterator(T){ - .inner = std.mem.split(u8, bytes, ","), - }; - } - - fn getConfigForKeyAlloc( - self: TestManifest, - allocator: Allocator, - key: []const u8, - comptime T: type, - ) ![]const T { - var out = std.ArrayList(T).init(allocator); - defer out.deinit(); - var it = self.getConfigForKey(key, T); - while (try it.next()) |item| { - try out.append(item); - } - return try out.toOwnedSlice(); - } - - fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T { - var it = self.getConfigForKey(key, T); - const res = (try it.next()) orelse unreachable; - assert((try it.next()) == null); - return res; - } - - fn trailing(self: TestManifest) TrailingIterator { - return .{ - .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"), - }; - } - - fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 { - var out = std.ArrayList([]const u8).init(allocator); - defer out.deinit(); - var it = self.trailing(); - while (it.next()) |line| { - try out.append(line); - } - return try out.toOwnedSlice(); - } - - fn ParseFn(comptime T: type) type { - return fn ([]const u8) anyerror!T; - } - - fn getDefaultParser(comptime T: type) ParseFn(T) { - if (T == CrossTarget) return struct { - fn parse(str: []const u8) anyerror!T { - var opts = CrossTarget.ParseOptions{ - .arch_os_abi = str, - }; - return try CrossTarget.parse(opts); - } - }.parse; - - switch (@typeInfo(T)) { - .Int => return struct { - fn parse(str: []const u8) anyerror!T { - return try std.fmt.parseInt(T, str, 0); - } - }.parse, - .Bool => return struct { - fn parse(str: []const u8) anyerror!T { - const as_int = try std.fmt.parseInt(u1, str, 0); - return as_int > 0; - } - }.parse, - .Enum => return struct { - fn parse(str: []const u8) anyerror!T { - return std.meta.stringToEnum(T, str) orelse { - std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str }); - return error.UnknownEnumVariant; - }; - } - }.parse, - .Struct => @compileError("no default parser for " ++ @typeName(T)), - else => @compileError("no default parser for " ++ @typeName(T)), - } - } -}; - -const TestStrategy = enum { - /// Execute tests as independent compilations, unless they are explicitly - /// incremental ("foo.0.zig", "foo.1.zig", etc.) - independent, - /// Execute all tests as incremental updates to a single compilation. Explicitly - /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order - incremental, -}; - -/// Iterates a set of filenames extracting batches that are either incremental -/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.). -/// Assumes filenames are sorted. -const TestIterator = struct { - start: usize = 0, - end: usize = 0, - filenames: []const []const u8, - /// reset on each call to `next` - index: usize = 0, - - const Error = error{InvalidIncrementalTestIndex}; - - fn next(it: *TestIterator) Error!?[]const []const u8 { - try it.nextInner(); - if (it.start == it.end) return null; - return it.filenames[it.start..it.end]; - } - - fn nextInner(it: *TestIterator) Error!void { - it.start = it.end; - if (it.end == it.filenames.len) return; - if (it.end + 1 == it.filenames.len) { - it.end += 1; - return; - } - - const remaining = it.filenames[it.end..]; - it.index = 0; - while (it.index < remaining.len - 1) : (it.index += 1) { - // First, check if this file is part of an incremental update sequence - // Split filename into ".." - const prev_parts = getTestFileNameParts(remaining[it.index]); - const new_parts = getTestFileNameParts(remaining[it.index + 1]); - - // If base_name and file_ext match, these files are in the same test sequence - // and the new one should be the incremented version of the previous test - if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and - std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext)) - { - // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1 - if (prev_parts.test_index == null) - return error.InvalidIncrementalTestIndex; - if (new_parts.test_index == null) - return error.InvalidIncrementalTestIndex; - if (new_parts.test_index.? != prev_parts.test_index.? + 1) - return error.InvalidIncrementalTestIndex; - } else { - // This is not the same test sequence, so the new file must be the first file - // in a new sequence ("*.0.zig") or an independent test file ("*.zig") - if (new_parts.test_index != null and new_parts.test_index.? != 0) - return error.InvalidIncrementalTestIndex; - - it.end += it.index + 1; - break; - } - } else { - it.end += remaining.len; - } - } - - /// In the event of an `error.InvalidIncrementalTestIndex`, this function can - /// be used to find the current filename that was being processed. - /// Asserts the iterator hasn't reached the end. - fn currentFilename(it: TestIterator) []const u8 { - assert(it.end != it.filenames.len); - const remaining = it.filenames[it.end..]; - return remaining[it.index + 1]; - } -}; - -/// For a filename in the format ".X." or ".", returns -/// "", "" and X parsed as a decimal number. If X is not present, or -/// cannot be parsed as a decimal number, it is treated as part of -fn getTestFileNameParts(name: []const u8) struct { - base_name: []const u8, - file_ext: []const u8, - test_index: ?usize, -} { - const file_ext = std.fs.path.extension(name); - const trimmed = name[0 .. name.len - file_ext.len]; // Trim off "." - const maybe_index = std.fs.path.extension(trimmed); // Extract ".X" - - // Attempt to parse index - const index: ?usize = if (maybe_index.len > 0) - std.fmt.parseInt(usize, maybe_index[1..], 10) catch null - else - null; - - // Adjust "" extent based on parsing success - const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0; - return .{ - .base_name = name[0..base_name_end], - .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext, - .test_index = index, - }; -} - -/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig", -/// "foo.1.zig", etc.) are contiguous and appear in numerical order. -fn sortTestFilenames(filenames: [][]const u8) void { - const Context = struct { - pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool { - const a_parts = getTestFileNameParts(a); - const b_parts = getTestFileNameParts(b); - - // Sort ".X." based on "" and "" first - return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) { - .lt => true, - .gt => false, - .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) { - .lt => true, - .gt => false, - .eq => { - // a and b differ only in their ".X" part - - // Sort "." before any ".X." - if (a_parts.test_index) |a_index| { - if (b_parts.test_index) |b_index| { - // Make sure that incremental tests appear in linear order - return a_index < b_index; - } else { - return false; - } - } else { - return b_parts.test_index != null; - } - }, - }, - }; - } - }; - std.sort.sort([]const u8, filenames, Context{}, Context.lessThan); -} - -pub const TestContext = struct { - gpa: Allocator, - arena: Allocator, - cases: std.ArrayList(Case), - - pub const Update = struct { - /// The input to the current update. We simulate an incremental update - /// with the file's contents changed to this value each update. - /// - /// This value can change entirely between updates, which would be akin - /// to deleting the source file and creating a new one from scratch; or - /// you can keep it mostly consistent, with small changes, testing the - /// effects of the incremental compilation. - src: [:0]const u8, - name: []const u8, - case: union(enum) { - /// Check the main binary output file against an expected set of bytes. - /// This is most useful with, for example, `-ofmt=c`. - CompareObjectFile: []const u8, - /// An error update attempts to compile bad code, and ensures that it - /// fails to compile, and for the expected reasons. - /// A slice containing the expected errors *in sequential order*. - Error: []const ErrorMsg, - /// An execution update compiles and runs the input, testing the - /// stdout against the expected results - /// This is a slice containing the expected message. - Execution: []const u8, - /// A header update compiles the input with the equivalent of - /// `-femit-h` and tests the produced header against the - /// expected result - Header: []const u8, - }, - }; - - pub const File = struct { - /// Contents of the importable file. Doesn't yet support incremental updates. - src: [:0]const u8, - path: []const u8, - }; - - pub const DepModule = struct { - name: []const u8, - path: []const u8, - }; - - pub const Backend = enum { - stage1, - stage2, - llvm, - }; - - /// A `Case` consists of a list of `Update`. The same `Compilation` is used for each - /// update, so each update's source is treated as a single file being - /// updated by the test harness and incrementally compiled. - pub const Case = struct { - /// The name of the test case. This is shown if a test fails, and - /// otherwise ignored. - name: []const u8, - /// The platform the test targets. For non-native platforms, an emulator - /// such as QEMU is required for tests to complete. - target: CrossTarget, - /// In order to be able to run e.g. Execution updates, this must be set - /// to Executable. - output_mode: std.builtin.OutputMode, - optimize_mode: std.builtin.Mode = .Debug, - updates: std.ArrayList(Update), - emit_h: bool = false, - is_test: bool = false, - expect_exact: bool = false, - backend: Backend = .stage2, - link_libc: bool = false, - - files: std.ArrayList(File), - deps: std.ArrayList(DepModule), - - result: anyerror!void = {}, - - pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void { - case.files.append(.{ .path = name, .src = src }) catch @panic("out of memory"); - } - - pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void { - case.deps.append(.{ - .name = name, - .path = path, - }) catch @panic("out of memory"); - } - - /// Adds a subcase in which the module is updated with `src`, and a C - /// header is generated. - pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void { - self.emit_h = true; - self.updates.append(.{ - .src = src, - .name = "update", - .case = .{ .Header = result }, - }) catch @panic("out of memory"); - } - - /// Adds a subcase in which the module is updated with `src`, compiled, - /// run, and the output is tested against `result`. - pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { - self.updates.append(.{ - .src = src, - .name = "update", - .case = .{ .Execution = result }, - }) catch @panic("out of memory"); - } - - /// Adds a subcase in which the module is updated with `src`, compiled, - /// and the object file data is compared against `result`. - pub fn addCompareObjectFile(self: *Case, src: [:0]const u8, result: []const u8) void { - self.updates.append(.{ - .src = src, - .name = "update", - .case = .{ .CompareObjectFile = result }, - }) catch @panic("out of memory"); - } - - pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { - return self.addErrorNamed("update", src, errors); - } - - /// Adds a subcase in which the module is updated with `src`, which - /// should contain invalid input, and ensures that compilation fails - /// for the expected reasons, given in sequential order in `errors` in - /// the form `:line:column: error: message`. - pub fn addErrorNamed( - self: *Case, - name: []const u8, - src: [:0]const u8, - errors: []const []const u8, - ) void { - var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch @panic("out of memory"); - for (errors, 0..) |err_msg_line, i| { - if (std.mem.startsWith(u8, err_msg_line, "error: ")) { - array[i] = .{ - .plain = .{ - .msg = err_msg_line["error: ".len..], - .kind = .@"error", - .count = 1, - }, - }; - continue; - } else if (std.mem.startsWith(u8, err_msg_line, "note: ")) { - array[i] = .{ - .plain = .{ - .msg = err_msg_line["note: ".len..], - .kind = .note, - .count = 1, - }, - }; - continue; - } - // example: "file.zig:1:2: error: bad thing happened" - var it = std.mem.split(u8, err_msg_line, ":"); - const src_path = it.first(); - const line_text = it.next() orelse @panic("missing line"); - const col_text = it.next() orelse @panic("missing column"); - const kind_text = it.next() orelse @panic("missing 'error'/'note'"); - var msg = it.rest()[1..]; // skip over the space at end of "error: " - - const line: ?u32 = if (std.mem.eql(u8, line_text, "?")) - null - else - std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number"); - const column: ?u32 = if (std.mem.eql(u8, line_text, "?")) - null - else - std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number"); - const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error")) - .@"error" - else if (std.mem.eql(u8, kind_text, " note")) - .note - else - @panic("expected 'error'/'note'"); - - const line_0based: u32 = if (line) |n| blk: { - if (n == 0) { - print("{s}: line must be specified starting at one\n", .{self.name}); - return; - } - break :blk n - 1; - } else std.math.maxInt(u32); - - const column_0based: u32 = if (column) |n| blk: { - if (n == 0) { - print("{s}: line must be specified starting at one\n", .{self.name}); - return; - } - break :blk n - 1; - } else std.math.maxInt(u32); - - const suffix = " times)"; - const count = if (std.mem.endsWith(u8, msg, suffix)) count: { - const lparen = std.mem.lastIndexOfScalar(u8, msg, '(').?; - const count = std.fmt.parseInt(u32, msg[lparen + 1 .. msg.len - suffix.len], 10) catch @panic("bad error note count number"); - msg = msg[0 .. lparen - 1]; - break :count count; - } else 1; - - array[i] = .{ - .src = .{ - .src_path = src_path, - .msg = msg, - .line = line_0based, - .column = column_0based, - .kind = kind, - .count = count, - }, - }; - } - self.updates.append(.{ - .src = src, - .name = name, - .case = .{ .Error = array }, - }) catch @panic("out of memory"); - } - - /// Adds a subcase in which the module is updated with `src`, and - /// asserts that it compiles without issue - pub fn compiles(self: *Case, src: [:0]const u8) void { - self.addError(src, &[_][]const u8{}); - } - }; - - pub fn addExe( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - ) *Case { - ctx.cases.append(Case{ - .name = name, - .target = target, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Exe, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - /// Adds a test case for Zig input, producing an executable - pub fn exe(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - return ctx.addExe(name, target); - } - - pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - const prefixed_name = std.fmt.allocPrint(ctx.arena, "CBE: {s}", .{name}) catch - @panic("out of memory"); - var target_adjusted = target; - target_adjusted.ofmt = std.Target.ObjectFormat.c; - ctx.cases.append(Case{ - .name = prefixed_name, - .target = target_adjusted, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Exe, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - .link_libc = true, - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - /// Adds a test case that uses the LLVM backend to emit an executable. - /// Currently this implies linking libc, because only then we can generate a testable executable. - pub fn exeUsingLlvmBackend(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - ctx.cases.append(Case{ - .name = name, - .target = target, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Exe, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - .backend = .llvm, - .link_libc = true, - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - pub fn addObj( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - ) *Case { - ctx.cases.append(Case{ - .name = name, - .target = target, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Obj, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - pub fn addTest( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - ) *Case { - ctx.cases.append(Case{ - .name = name, - .target = target, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Exe, - .is_test = true, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - /// Adds a test case for Zig input, producing an object file. - pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - return ctx.addObj(name, target); - } - - /// Adds a test case for ZIR input, producing an object file. - pub fn objZIR(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - return ctx.addObj(name, target, .ZIR); - } - - /// Adds a test case for Zig or ZIR input, producing C code. - pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case { - var target_adjusted = target; - target_adjusted.ofmt = std.Target.ObjectFormat.c; - ctx.cases.append(Case{ - .name = name, - .target = target_adjusted, - .updates = std.ArrayList(Update).init(ctx.cases.allocator), - .output_mode = .Obj, - .files = std.ArrayList(File).init(ctx.arena), - .deps = std.ArrayList(DepModule).init(ctx.arena), - }) catch @panic("out of memory"); - return &ctx.cases.items[ctx.cases.items.len - 1]; - } - - pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target).addCompareObjectFile(src, zig_h ++ out); - } - - pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target).addHeader(src, zig_h ++ out); - } - - pub fn objErrStage1( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - const case = ctx.addObj(name, .{}); - case.backend = .stage1; - case.addError(src, expected_errors); - } - - pub fn testErrStage1( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - const case = ctx.addTest(name, .{}); - case.backend = .stage1; - case.addError(src, expected_errors); - } - - pub fn exeErrStage1( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - const case = ctx.addExe(name, .{}); - case.backend = .stage1; - case.addError(src, expected_errors); - } - - pub fn addCompareOutput( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout); - } - - /// Adds a test case that compiles the Zig source given in `src`, executes - /// it, runs it, and tests the output against `expected_stdout` - pub fn compareOutput( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - return ctx.addCompareOutput(name, src, expected_stdout); - } - - /// Adds a test case that compiles the ZIR source given in `src`, executes - /// it, runs it, and tests the output against `expected_stdout` - pub fn compareOutputZIR( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - ctx.addCompareOutput(name, .ZIR, src, expected_stdout); - } - - pub fn addTransform( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - result: [:0]const u8, - ) void { - ctx.addObj(name, target).addTransform(src, result); - } - - /// Adds a test case that compiles the Zig given in `src` to ZIR and tests - /// the ZIR against `result` - pub fn transform( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - result: [:0]const u8, - ) void { - ctx.addTransform(name, target, src, result); - } - - pub fn addError( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - ctx.addObj(name, target).addError(src, expected_errors); - } - - /// Adds a test case that ensures that the Zig given in `src` fails to - /// compile for the expected reasons, given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`. - pub fn compileError( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - ctx.addError(name, target, src, expected_errors); - } - - /// Adds a test case that ensures that the ZIR given in `src` fails to - /// compile for the expected reasons, given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`. - pub fn compileErrorZIR( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - ) void { - ctx.addError(name, target, .ZIR, src, expected_errors); - } - - pub fn addCompiles( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - ) void { - ctx.addObj(name, target).compiles(src); - } - - /// Adds a test case that asserts that the Zig given in `src` compiles - /// without any errors. - pub fn compiles( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - ) void { - ctx.addCompiles(name, target, src); - } - - /// Adds a test case that asserts that the ZIR given in `src` compiles - /// without any errors. - pub fn compilesZIR( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - ) void { - ctx.addCompiles(name, target, .ZIR, src); - } - - /// Adds a test case that first ensures that the Zig given in `src` fails - /// to compile for the reasons given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`, then - /// asserts that fixing the source (updating with `fixed_src`) isn't broken - /// by incremental compilation. - pub fn incrementalFailure( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - fixed_src: [:0]const u8, - ) void { - var case = ctx.addObj(name, target); - case.addError(src, expected_errors); - case.compiles(fixed_src); - } - - /// Adds a test case that first ensures that the ZIR given in `src` fails - /// to compile for the reasons given in sequential order in - /// `expected_errors` in the form `:line:column: error: message`, then - /// asserts that fixing the source (updating with `fixed_src`) isn't broken - /// by incremental compilation. - pub fn incrementalFailureZIR( - ctx: *TestContext, - name: []const u8, - target: CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - fixed_src: [:0]const u8, - ) void { - var case = ctx.addObj(name, target, .ZIR); - case.addError(src, expected_errors); - case.compiles(fixed_src); - } - - /// Adds a test for each file in the provided directory. - /// Testing strategy (TestStrategy) is inferred automatically from filenames. - /// Recurses nested directories. - /// - /// Each file should include a test manifest as a contiguous block of comments at - /// the end of the file. The first line should be the test type, followed by a set of - /// key-value config values, followed by a blank line, then the expected output. - pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.IterableDir) void { - var current_file: []const u8 = "none"; - ctx.addTestCasesFromDirInner(dir, ¤t_file) catch |err| { - std.debug.panic("test harness failed to process file '{s}': {s}\n", .{ - current_file, @errorName(err), - }); - }; - } - - fn addTestCasesFromDirInner( - ctx: *TestContext, - iterable_dir: std.fs.IterableDir, - /// This is kept up to date with the currently being processed file so - /// that if any errors occur the caller knows it happened during this file. - current_file: *[]const u8, - ) !void { - var it = try iterable_dir.walk(ctx.arena); - var filenames = std.ArrayList([]const u8).init(ctx.arena); - - while (try it.next()) |entry| { - if (entry.kind != .File) continue; - - // Ignore stuff such as .swp files - switch (Compilation.classifyFileExt(entry.basename)) { - .unknown => continue, - else => {}, - } - try filenames.append(try ctx.arena.dupe(u8, entry.path)); - } - - // Sort filenames, so that incremental tests are contiguous and in-order - sortTestFilenames(filenames.items); - - var test_it = TestIterator{ .filenames = filenames.items }; - while (test_it.next()) |maybe_batch| { - const batch = maybe_batch orelse break; - const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent; - var cases = std.ArrayList(usize).init(ctx.arena); - - for (batch) |filename| { - current_file.* = filename; - - const max_file_size = 10 * 1024 * 1024; - const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0); - - // Parse the manifest - var manifest = try TestManifest.parse(ctx.arena, src); - - if (cases.items.len == 0) { - const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); - const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget); - const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); - const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); - - const name_prefix = blk: { - const ext_index = std.mem.lastIndexOfScalar(u8, current_file.*, '.') orelse - return error.InvalidFilename; - const index = std.mem.lastIndexOfScalar(u8, current_file.*[0..ext_index], '.') orelse ext_index; - break :blk current_file.*[0..index]; - }; - - // Cross-product to get all possible test combinations - for (backends) |backend| { - for (targets) |target| { - const name = try std.fmt.allocPrint(ctx.arena, "{s} ({s}, {s})", .{ - name_prefix, - @tagName(backend), - try target.zigTriple(ctx.arena), - }); - const next = ctx.cases.items.len; - try ctx.cases.append(.{ - .name = name, - .target = target, - .backend = backend, - .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), - .is_test = is_test, - .output_mode = output_mode, - .link_libc = backend == .llvm, - .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), - .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), - }); - try cases.append(next); - } - } - } - - for (cases.items) |case_index| { - const case = &ctx.cases.items[case_index]; - switch (manifest.type) { - .@"error" => { - const errors = try manifest.trailingAlloc(ctx.arena); - switch (strategy) { - .independent => { - case.addError(src, errors); - }, - .incremental => { - case.addErrorNamed("update", src, errors); - }, - } - }, - .run => { - var output = std.ArrayList(u8).init(ctx.arena); - var trailing_it = manifest.trailing(); - while (trailing_it.next()) |line| { - try output.appendSlice(line); - try output.append('\n'); - } - if (output.items.len > 0) { - try output.resize(output.items.len - 1); - } - case.addCompareOutput(src, try output.toOwnedSlice()); - }, - .cli => @panic("TODO cli tests"), - } - } - } - } else |err| { - // make sure the current file is set to the file that produced an error - current_file.* = test_it.currentFilename(); - return err; - } - } - - fn init(gpa: Allocator, arena: Allocator) TestContext { - return .{ - .gpa = gpa, - .cases = std.ArrayList(Case).init(gpa), - .arena = arena, - }; - } - - fn deinit(self: *TestContext) void { - for (self.cases.items) |case| { - for (case.updates.items) |u| { - if (u.case == .Error) { - case.updates.allocator.free(u.case.Error); - } - } - case.updates.deinit(); - } - self.cases.deinit(); - self.* = undefined; - } - - fn run(self: *TestContext) !void { - const host = try std.zig.system.NativeTargetInfo.detect(.{}); - const zig_exe_path = try std.process.getEnvVarOwned(self.arena, "ZIG_EXE"); - - var progress = std.Progress{}; - const root_node = progress.start("compiler", self.cases.items.len); - defer root_node.end(); - - var zig_lib_directory = try introspect.findZigLibDir(self.gpa); - defer zig_lib_directory.handle.close(); - defer self.gpa.free(zig_lib_directory.path.?); - - var aux_thread_pool: ThreadPool = undefined; - try aux_thread_pool.init(.{ .allocator = self.gpa }); - defer aux_thread_pool.deinit(); - - // Use the same global cache dir for all the tests, such that we for example don't have to - // rebuild musl libc for every case (when LLVM backend is enabled). - var global_tmp = std.testing.tmpDir(.{}); - defer global_tmp.cleanup(); - - var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{}); - defer cache_dir.close(); - const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path }); - defer self.gpa.free(tmp_dir_path); - - const global_cache_directory: Compilation.Directory = .{ - .handle = cache_dir, - .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }), - }; - defer self.gpa.free(global_cache_directory.path.?); - - { - for (self.cases.items) |*case| { - if (build_options.skip_non_native) { - if (case.target.getCpuArch() != builtin.cpu.arch) - continue; - if (case.target.getObjectFormat() != builtin.object_format) - continue; - } - - // Skip tests that require LLVM backend when it is not available - if (!build_options.have_llvm and case.backend == .llvm) - continue; - - if (skip_stage1 and case.backend == .stage1) - continue; - - if (build_options.test_filter) |test_filter| { - if (std.mem.indexOf(u8, case.name, test_filter) == null) continue; - } - - var prg_node = root_node.start(case.name, case.updates.items.len); - prg_node.activate(); - defer prg_node.end(); - - case.result = runOneCase( - self.gpa, - &prg_node, - case.*, - zig_lib_directory, - zig_exe_path, - &aux_thread_pool, - global_cache_directory, - host, - ); - } - } - - var fail_count: usize = 0; - for (self.cases.items) |*case| { - case.result catch |err| { - fail_count += 1; - print("{s} failed: {s}\n", .{ case.name, @errorName(err) }); - }; - } - - if (fail_count != 0) { - print("{d} tests failed\n", .{fail_count}); - return error.TestFailed; - } - } - - fn runOneCase( - allocator: Allocator, - root_node: *std.Progress.Node, - case: Case, - zig_lib_directory: Compilation.Directory, - zig_exe_path: []const u8, - thread_pool: *ThreadPool, - global_cache_directory: Compilation.Directory, - host: std.zig.system.NativeTargetInfo, - ) !void { - const target_info = try std.zig.system.NativeTargetInfo.detect(case.target); - const target = target_info.target; - - var arena_allocator = std.heap.ArenaAllocator.init(allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); - defer cache_dir.close(); - - const tmp_dir_path = try std.fs.path.join( - arena, - &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path }, - ); - const tmp_dir_path_plus_slash = try std.fmt.allocPrint( - arena, - "{s}" ++ std.fs.path.sep_str, - .{tmp_dir_path}, - ); - const local_cache_path = try std.fs.path.join( - arena, - &[_][]const u8{ tmp_dir_path, "zig-cache" }, - ); - - for (case.files.items) |file| { - try tmp.dir.writeFile(file.path, file.src); - } - - if (case.backend == .stage1) { - // stage1 backend has limitations: - // * leaks memory - // * calls exit() when a compile error happens - // * cannot handle updates - // because of this we must spawn a child process rather than - // using Compilation directly. - - if (!std.process.can_spawn) { - print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)}); - return; // Pass test. - } - - assert(case.updates.items.len == 1); - const update = case.updates.items[0]; - try tmp.dir.writeFile(tmp_src_path, update.src); - - var zig_args = std.ArrayList([]const u8).init(arena); - try zig_args.append(zig_exe_path); - - if (case.is_test) { - try zig_args.append("test"); - } else if (update.case == .Execution) { - try zig_args.append("run"); - } else switch (case.output_mode) { - .Obj => try zig_args.append("build-obj"), - .Exe => try zig_args.append("build-exe"), - .Lib => try zig_args.append("build-lib"), - } - - try zig_args.append(try std.fs.path.join(arena, &.{ tmp_dir_path, tmp_src_path })); - - try zig_args.append("--name"); - try zig_args.append("test"); - - try zig_args.append("--cache-dir"); - try zig_args.append(local_cache_path); - - try zig_args.append("--global-cache-dir"); - try zig_args.append(global_cache_directory.path orelse "."); - - if (!case.target.isNative()) { - try zig_args.append("-target"); - try zig_args.append(try target.zigTriple(arena)); - } - - try zig_args.append("-O"); - try zig_args.append(@tagName(case.optimize_mode)); - - // Prevent sub-process progress bar from interfering with the - // one in this parent process. - try zig_args.append("--color"); - try zig_args.append("off"); - - const result = try std.ChildProcess.exec(.{ - .allocator = arena, - .argv = zig_args.items, - }); - switch (update.case) { - .Error => |case_error_list| { - switch (result.term) { - .Exited => |code| { - if (code == 0) { - dumpArgs(zig_args.items); - return error.CompilationIncorrectlySucceeded; - } - }, - else => { - std.debug.print("{s}", .{result.stderr}); - dumpArgs(zig_args.items); - return error.CompilationCrashed; - }, - } - var ok = true; - if (case.expect_exact) { - var err_iter = std.mem.split(u8, result.stderr, "\n"); - var i: usize = 0; - ok = while (err_iter.next()) |line| : (i += 1) { - if (i >= case_error_list.len) break false; - const expected = try std.mem.replaceOwned( - u8, - arena, - try std.fmt.allocPrint(arena, "{s}", .{case_error_list[i]}), - "${DIR}", - tmp_dir_path_plus_slash, - ); - - if (std.mem.indexOf(u8, line, expected) == null) break false; - continue; - } else true; - - ok = ok and i == case_error_list.len; - - if (!ok) { - print("\n======== Expected these compile errors: ========\n", .{}); - for (case_error_list) |msg| { - const expected = try std.fmt.allocPrint(arena, "{s}", .{msg}); - print("{s}\n", .{expected}); - } - } - } else { - for (case_error_list) |msg| { - const expected = try std.mem.replaceOwned( - u8, - arena, - try std.fmt.allocPrint(arena, "{s}", .{msg}), - "${DIR}", - tmp_dir_path_plus_slash, - ); - if (std.mem.indexOf(u8, result.stderr, expected) == null) { - print( - \\ - \\=========== Expected compile error: ============ - \\{s} - \\ - , .{expected}); - ok = false; - break; - } - } - } - - if (!ok) { - print( - \\================= Full output: ================= - \\{s} - \\================================================ - \\ - , .{result.stderr}); - return error.TestFailed; - } - }, - .CompareObjectFile => @panic("TODO implement in the test harness"), - .Execution => |expected_stdout| { - switch (result.term) { - .Exited => |code| { - if (code != 0) { - std.debug.print("{s}", .{result.stderr}); - dumpArgs(zig_args.items); - return error.CompilationFailed; - } - }, - else => { - std.debug.print("{s}", .{result.stderr}); - dumpArgs(zig_args.items); - return error.CompilationCrashed; - }, - } - try std.testing.expectEqualStrings("", result.stderr); - try std.testing.expectEqualStrings(expected_stdout, result.stdout); - }, - .Header => @panic("TODO implement in the test harness"), - } - return; - } - - const zig_cache_directory: Compilation.Directory = .{ - .handle = cache_dir, - .path = local_cache_path, - }; - - var main_pkg: Package = .{ - .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, - .root_src_path = tmp_src_path, - }; - defer { - var it = main_pkg.table.iterator(); - while (it.next()) |kv| { - allocator.free(kv.key_ptr.*); - kv.value_ptr.*.destroy(allocator); - } - main_pkg.table.deinit(allocator); - } - - for (case.deps.items) |dep| { - var pkg = try Package.create( - allocator, - tmp_dir_path, - dep.path, - ); - errdefer pkg.destroy(allocator); - try main_pkg.add(allocator, dep.name, pkg); - } - - const bin_name = try std.zig.binNameAlloc(arena, .{ - .root_name = "test_case", - .target = target, - .output_mode = case.output_mode, - }); - - const emit_directory: Compilation.Directory = .{ - .path = tmp_dir_path, - .handle = tmp.dir, - }; - const emit_bin: Compilation.EmitLoc = .{ - .directory = emit_directory, - .basename = bin_name, - }; - const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{ - .directory = emit_directory, - .basename = "test_case.h", - } else null; - const use_llvm: bool = switch (case.backend) { - .llvm => true, - else => false, - }; - const comp = try Compilation.create(allocator, .{ - .local_cache_directory = zig_cache_directory, - .global_cache_directory = global_cache_directory, - .zig_lib_directory = zig_lib_directory, - .thread_pool = thread_pool, - .root_name = "test_case", - .target = target, - // TODO: support tests for object file building, and library builds - // and linking. This will require a rework to support multi-file - // tests. - .output_mode = case.output_mode, - .is_test = case.is_test, - .optimize_mode = case.optimize_mode, - .emit_bin = emit_bin, - .emit_h = emit_h, - .main_pkg = &main_pkg, - .keep_source_files_loaded = true, - .is_native_os = case.target.isNativeOs(), - .is_native_abi = case.target.isNativeAbi(), - .dynamic_linker = target_info.dynamic_linker.get(), - .link_libc = case.link_libc, - .use_llvm = use_llvm, - .self_exe_path = zig_exe_path, - // TODO instead of turning off color, pass in a std.Progress.Node - .color = .off, - .reference_trace = 0, - // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in - // until the auto-select mechanism deems them worthy - .use_lld = switch (case.backend) { - .stage2 => false, - else => null, - }, - }); - defer comp.destroy(); - - update: for (case.updates.items, 0..) |update, update_index| { - var update_node = root_node.start(update.name, 3); - update_node.activate(); - defer update_node.end(); - - var sync_node = update_node.start("write", 0); - sync_node.activate(); - try tmp.dir.writeFile(tmp_src_path, update.src); - sync_node.end(); - - var module_node = update_node.start("parse/analysis/codegen", 0); - module_node.activate(); - try comp.makeBinFileWritable(); - try comp.update(&module_node); - module_node.end(); - - if (update.case != .Error) { - var all_errors = try comp.getAllErrorsAlloc(); - defer all_errors.deinit(allocator); - if (all_errors.errorMessageCount() > 0) { - all_errors.renderToStdErr(std.debug.detectTTYConfig(std.io.getStdErr())); - // TODO print generated C code - return error.UnexpectedCompileErrors; - } - } - - switch (update.case) { - .Header => |expected_output| { - var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only }); - defer file.close(); - const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); - - try std.testing.expectEqualStrings(expected_output, out); - }, - .CompareObjectFile => |expected_output| { - var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only }); - defer file.close(); - const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); - - try std.testing.expectEqualStrings(expected_output, out); - }, - .Error => |case_error_list| { - var test_node = update_node.start("assert", 0); - test_node.activate(); - defer test_node.end(); - - const handled_errors = try arena.alloc(bool, case_error_list.len); - std.mem.set(bool, handled_errors, false); - - var actual_errors = try comp.getAllErrorsAlloc(); - defer actual_errors.deinit(allocator); - - var any_failed = false; - var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator); - defer notes_to_check.deinit(); - - for (actual_errors.list) |actual_error| { - for (case_error_list, 0..) |case_msg, i| { - if (handled_errors[i]) continue; - - const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg; - switch (actual_error) { - .src => |actual_msg| { - for (actual_msg.notes) |*note| { - try notes_to_check.append(note); - } - - if (ex_tag != .src) continue; - - const src_path_ok = case_msg.src.src_path.len == 0 or - std.mem.eql(u8, case_msg.src.src_path, actual_msg.src_path); - - const expected_msg = try std.mem.replaceOwned( - u8, - arena, - case_msg.src.msg, - "${DIR}", - tmp_dir_path_plus_slash, - ); - - var buf: [1024]u8 = undefined; - const rendered_msg = blk: { - var msg: Compilation.AllErrors.Message = actual_error; - msg.src.src_path = case_msg.src.src_path; - msg.src.notes = &.{}; - msg.src.source_line = null; - var fib = std.io.fixedBufferStream(&buf); - try msg.renderToWriter(.no_color, fib.writer(), "error", .Red, 0); - var it = std.mem.split(u8, fib.getWritten(), "error: "); - _ = it.first(); - const rendered = it.rest(); - break :blk rendered[0 .. rendered.len - 1]; // trim final newline - }; - - if (src_path_ok and - (case_msg.src.line == std.math.maxInt(u32) or - actual_msg.line == case_msg.src.line) and - (case_msg.src.column == std.math.maxInt(u32) or - actual_msg.column == case_msg.src.column) and - std.mem.eql(u8, expected_msg, rendered_msg) and - case_msg.src.kind == .@"error" and - actual_msg.count == case_msg.src.count) - { - handled_errors[i] = true; - break; - } - }, - .plain => |plain| { - if (ex_tag != .plain) continue; - - if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and - case_msg.plain.kind == .@"error" and - case_msg.plain.count == plain.count) - { - handled_errors[i] = true; - break; - } - }, - } - } else { - print( - "\nUnexpected error:\n{s}\n{}\n{s}", - .{ hr, ErrorMsg.init(actual_error, .@"error"), hr }, - ); - any_failed = true; - } - } - while (notes_to_check.popOrNull()) |note| { - for (case_error_list, 0..) |case_msg, i| { - const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg; - switch (note.*) { - .src => |actual_msg| { - for (actual_msg.notes) |*sub_note| { - try notes_to_check.append(sub_note); - } - if (ex_tag != .src) continue; - - const expected_msg = try std.mem.replaceOwned( - u8, - arena, - case_msg.src.msg, - "${DIR}", - tmp_dir_path_plus_slash, - ); - - if ((case_msg.src.line == std.math.maxInt(u32) or - actual_msg.line == case_msg.src.line) and - (case_msg.src.column == std.math.maxInt(u32) or - actual_msg.column == case_msg.src.column) and - std.mem.eql(u8, expected_msg, actual_msg.msg) and - case_msg.src.kind == .note and - actual_msg.count == case_msg.src.count) - { - handled_errors[i] = true; - break; - } - }, - .plain => |plain| { - if (ex_tag != .plain) continue; - - if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and - case_msg.plain.kind == .note and - case_msg.plain.count == plain.count) - { - handled_errors[i] = true; - break; - } - }, - } - } else { - print( - "\nUnexpected note:\n{s}\n{}\n{s}", - .{ hr, ErrorMsg.init(note.*, .note), hr }, - ); - any_failed = true; - } - } - - for (handled_errors, 0..) |handled, i| { - if (!handled) { - print( - "\nExpected error not found:\n{s}\n{}\n{s}", - .{ hr, case_error_list[i], hr }, - ); - any_failed = true; - } - } - - if (any_failed) { - print("\nupdate_index={d}\n", .{update_index}); - return error.WrongCompileErrors; - } - }, - .Execution => |expected_stdout| { - if (!std.process.can_spawn) { - print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)}); - continue :update; // Pass test. - } - - update_node.setEstimatedTotalItems(4); - - var argv = std.ArrayList([]const u8).init(allocator); - defer argv.deinit(); - - var exec_result = x: { - var exec_node = update_node.start("execute", 0); - exec_node.activate(); - defer exec_node.end(); - - // We go out of our way here to use the unique temporary directory name in - // the exe_path so that it makes its way into the cache hash, avoiding - // cache collisions from multiple threads doing `zig run` at the same time - // on the same test_case.c input filename. - const ss = std.fs.path.sep_str; - const exe_path = try std.fmt.allocPrint( - arena, - ".." ++ ss ++ "{s}" ++ ss ++ "{s}", - .{ &tmp.sub_path, bin_name }, - ); - if (case.target.ofmt != null and case.target.ofmt.? == .c) { - if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) { - // We wouldn't be able to run the compiled C code. - continue :update; // Pass test. - } - try argv.appendSlice(&[_][]const u8{ - zig_exe_path, - "run", - "-cflags", - "-std=c99", - "-pedantic", - "-Werror", - "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875 - "--", - "-lc", - exe_path, - }); - if (zig_lib_directory.path) |p| { - try argv.appendSlice(&.{ "-I", p }); - } - } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) { - .native => { - if (case.backend == .stage2 and case.target.getCpuArch() == .arm) { - // https://github.com/ziglang/zig/issues/13623 - continue :update; // Pass test. - } - try argv.append(exe_path); - }, - .bad_dl, .bad_os_or_cpu => continue :update, // Pass test. - - .rosetta => if (enable_rosetta) { - try argv.append(exe_path); - } else { - continue :update; // Rosetta not available, pass test. - }, - - .qemu => |qemu_bin_name| if (enable_qemu) { - const need_cross_glibc = target.isGnuLibC() and case.link_libc; - const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc) - glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test - else - null; - try argv.append(qemu_bin_name); - if (glibc_dir_arg) |dir| { - const linux_triple = try target.linuxTriple(arena); - const full_dir = try std.fs.path.join(arena, &[_][]const u8{ - dir, - linux_triple, - }); - - try argv.append("-L"); - try argv.append(full_dir); - } - try argv.append(exe_path); - } else { - continue :update; // QEMU not available; pass test. - }, - - .wine => |wine_bin_name| if (enable_wine) { - try argv.append(wine_bin_name); - try argv.append(exe_path); - } else { - continue :update; // Wine not available; pass test. - }, - - .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) { - try argv.append(wasmtime_bin_name); - try argv.append("--dir=."); - try argv.append(exe_path); - } else { - continue :update; // wasmtime not available; pass test. - }, - - .darling => |darling_bin_name| if (enable_darling) { - try argv.append(darling_bin_name); - // Since we use relative to cwd here, we invoke darling with - // "shell" subcommand. - try argv.append("shell"); - try argv.append(exe_path); - } else { - continue :update; // Darling not available; pass test. - }, - } - - try comp.makeBinFileExecutable(); - - while (true) { - break :x std.ChildProcess.exec(.{ - .allocator = allocator, - .argv = argv.items, - .cwd_dir = tmp.dir, - .cwd = tmp_dir_path, - }) catch |err| switch (err) { - error.FileBusy => { - // There is a fundamental design flaw in Unix systems with how - // ETXTBSY interacts with fork+exec. - // https://github.com/golang/go/issues/22315 - // https://bugs.openjdk.org/browse/JDK-8068370 - // Unfortunately, this could be a real error, but we can't - // tell the difference here. - continue; - }, - else => { - print("\n{s}.{d} The following command failed with {s}:\n", .{ - case.name, update_index, @errorName(err), - }); - dumpArgs(argv.items); - return error.ChildProcessExecution; - }, - }; - } - }; - var test_node = update_node.start("test", 0); - test_node.activate(); - defer test_node.end(); - defer allocator.free(exec_result.stdout); - defer allocator.free(exec_result.stderr); - switch (exec_result.term) { - .Exited => |code| { - if (code != 0) { - print("\n{s}\n{s}: execution exited with code {d}:\n", .{ - exec_result.stderr, case.name, code, - }); - dumpArgs(argv.items); - return error.ChildProcessExecution; - } - }, - else => { - print("\n{s}\n{s}: execution crashed:\n", .{ - exec_result.stderr, case.name, - }); - dumpArgs(argv.items); - return error.ChildProcessExecution; - }, - } - try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout); - // We allow stderr to have garbage in it because wasmtime prints a - // warning about --invoke even though we don't pass it. - //std.testing.expectEqualStrings("", exec_result.stderr); - }, - } - } - } -}; - -fn dumpArgs(argv: []const []const u8) void { - for (argv) |arg| { - print("{s} ", .{arg}); - } - print("\n", .{}); -} - -const tmp_src_path = "tmp.zig"; diff --git a/test/cases.zig b/test/cases.zig index 412b4cb5e2..ffe046c70e 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1,8 +1,8 @@ const std = @import("std"); -const TestContext = @import("../src/test.zig").TestContext; +const Cases = @import("src/Cases.zig"); -pub fn addCases(ctx: *TestContext) !void { - try @import("compile_errors.zig").addCases(ctx); - try @import("stage2/cbe.zig").addCases(ctx); - try @import("stage2/nvptx.zig").addCases(ctx); +pub fn addCases(cases: *Cases) !void { + try @import("compile_errors.zig").addCases(cases); + try @import("cbe.zig").addCases(cases); + try @import("nvptx.zig").addCases(cases); } diff --git a/test/cases/compile_errors/access_inactive_union_field_comptime.zig b/test/cases/compile_errors/access_inactive_union_field_comptime.zig index d990a85f9e..2098b19d14 100644 --- a/test/cases/compile_errors/access_inactive_union_field_comptime.zig +++ b/test/cases/compile_errors/access_inactive_union_field_comptime.zig @@ -21,3 +21,4 @@ pub export fn entry1() void { // :9:15: error: access of union field 'a' while field 'b' is active // :2:21: note: union declared here // :14:16: error: access of union field 'a' while field 'b' is active +// :2:21: note: union declared here diff --git a/test/cases/compile_errors/bad_import.zig b/test/cases/compile_errors/bad_import.zig index 49e78a4be4..e624d7104c 100644 --- a/test/cases/compile_errors/bad_import.zig +++ b/test/cases/compile_errors/bad_import.zig @@ -4,4 +4,4 @@ const bogus = @import("bogus-does-not-exist.zig",); // backend=stage2 // target=native // -// :1:23: error: unable to load '${DIR}bogus-does-not-exist.zig': FileNotFound +// bogus-does-not-exist.zig': FileNotFound diff --git a/test/cases/compile_errors/condition_comptime_reason_explained.zig b/test/cases/compile_errors/condition_comptime_reason_explained.zig index 332ae8afc8..d0193986a8 100644 --- a/test/cases/compile_errors/condition_comptime_reason_explained.zig +++ b/test/cases/compile_errors/condition_comptime_reason_explained.zig @@ -45,4 +45,6 @@ pub export fn entry2() void { // :22:13: error: unable to resolve comptime value // :22:13: note: condition in comptime switch must be comptime-known // :21:17: note: expression is evaluated at comptime because the function returns a comptime-only type 'tmp.S' +// :2:12: note: struct requires comptime because of this field +// :2:12: note: use '*const fn() void' for a function pointer type // :32:19: note: called from here diff --git a/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig b/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig index 2a64326093..ace90bccfc 100644 --- a/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig +++ b/test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig @@ -32,6 +32,7 @@ export fn d() void { // :3:8: error: opaque types have unknown size and therefore cannot be directly embedded in structs // :1:11: note: opaque declared here // :7:10: error: opaque types have unknown size and therefore cannot be directly embedded in unions +// :1:11: note: opaque declared here // :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs // :18:22: note: opaque declared here // :24:23: error: opaque types have unknown size and therefore cannot be directly embedded in structs diff --git a/test/cases/compile_errors/extern_function_with_comptime_parameter.zig b/test/cases/compile_errors/extern_function_with_comptime_parameter.zig index de69fa409f..58f15f7fab 100644 --- a/test/cases/compile_errors/extern_function_with_comptime_parameter.zig +++ b/test/cases/compile_errors/extern_function_with_comptime_parameter.zig @@ -12,6 +12,6 @@ comptime { _ = entry2; } // backend=stage2 // target=native // -// :1:15: error: comptime parameters not allowed in function with calling convention 'C' // :5:30: error: comptime parameters not allowed in function with calling convention 'C' // :6:30: error: generic parameters not allowed in function with calling convention 'C' +// :1:15: error: comptime parameters not allowed in function with calling convention 'C' diff --git a/test/cases/compile_errors/function_parameter_is_opaque.zig b/test/cases/compile_errors/function_parameter_is_opaque.zig index 1f92274577..57c89bd7f4 100644 --- a/test/cases/compile_errors/function_parameter_is_opaque.zig +++ b/test/cases/compile_errors/function_parameter_is_opaque.zig @@ -27,4 +27,5 @@ export fn entry4() void { // :1:17: note: opaque declared here // :8:28: error: parameter of type '@TypeOf(null)' not allowed // :12:8: error: parameter of opaque type 'tmp.FooType' not allowed +// :1:17: note: opaque declared here // :17:8: error: parameter of type '@TypeOf(null)' not allowed diff --git a/test/cases/compile_errors/helpful_return_type_error_message.zig b/test/cases/compile_errors/helpful_return_type_error_message.zig index 871e948537..83342c7ec3 100644 --- a/test/cases/compile_errors/helpful_return_type_error_message.zig +++ b/test/cases/compile_errors/helpful_return_type_error_message.zig @@ -24,9 +24,9 @@ export fn quux() u32 { // :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' // :7:17: note: function cannot return an error // :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' -// :10:17: note: function cannot return an error // :11:15: note: cannot convert error union to payload type // :11:15: note: consider using 'try', 'catch', or 'if' +// :10:17: note: function cannot return an error // :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' // :15:14: note: cannot convert error union to payload type // :15:14: note: consider using 'try', 'catch', or 'if' diff --git a/test/cases/compile_errors/implicit_semicolon-block_expr.zig b/test/cases/compile_errors/implicit_semicolon-block_expr.zig index 7dd82b897b..bab8ec29c0 100644 --- a/test/cases/compile_errors/implicit_semicolon-block_expr.zig +++ b/test/cases/compile_errors/implicit_semicolon-block_expr.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-block_statement.zig b/test/cases/compile_errors/implicit_semicolon-block_statement.zig index 189ba84d98..912ccbc790 100644 --- a/test/cases/compile_errors/implicit_semicolon-block_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-block_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig b/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig index decbc352e8..e8dc8bb534 100644 --- a/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-comptime_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = comptime {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig b/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig index d17db15924..afc1798669 100644 --- a/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-comptime_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; comptime ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-defer.zig b/test/cases/compile_errors/implicit_semicolon-defer.zig index 57fd3a2626..e91dbae7f8 100644 --- a/test/cases/compile_errors/implicit_semicolon-defer.zig +++ b/test/cases/compile_errors/implicit_semicolon-defer.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; defer ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-for_expression.zig b/test/cases/compile_errors/implicit_semicolon-for_expression.zig index c751384e11..1fbe4dd3ad 100644 --- a/test/cases/compile_errors/implicit_semicolon-for_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-for_expression.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; _ = for(foo()) |_| {} var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-for_statement.zig b/test/cases/compile_errors/implicit_semicolon-for_statement.zig index 14709cef4c..2830293b70 100644 --- a/test/cases/compile_errors/implicit_semicolon-for_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-for_statement.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; for(foo()) |_| ({}) var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig index 72a4fa7d3e..9e99421cd1 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} else if(true) {} else {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig index 95135006ba..e2e7b7e3b3 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) else if(true) ({}) else ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig index a29636bd1d..33ca6ab600 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} else if(true) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig b/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig index c62430a0a2..e3d004fee1 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) else if(true) ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig b/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig index d5bee6e52b..a23809528b 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} else {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig b/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig index 94df128626..ed01aa7df2 100644 --- a/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if-else_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) else ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if_expression.zig b/test/cases/compile_errors/implicit_semicolon-if_expression.zig index 339a5378cf..e28f8616e2 100644 --- a/test/cases/compile_errors/implicit_semicolon-if_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-if_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = if(true) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-if_statement.zig b/test/cases/compile_errors/implicit_semicolon-if_statement.zig index b8ccb5e401..3067c07767 100644 --- a/test/cases/compile_errors/implicit_semicolon-if_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-if_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; if(true) ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-test_expression.zig b/test/cases/compile_errors/implicit_semicolon-test_expression.zig index 2a37c0aa0e..0bb345b387 100644 --- a/test/cases/compile_errors/implicit_semicolon-test_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-test_expression.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; _ = if (foo()) |_| {} var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-test_statement.zig b/test/cases/compile_errors/implicit_semicolon-test_statement.zig index afe00eba75..3a4eb8b5ba 100644 --- a/test/cases/compile_errors/implicit_semicolon-test_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-test_statement.zig @@ -3,7 +3,10 @@ export fn entry() void { var good = {}; if (foo()) |_| ({}) var bad = {}; + _ = good; + _ = bad; } +fn foo() void {} // error // backend=stage2 diff --git a/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig b/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig index 5587627597..f05c70bc14 100644 --- a/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = while(true):({}) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig b/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig index 9bebe3861e..2d27824f6b 100644 --- a/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; while(true):({}) ({}) var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-while_expression.zig b/test/cases/compile_errors/implicit_semicolon-while_expression.zig index df388a7c39..4b39ed7c16 100644 --- a/test/cases/compile_errors/implicit_semicolon-while_expression.zig +++ b/test/cases/compile_errors/implicit_semicolon-while_expression.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; _ = while(true) {} var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/implicit_semicolon-while_statement.zig b/test/cases/compile_errors/implicit_semicolon-while_statement.zig index d9ed3d1e2a..538a56faf1 100644 --- a/test/cases/compile_errors/implicit_semicolon-while_statement.zig +++ b/test/cases/compile_errors/implicit_semicolon-while_statement.zig @@ -3,6 +3,8 @@ export fn entry() void { var good = {}; while(true) 1 var bad = {}; + _ = good; + _ = bad; } // error diff --git a/test/cases/compile_errors/invalid_member_of_builtin_enum.zig b/test/cases/compile_errors/invalid_member_of_builtin_enum.zig index b0a176d792..f3ea66ae1c 100644 --- a/test/cases/compile_errors/invalid_member_of_builtin_enum.zig +++ b/test/cases/compile_errors/invalid_member_of_builtin_enum.zig @@ -9,4 +9,4 @@ export fn entry() void { // target=native // // :3:38: error: enum 'builtin.OptimizeMode' has no member named 'x86' -// :?:18: note: enum declared here +// : note: enum declared here diff --git a/test/cases/compile_errors/invalid_store_to_comptime_field.zig b/test/cases/compile_errors/invalid_store_to_comptime_field.zig index 0f444ba78c..fd6fff5e17 100644 --- a/test/cases/compile_errors/invalid_store_to_comptime_field.zig +++ b/test/cases/compile_errors/invalid_store_to_comptime_field.zig @@ -73,11 +73,11 @@ pub export fn entry8() void { // // :6:19: error: value stored in comptime field does not match the default value of the field // :14:19: error: value stored in comptime field does not match the default value of the field -// :53:16: error: value stored in comptime field does not match the default value of the field // :19:38: error: value stored in comptime field does not match the default value of the field // :31:19: error: value stored in comptime field does not match the default value of the field // :25:29: note: default value set here // :41:16: error: value stored in comptime field does not match the default value of the field // :45:12: error: value stored in comptime field does not match the default value of the field +// :53:16: error: value stored in comptime field does not match the default value of the field // :66:43: error: value stored in comptime field does not match the default value of the field // :59:35: error: value stored in comptime field does not match the default value of the field diff --git a/test/cases/compile_errors/invalid_struct_field.zig b/test/cases/compile_errors/invalid_struct_field.zig index 4450375cb8..ff8c96a0b6 100644 --- a/test/cases/compile_errors/invalid_struct_field.zig +++ b/test/cases/compile_errors/invalid_struct_field.zig @@ -25,5 +25,6 @@ export fn e() void { // :4:7: error: no field named 'foo' in struct 'tmp.A' // :1:11: note: struct declared here // :10:17: error: no field named 'bar' in struct 'tmp.A' +// :1:11: note: struct declared here // :18:45: error: no field named 'f' in struct 'tmp.e.B' // :14:15: note: struct declared here diff --git a/test/cases/compile_errors/missing_main_fn_in_executable.zig b/test/cases/compile_errors/missing_main_fn_in_executable.zig index 2d608ad2b8..3c1ae631ac 100644 --- a/test/cases/compile_errors/missing_main_fn_in_executable.zig +++ b/test/cases/compile_errors/missing_main_fn_in_executable.zig @@ -5,5 +5,7 @@ // target=x86_64-linux // output_mode=Exe // -// :?:?: error: root struct of file 'tmp' has no member named 'main' -// :?:?: note: called from here +// : error: root struct of file 'tmp' has no member named 'main' +// : note: called from here +// : note: called from here +// : note: called from here diff --git a/test/cases/compile_errors/private_main_fn.zig b/test/cases/compile_errors/private_main_fn.zig index 26ad3d22db..6e53fbdce2 100644 --- a/test/cases/compile_errors/private_main_fn.zig +++ b/test/cases/compile_errors/private_main_fn.zig @@ -5,6 +5,8 @@ fn main() void {} // target=x86_64-linux // output_mode=Exe // -// :?:?: error: 'main' is not marked 'pub' +// : error: 'main' is not marked 'pub' // :1:1: note: declared here -// :?:?: note: called from here +// : note: called from here +// : note: called from here +// : note: called from here diff --git a/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig b/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig index 4c235ed94b..9cd0fe1798 100644 --- a/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig +++ b/test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig @@ -15,5 +15,6 @@ export fn entry() void { // target=native // // :9:51: error: values of type '[]const builtin.Type.StructField' must be comptime-known, but index value is runtime-known -// :?:21: note: struct requires comptime because of this field -// :?:21: note: types are not available at runtime +// : note: struct requires comptime because of this field +// : note: types are not available at runtime +// : struct requires comptime because of this field diff --git a/test/cases/compile_errors/struct_type_mismatch_in_arg.zig b/test/cases/compile_errors/struct_type_mismatch_in_arg.zig index a52bdfab6c..d051966c52 100644 --- a/test/cases/compile_errors/struct_type_mismatch_in_arg.zig +++ b/test/cases/compile_errors/struct_type_mismatch_in_arg.zig @@ -13,6 +13,6 @@ comptime { // target=native // // :7:16: error: expected type 'tmp.Foo', found 'tmp.Bar' -// :1:13: note: struct declared here // :2:13: note: struct declared here +// :1:13: note: struct declared here // :4:18: note: parameter type declared here diff --git a/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig b/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig index 5f486bf2b7..a700f0d0f2 100644 --- a/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig +++ b/test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig @@ -28,10 +28,11 @@ export fn u2m() void { // target=native // // :9:1: error: union initializer must initialize one field +// :1:12: note: union declared here // :14:20: error: cannot initialize multiple union fields at once, unions can only have one active field // :14:31: note: additional initializer here +// :1:12: note: union declared here // :18:21: error: union initializer must initialize one field // :22:20: error: cannot initialize multiple union fields at once, unions can only have one active field // :22:31: note: additional initializer here -// :1:12: note: union declared here // :5:12: note: union declared here diff --git a/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig b/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig index cf43513159..00d4a7ecc9 100644 --- a/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig +++ b/test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig b/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig index 5907c1dad5..f23498e955 100644 --- a/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig +++ b/test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig b/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig index ece0614f73..4f54f38e6b 100644 --- a/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig +++ b/test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig @@ -6,7 +6,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig b/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig index 9175bcbc0e..84695cb35b 100644 --- a/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig +++ b/test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig @@ -6,7 +6,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig b/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig index 8f36700757..badab821d3 100644 --- a/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig +++ b/test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/pointer_keeps_address_space.zig b/test/cases/llvm/pointer_keeps_address_space.zig index bfd40566f8..f894c96d7b 100644 --- a/test/cases/llvm/pointer_keeps_address_space.zig +++ b/test/cases/llvm/pointer_keeps_address_space.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig b/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig index 8114e86c5d..b5803a3076 100644 --- a/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig +++ b/test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig b/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig index 78bc3e4bd6..b3c0116983 100644 --- a/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig +++ b/test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig @@ -5,7 +5,7 @@ pub fn main() void { _ = entry; } -// error +// compile // output_mode=Exe // backend=stage2,llvm // target=x86_64-linux,x86_64-macos diff --git a/test/stage2/cbe.zig b/test/cbe.zig similarity index 92% rename from test/stage2/cbe.zig rename to test/cbe.zig index e9750853a6..25ac3cb137 100644 --- a/test/stage2/cbe.zig +++ b/test/cbe.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const TestContext = @import("../../src/test.zig").TestContext; +const Cases = @import("src/Cases.zig"); // These tests should work with all platforms, but we're using linux_x64 for // now for consistency. Will be expanded eventually. @@ -8,7 +8,7 @@ const linux_x64 = std.zig.CrossTarget{ .os_tag = .linux, }; -pub fn addCases(ctx: *TestContext) !void { +pub fn addCases(ctx: *Cases) !void { { var case = ctx.exeFromCompiledC("hello world with updates", .{}); @@ -71,7 +71,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exeFromCompiledC("@intToError", .{}); + var case = ctx.exeFromCompiledC("intToError", .{}); case.addCompareOutput( \\pub export fn main() c_int { @@ -837,7 +837,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exeFromCompiledC("shift right + left", .{}); + var case = ctx.exeFromCompiledC("shift right and left", .{}); case.addCompareOutput( \\pub export fn main() c_int { \\ var i: u32 = 16; @@ -883,7 +883,7 @@ pub fn addCases(ctx: *TestContext) !void { { // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64) - var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{}); + var case = ctx.exeFromCompiledC("add and sub wrapping operations", .{}); case.addCompareOutput( \\pub export fn main() c_int { \\ // Addition @@ -932,7 +932,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exeFromCompiledC("@rem", linux_x64); + var case = ctx.exeFromCompiledC("rem", linux_x64); case.addCompareOutput( \\fn assert(ok: bool) void { \\ if (!ok) unreachable; @@ -947,69 +947,4 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } - - ctx.h("simple header", linux_x64, - \\export fn start() void{} - , - \\zig_extern void start(void); - \\ - ); - ctx.h("header with single param function", linux_x64, - \\export fn start(a: u8) void{ - \\ _ = a; - \\} - , - \\zig_extern void start(uint8_t const a0); - \\ - ); - ctx.h("header with multiple param function", linux_x64, - \\export fn start(a: u8, b: u8, c: u8) void{ - \\ _ = a; _ = b; _ = c; - \\} - , - \\zig_extern void start(uint8_t const a0, uint8_t const a1, uint8_t const a2); - \\ - ); - ctx.h("header with u32 param function", linux_x64, - \\export fn start(a: u32) void{ _ = a; } - , - \\zig_extern void start(uint32_t const a0); - \\ - ); - ctx.h("header with usize param function", linux_x64, - \\export fn start(a: usize) void{ _ = a; } - , - \\zig_extern void start(uintptr_t const a0); - \\ - ); - ctx.h("header with bool param function", linux_x64, - \\export fn start(a: bool) void{_ = a;} - , - \\zig_extern void start(bool const a0); - \\ - ); - ctx.h("header with noreturn function", linux_x64, - \\export fn start() noreturn { - \\ unreachable; - \\} - , - \\zig_extern zig_noreturn void start(void); - \\ - ); - ctx.h("header with multiple functions", linux_x64, - \\export fn a() void{} - \\export fn b() void{} - \\export fn c() void{} - , - \\zig_extern void a(void); - \\zig_extern void b(void); - \\zig_extern void c(void); - \\ - ); - ctx.h("header with multiple includes", linux_x64, - \\export fn start(a: u32, b: usize) void{ _ = a; _ = b; } - , - \\zig_extern void start(uint32_t const a0, uintptr_t const a1); - \\ - ); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index e0b78b3000..2d796b9463 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,146 +1,10 @@ const std = @import("std"); const builtin = @import("builtin"); -const TestContext = @import("../src/test.zig").TestContext; - -pub fn addCases(ctx: *TestContext) !void { - { - const case = ctx.obj("wrong same named struct", .{}); - case.backend = .stage1; - - case.addSourceFile("a.zig", - \\pub const Foo = struct { - \\ x: i32, - \\}; - ); - - case.addSourceFile("b.zig", - \\pub const Foo = struct { - \\ z: f64, - \\}; - ); - - case.addError( - \\const a = @import("a.zig"); - \\const b = @import("b.zig"); - \\ - \\export fn entry() void { - \\ var a1: a.Foo = undefined; - \\ bar(&a1); - \\} - \\ - \\fn bar(x: *b.Foo) void {_ = x;} - , &[_][]const u8{ - "tmp.zig:6:10: error: expected type '*b.Foo', found '*a.Foo'", - "tmp.zig:6:10: note: pointer type child 'a.Foo' cannot cast into pointer type child 'b.Foo'", - "a.zig:1:17: note: a.Foo declared here", - "b.zig:1:17: note: b.Foo declared here", - }); - } - - { - const case = ctx.obj("multiple files with private function error", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\fn privateFunction() void { } - ); - - case.addError( - \\const foo = @import("foo.zig",); - \\ - \\export fn callPrivFunction() void { - \\ foo.privateFunction(); - \\} - , &[_][]const u8{ - "tmp.zig:4:8: error: 'privateFunction' is private", - "foo.zig:1:1: note: declared here", - }); - } - - { - const case = ctx.obj("multiple files with private member instance function (canonical invocation) error", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\pub const Foo = struct { - \\ fn privateFunction(self: *Foo) void { _ = self; } - \\}; - ); - - case.addError( - \\const Foo = @import("foo.zig",).Foo; - \\ - \\export fn callPrivFunction() void { - \\ var foo = Foo{}; - \\ Foo.privateFunction(foo); - \\} - , &[_][]const u8{ - "tmp.zig:5:8: error: 'privateFunction' is private", - "foo.zig:2:5: note: declared here", - }); - } - - { - const case = ctx.obj("multiple files with private member instance function error", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\pub const Foo = struct { - \\ fn privateFunction(self: *Foo) void { _ = self; } - \\}; - ); - - case.addError( - \\const Foo = @import("foo.zig",).Foo; - \\ - \\export fn callPrivFunction() void { - \\ var foo = Foo{}; - \\ foo.privateFunction(); - \\} - , &[_][]const u8{ - "tmp.zig:5:8: error: 'privateFunction' is private", - "foo.zig:2:5: note: declared here", - }); - } - - { - const case = ctx.obj("export collision", .{}); - case.backend = .stage1; - - case.addSourceFile("foo.zig", - \\export fn bar() void {} - \\pub const baz = 1234; - ); - - case.addError( - \\const foo = @import("foo.zig",); - \\ - \\export fn bar() usize { - \\ return foo.baz; - \\} - , &[_][]const u8{ - "foo.zig:1:1: error: exported symbol collision: 'bar'", - "tmp.zig:3:1: note: other symbol here", - }); - } - - ctx.objErrStage1("non-printable invalid character", "\xff\xfe" ++ - "fn foo() bool {\r\n" ++ - " return true;\r\n" ++ - "}\r\n", &[_][]const u8{ - "tmp.zig:1:1: error: expected test, comptime, var decl, or container field, found 'invalid bytes'", - "tmp.zig:1:1: note: invalid byte: '\\xff'", - }); - - ctx.objErrStage1("non-printable invalid character with escape alternative", "fn foo() bool {\n" ++ - "\treturn true;\n" ++ - "}\n", &[_][]const u8{ - "tmp.zig:2:1: error: invalid character: '\\t'", - }); +const Cases = @import("src/Cases.zig"); +pub fn addCases(ctx: *Cases) !void { { const case = ctx.obj("multiline error messages", .{}); - case.backend = .stage2; case.addError( \\comptime { @@ -176,7 +40,6 @@ pub fn addCases(ctx: *TestContext) !void { { const case = ctx.obj("isolated carriage return in multiline string literal", .{}); - case.backend = .stage2; case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{ ":1:19: error: expected ';' after declaration", @@ -195,16 +58,6 @@ pub fn addCases(ctx: *TestContext) !void { { const case = ctx.obj("argument causes error", .{}); - case.backend = .stage2; - - case.addSourceFile("b.zig", - \\pub const ElfDynLib = struct { - \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T { - \\ _ = self; - \\ return undefined; - \\ } - \\}; - ); case.addError( \\pub export fn entry() void { @@ -216,15 +69,18 @@ pub fn addCases(ctx: *TestContext) !void { ":3:12: note: argument to function being called at comptime must be comptime-known", ":2:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type", }); + case.addSourceFile("b.zig", + \\pub const ElfDynLib = struct { + \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T { + \\ _ = self; + \\ return undefined; + \\ } + \\}; + ); } { const case = ctx.obj("astgen failure in file struct", .{}); - case.backend = .stage2; - - case.addSourceFile("b.zig", - \\+ - ); case.addError( \\pub export fn entry() void { @@ -233,21 +89,13 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":1:1: error: expected type expression, found '+'", }); + case.addSourceFile("b.zig", + \\+ + ); } { const case = ctx.obj("invalid store to comptime field", .{}); - case.backend = .stage2; - - case.addSourceFile("a.zig", - \\pub const S = struct { - \\ comptime foo: u32 = 1, - \\ bar: u32, - \\ pub fn foo(x: @This()) void { - \\ _ = x; - \\ } - \\}; - ); case.addError( \\const a = @import("a.zig"); @@ -259,44 +107,19 @@ pub fn addCases(ctx: *TestContext) !void { ":4:23: error: value stored in comptime field does not match the default value of the field", ":2:25: note: default value set here", }); + case.addSourceFile("a.zig", + \\pub const S = struct { + \\ comptime foo: u32 = 1, + \\ bar: u32, + \\ pub fn foo(x: @This()) void { + \\ _ = x; + \\ } + \\}; + ); } - // TODO test this in stage2, but we won't even try in stage1 - //ctx.objErrStage1("inline fn calls itself indirectly", - // \\export fn foo() void { - // \\ bar(); - // \\} - // \\fn bar() callconv(.Inline) void { - // \\ baz(); - // \\ quux(); - // \\} - // \\fn baz() callconv(.Inline) void { - // \\ bar(); - // \\ quux(); - // \\} - // \\extern fn quux() void; - //, &[_][]const u8{ - // "tmp.zig:4:1: error: unable to inline function", - //}); - - //ctx.objErrStage1("save reference to inline function", - // \\export fn foo() void { - // \\ quux(@ptrToInt(bar)); - // \\} - // \\fn bar() callconv(.Inline) void { } - // \\extern fn quux(usize) void; - //, &[_][]const u8{ - // "tmp.zig:4:1: error: unable to inline function", - //}); - { const case = ctx.obj("file in multiple modules", .{}); - case.backend = .stage2; - - case.addSourceFile("foo.zig", - \\const dummy = 0; - ); - case.addDepModule("foo", "foo.zig"); case.addError( @@ -309,5 +132,8 @@ pub fn addCases(ctx: *TestContext) !void { ":1:1: note: root of module root.foo", ":3:17: note: imported from module root", }); + case.addSourceFile("foo.zig", + \\const dummy = 0; + ); } } diff --git a/test/link/macho/dead_strip/build.zig b/test/link/macho/dead_strip/build.zig index 4131d7baf9..4c739b3d8c 100644 --- a/test/link/macho/dead_strip/build.zig +++ b/test/link/macho/dead_strip/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void { // Without -dead_strip, we expect `iAmUnused` symbol present const exe = createScenario(b, optimize, target, "no-gc"); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkInSymtab(); check.checkNext("{*} (__TEXT,__text) external _iAmUnused"); @@ -27,7 +27,7 @@ pub fn build(b: *std.Build) void { const exe = createScenario(b, optimize, target, "yes-gc"); exe.link_gc_sections = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkInSymtab(); check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused"); diff --git a/test/link/macho/dead_strip_dylibs/build.zig b/test/link/macho/dead_strip_dylibs/build.zig index 4a4dda7b7f..b9b97949c1 100644 --- a/test/link/macho/dead_strip_dylibs/build.zig +++ b/test/link/macho/dead_strip_dylibs/build.zig @@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable const exe = createScenario(b, optimize, "no-dead-strip"); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name {*}Cocoa"); diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index bf4a49f50b..2d775aa23f 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize dylib.addCSourceFile("a.c", &.{}); dylib.linkLibC(); - const check_dylib = dylib.checkObject(.macho); + const check_dylib = dylib.checkObject(); check_dylib.checkStart("cmd ID_DYLIB"); check_dylib.checkNext("name @rpath/liba.dylib"); check_dylib.checkNext("timestamp 2"); @@ -44,7 +44,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); exe.linkLibC(); - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("cmd LOAD_DYLIB"); check_exe.checkNext("name @rpath/liba.dylib"); check_exe.checkNext("timestamp 2"); diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index b2c41ef6dc..e983bc9391 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkLibC(); exe.entry_symbol_name = "_non_main"; - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("segname __TEXT"); check_exe.checkNext("vmaddr {vmaddr}"); diff --git a/test/link/macho/headerpad/build.zig b/test/link/macho/headerpad/build.zig index 07d30ee6f2..97161e8e60 100644 --- a/test/link/macho/headerpad/build.zig +++ b/test/link/macho/headerpad/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const exe = simpleExe(b, optimize); exe.headerpad_max_install_names = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); @@ -45,7 +45,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const exe = simpleExe(b, optimize); exe.headerpad_size = 0x10000; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); @@ -62,7 +62,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.headerpad_max_install_names = true; exe.headerpad_size = 0x10000; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); @@ -79,7 +79,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.headerpad_size = 0x1000; exe.headerpad_max_install_names = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("sectname __text"); check.checkNext("offset {offset}"); diff --git a/test/link/macho/linksection/build.zig b/test/link/macho/linksection/build.zig index 37e0b7cbef..b8b3a59f35 100644 --- a/test/link/macho/linksection/build.zig +++ b/test/link/macho/linksection/build.zig @@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .target = target, }); - const check = obj.checkObject(.macho); + const check = obj.checkObject(); check.checkInSymtab(); check.checkNext("{*} (__DATA,__TestGlobal) external _test_global"); diff --git a/test/link/macho/needed_framework/build.zig b/test/link/macho/needed_framework/build.zig index cd53a5d212..3de96efbc7 100644 --- a/test/link/macho/needed_framework/build.zig +++ b/test/link/macho/needed_framework/build.zig @@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkFrameworkNeeded("Cocoa"); exe.dead_strip_dylibs = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name {*}Cocoa"); test_step.dependOn(&check.step); diff --git a/test/link/macho/needed_library/build.zig b/test/link/macho/needed_library/build.zig index 6e24a12c65..cb9ea38d4b 100644 --- a/test/link/macho/needed_library/build.zig +++ b/test/link/macho/needed_library/build.zig @@ -38,7 +38,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); exe.dead_strip_dylibs = true; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name @rpath/liba.dylib"); diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index e7b002a389..b467df2b20 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { exe.linkLibC(); exe.pagezero_size = 0x4000; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("LC 0"); check.checkNext("segname __PAGEZERO"); check.checkNext("vmaddr 0"); @@ -41,7 +41,7 @@ pub fn build(b: *std.Build) void { exe.linkLibC(); exe.pagezero_size = 0; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("LC 0"); check.checkNext("segname __TEXT"); check.checkNext("vmaddr 0"); diff --git a/test/link/macho/search_strategy/build.zig b/test/link/macho/search_strategy/build.zig index ae1037c2d7..4777629c8b 100644 --- a/test/link/macho/search_strategy/build.zig +++ b/test/link/macho/search_strategy/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const exe = createScenario(b, optimize, target, "search_dylibs_first"); exe.search_strategy = .dylibs_first; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name @rpath/libsearch_dylibs_first.dylib"); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 51efed4c34..c7d308d004 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkLibC(); exe.stack_size = 0x100000000; - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("cmd MAIN"); check_exe.checkNext("stacksize 100000000"); diff --git a/test/link/macho/strict_validation/build.zig b/test/link/macho/strict_validation/build.zig index a95793234b..34a0cd73fc 100644 --- a/test/link/macho/strict_validation/build.zig +++ b/test/link/macho/strict_validation/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize }); exe.linkLibC(); - const check_exe = exe.checkObject(.macho); + const check_exe = exe.checkObject(); check_exe.checkStart("cmd SEGMENT_64"); check_exe.checkNext("segname __LINKEDIT"); diff --git a/test/link/macho/unwind_info/build.zig b/test/link/macho/unwind_info/build.zig index b9bcea8358..4ace2a4e96 100644 --- a/test/link/macho/unwind_info/build.zig +++ b/test/link/macho/unwind_info/build.zig @@ -31,7 +31,7 @@ fn testUnwindInfo( const exe = createScenario(b, optimize, target, name); exe.link_gc_sections = dead_strip; - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("segname __TEXT"); check.checkNext("sectname __gcc_except_tab"); check.checkNext("sectname __unwind_info"); diff --git a/test/link/macho/weak_framework/build.zig b/test/link/macho/weak_framework/build.zig index a4e1d6e5d9..7cc08f5b9d 100644 --- a/test/link/macho/weak_framework/build.zig +++ b/test/link/macho/weak_framework/build.zig @@ -22,7 +22,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.linkLibC(); exe.linkFrameworkWeak("Cocoa"); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_WEAK_DYLIB"); check.checkNext("name {*}Cocoa"); test_step.dependOn(&check.step); diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig index ac265689e3..b12ec087f5 100644 --- a/test/link/macho/weak_library/build.zig +++ b/test/link/macho/weak_library/build.zig @@ -36,7 +36,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource()); exe.addRPathDirectorySource(dylib.getOutputDirectorySource()); - const check = exe.checkObject(.macho); + const check = exe.checkObject(); check.checkStart("cmd LOAD_WEAK_DYLIB"); check.checkNext("name @rpath/liba.dylib"); diff --git a/test/link/wasm/archive/build.zig b/test/link/wasm/archive/build.zig index f586187105..6c242a6bf1 100644 --- a/test/link/wasm/archive/build.zig +++ b/test/link/wasm/archive/build.zig @@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.use_lld = false; lib.strip = false; - const check = lib.checkObject(.wasm); + const check = lib.checkObject(); check.checkStart("Section custom"); check.checkNext("name __truncsfhf2"); // Ensure it was imported and resolved diff --git a/test/link/wasm/basic-features/build.zig b/test/link/wasm/basic-features/build.zig index f3f5320e54..be709a698f 100644 --- a/test/link/wasm/basic-features/build.zig +++ b/test/link/wasm/basic-features/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { lib.use_lld = false; // Verify the result contains the features explicitly set on the target for the library. - const check = lib.checkObject(.wasm); + const check = lib.checkObject(); check.checkStart("name target_features"); check.checkNext("features 1"); check.checkNext("+ atomics"); diff --git a/test/link/wasm/bss/build.zig b/test/link/wasm/bss/build.zig index 8e6b19c7be..bba2e7c602 100644 --- a/test/link/wasm/bss/build.zig +++ b/test/link/wasm/bss/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { lib.import_memory = true; lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); // since we import memory, make sure it exists with the correct naming check_lib.checkStart("Section import"); diff --git a/test/link/wasm/export-data/build.zig b/test/link/wasm/export-data/build.zig index 0bd10921a2..38b8c3e19e 100644 --- a/test/link/wasm/export-data/build.zig +++ b/test/link/wasm/export-data/build.zig @@ -19,7 +19,7 @@ pub fn build(b: *std.Build) void { lib.export_symbol_names = &.{ "foo", "bar" }; lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section global"); check_lib.checkNext("entries 3"); diff --git a/test/link/wasm/export/build.zig b/test/link/wasm/export/build.zig index 03c4baabe3..794201dbf6 100644 --- a/test/link/wasm/export/build.zig +++ b/test/link/wasm/export/build.zig @@ -42,19 +42,19 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize force_export.use_llvm = false; force_export.use_lld = false; - const check_no_export = no_export.checkObject(.wasm); + const check_no_export = no_export.checkObject(); check_no_export.checkStart("Section export"); check_no_export.checkNext("entries 1"); check_no_export.checkNext("name memory"); check_no_export.checkNext("kind memory"); - const check_dynamic_export = dynamic_export.checkObject(.wasm); + const check_dynamic_export = dynamic_export.checkObject(); check_dynamic_export.checkStart("Section export"); check_dynamic_export.checkNext("entries 2"); check_dynamic_export.checkNext("name foo"); check_dynamic_export.checkNext("kind function"); - const check_force_export = force_export.checkObject(.wasm); + const check_force_export = force_export.checkObject(); check_force_export.checkStart("Section export"); check_force_export.checkNext("entries 2"); check_force_export.checkNext("name foo"); diff --git a/test/link/wasm/extern-mangle/build.zig b/test/link/wasm/extern-mangle/build.zig index b0655cbc1f..6c292acbab 100644 --- a/test/link/wasm/extern-mangle/build.zig +++ b/test/link/wasm/extern-mangle/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.import_symbols = true; // import `a` and `b` lib.rdynamic = true; // export `foo` - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section import"); check_lib.checkNext("entries 2"); // a.hello & b.hello check_lib.checkNext("module a"); diff --git a/test/link/wasm/function-table/build.zig b/test/link/wasm/function-table/build.zig index e1921caee3..4ce6294727 100644 --- a/test/link/wasm/function-table/build.zig +++ b/test/link/wasm/function-table/build.zig @@ -42,9 +42,9 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize regular_table.use_llvm = false; regular_table.use_lld = false; - const check_import = import_table.checkObject(.wasm); - const check_export = export_table.checkObject(.wasm); - const check_regular = regular_table.checkObject(.wasm); + const check_import = import_table.checkObject(); + const check_export = export_table.checkObject(); + const check_regular = regular_table.checkObject(); check_import.checkStart("Section import"); check_import.checkNext("entries 1"); diff --git a/test/link/wasm/infer-features/build.zig b/test/link/wasm/infer-features/build.zig index a7cfa985d5..00fb48651b 100644 --- a/test/link/wasm/infer-features/build.zig +++ b/test/link/wasm/infer-features/build.zig @@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void { lib.addObject(c_obj); // Verify the result contains the features from the C Object file. - const check = lib.checkObject(.wasm); + const check = lib.checkObject(); check.checkStart("name target_features"); check.checkNext("features 7"); check.checkNext("+ atomics"); diff --git a/test/link/wasm/producers/build.zig b/test/link/wasm/producers/build.zig index 41952b0adb..7b7cefd7e0 100644 --- a/test/link/wasm/producers/build.zig +++ b/test/link/wasm/producers/build.zig @@ -27,7 +27,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize const version_fmt = "version " ++ builtin.zig_version_string; - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("name producers"); check_lib.checkNext("fields 2"); check_lib.checkNext("field_name language"); diff --git a/test/link/wasm/segments/build.zig b/test/link/wasm/segments/build.zig index f8e16eee24..281d8ae32b 100644 --- a/test/link/wasm/segments/build.zig +++ b/test/link/wasm/segments/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.strip = false; lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section data"); check_lib.checkNext("entries 2"); // rodata & data, no bss because we're exporting memory diff --git a/test/link/wasm/stack_pointer/build.zig b/test/link/wasm/stack_pointer/build.zig index 41bdd828f2..794b7d27fb 100644 --- a/test/link/wasm/stack_pointer/build.zig +++ b/test/link/wasm/stack_pointer/build.zig @@ -25,7 +25,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); // ensure global exists and its initial value is equal to explitic stack size check_lib.checkStart("Section global"); diff --git a/test/link/wasm/type/build.zig b/test/link/wasm/type/build.zig index df8cbad021..4a8395645f 100644 --- a/test/link/wasm/type/build.zig +++ b/test/link/wasm/type/build.zig @@ -24,7 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize lib.strip = false; lib.install(); - const check_lib = lib.checkObject(.wasm); + const check_lib = lib.checkObject(); check_lib.checkStart("Section type"); // only 2 entries, although we have 3 functions. // This is to test functions with the same function signature diff --git a/test/stage2/nvptx.zig b/test/nvptx.zig similarity index 76% rename from test/stage2/nvptx.zig rename to test/nvptx.zig index f08aa9fca4..57853a657d 100644 --- a/test/stage2/nvptx.zig +++ b/test/nvptx.zig @@ -1,11 +1,11 @@ const std = @import("std"); -const TestContext = @import("../../src/test.zig").TestContext; +const Cases = @import("src/Cases.zig"); -pub fn addCases(ctx: *TestContext) !void { +pub fn addCases(ctx: *Cases) !void { { - var case = addPtx(ctx, "nvptx: simple addition and subtraction"); + var case = addPtx(ctx, "simple addition and subtraction"); - case.compiles( + case.addCompile( \\fn add(a: i32, b: i32) i32 { \\ return a + b; \\} @@ -20,9 +20,9 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = addPtx(ctx, "nvptx: read special registers"); + var case = addPtx(ctx, "read special registers"); - case.compiles( + case.addCompile( \\fn threadIdX() u32 { \\ return asm ("mov.u32 \t%[r], %tid.x;" \\ : [r] "=r" (-> u32), @@ -37,9 +37,9 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = addPtx(ctx, "nvptx: address spaces"); + var case = addPtx(ctx, "address spaces"); - case.compiles( + case.addCompile( \\var x: i32 addrspace(.global) = 0; \\ \\pub export fn increment(out: *i32) callconv(.PtxKernel) void { @@ -50,8 +50,8 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = addPtx(ctx, "nvptx: reduce in shared mem"); - case.compiles( + var case = addPtx(ctx, "reduce in shared mem"); + case.addCompile( \\fn threadIdX() u32 { \\ return asm ("mov.u32 \t%[r], %tid.x;" \\ : [r] "=r" (-> u32), @@ -88,16 +88,15 @@ const nvptx_target = std.zig.CrossTarget{ }; pub fn addPtx( - ctx: *TestContext, + ctx: *Cases, name: []const u8, -) *TestContext.Case { - ctx.cases.append(TestContext.Case{ +) *Cases.Case { + ctx.cases.append(.{ .name = name, .target = nvptx_target, - .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), + .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), .output_mode = .Obj, - .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), - .deps = std.ArrayList(TestContext.DepModule).init(ctx.cases.allocator), + .deps = std.ArrayList(Cases.DepModule).init(ctx.cases.allocator), .link_libc = false, .backend = .llvm, // Bug in Debug mode diff --git a/test/src/Cases.zig b/test/src/Cases.zig new file mode 100644 index 0000000000..27b0cf6b21 --- /dev/null +++ b/test/src/Cases.zig @@ -0,0 +1,1587 @@ +gpa: Allocator, +arena: Allocator, +cases: std.ArrayList(Case), +incremental_cases: std.ArrayList(IncrementalCase), + +pub const IncrementalCase = struct { + base_path: []const u8, +}; + +pub const Update = struct { + /// The input to the current update. We simulate an incremental update + /// with the file's contents changed to this value each update. + /// + /// This value can change entirely between updates, which would be akin + /// to deleting the source file and creating a new one from scratch; or + /// you can keep it mostly consistent, with small changes, testing the + /// effects of the incremental compilation. + files: std.ArrayList(File), + /// This is a description of what happens with the update, for debugging + /// purposes. + name: []const u8, + case: union(enum) { + /// Check that it compiles with no errors. + Compile: void, + /// Check the main binary output file against an expected set of bytes. + /// This is most useful with, for example, `-ofmt=c`. + CompareObjectFile: []const u8, + /// An error update attempts to compile bad code, and ensures that it + /// fails to compile, and for the expected reasons. + /// A slice containing the expected stderr template, which + /// gets some values substituted. + Error: []const []const u8, + /// An execution update compiles and runs the input, testing the + /// stdout against the expected results + /// This is a slice containing the expected message. + Execution: []const u8, + /// A header update compiles the input with the equivalent of + /// `-femit-h` and tests the produced header against the + /// expected result + Header: []const u8, + }, + + pub fn addSourceFile(update: *Update, name: []const u8, src: [:0]const u8) void { + update.files.append(.{ .path = name, .src = src }) catch @panic("out of memory"); + } +}; + +pub const File = struct { + src: [:0]const u8, + path: []const u8, +}; + +pub const DepModule = struct { + name: []const u8, + path: []const u8, +}; + +pub const Backend = enum { + stage1, + stage2, + llvm, +}; + +/// A `Case` consists of a list of `Update`. The same `Compilation` is used for each +/// update, so each update's source is treated as a single file being +/// updated by the test harness and incrementally compiled. +pub const Case = struct { + /// The name of the test case. This is shown if a test fails, and + /// otherwise ignored. + name: []const u8, + /// The platform the test targets. For non-native platforms, an emulator + /// such as QEMU is required for tests to complete. + target: CrossTarget, + /// In order to be able to run e.g. Execution updates, this must be set + /// to Executable. + output_mode: std.builtin.OutputMode, + optimize_mode: std.builtin.Mode = .Debug, + updates: std.ArrayList(Update), + emit_h: bool = false, + is_test: bool = false, + expect_exact: bool = false, + backend: Backend = .stage2, + link_libc: bool = false, + + deps: std.ArrayList(DepModule), + + pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void { + const update = &case.updates.items[case.updates.items.len - 1]; + update.files.append(.{ .path = name, .src = src }) catch @panic("OOM"); + } + + pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void { + case.deps.append(.{ + .name = name, + .path = path, + }) catch @panic("out of memory"); + } + + /// Adds a subcase in which the module is updated with `src`, compiled, + /// run, and the output is tested against `result`. + pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { + self.updates.append(.{ + .files = std.ArrayList(File).init(self.updates.allocator), + .name = "update", + .case = .{ .Execution = result }, + }) catch @panic("out of memory"); + addSourceFile(self, "tmp.zig", src); + } + + pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { + return self.addErrorNamed("update", src, errors); + } + + /// Adds a subcase in which the module is updated with `src`, which + /// should contain invalid input, and ensures that compilation fails + /// for the expected reasons, given in sequential order in `errors` in + /// the form `:line:column: error: message`. + pub fn addErrorNamed( + self: *Case, + name: []const u8, + src: [:0]const u8, + errors: []const []const u8, + ) void { + assert(errors.len != 0); + self.updates.append(.{ + .files = std.ArrayList(File).init(self.updates.allocator), + .name = name, + .case = .{ .Error = errors }, + }) catch @panic("out of memory"); + addSourceFile(self, "tmp.zig", src); + } + + /// Adds a subcase in which the module is updated with `src`, and + /// asserts that it compiles without issue + pub fn addCompile(self: *Case, src: [:0]const u8) void { + self.updates.append(.{ + .files = std.ArrayList(File).init(self.updates.allocator), + .name = "compile", + .case = .{ .Compile = {} }, + }) catch @panic("out of memory"); + addSourceFile(self, "tmp.zig", src); + } +}; + +pub fn addExe( + ctx: *Cases, + name: []const u8, + target: CrossTarget, +) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Exe, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +/// Adds a test case for Zig input, producing an executable +pub fn exe(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + return ctx.addExe(name, target); +} + +pub fn exeFromCompiledC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + var target_adjusted = target; + target_adjusted.ofmt = .c; + ctx.cases.append(Case{ + .name = name, + .target = target_adjusted, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Exe, + .deps = std.ArrayList(DepModule).init(ctx.arena), + .link_libc = true, + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +/// Adds a test case that uses the LLVM backend to emit an executable. +/// Currently this implies linking libc, because only then we can generate a testable executable. +pub fn exeUsingLlvmBackend(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Exe, + .deps = std.ArrayList(DepModule).init(ctx.arena), + .backend = .llvm, + .link_libc = true, + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +pub fn addObj( + ctx: *Cases, + name: []const u8, + target: CrossTarget, +) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +pub fn addTest( + ctx: *Cases, + name: []const u8, + target: CrossTarget, +) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Exe, + .is_test = true, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +/// Adds a test case for Zig input, producing an object file. +pub fn obj(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + return ctx.addObj(name, target); +} + +/// Adds a test case for ZIR input, producing an object file. +pub fn objZIR(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + return ctx.addObj(name, target, .ZIR); +} + +/// Adds a test case for Zig or ZIR input, producing C code. +pub fn addC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + var target_adjusted = target; + target_adjusted.ofmt = std.Target.ObjectFormat.c; + ctx.cases.append(Case{ + .name = name, + .target = target_adjusted, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .deps = std.ArrayList(DepModule).init(ctx.arena), + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + +pub fn addCompareOutput( + ctx: *Cases, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, +) void { + ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout); +} + +/// Adds a test case that compiles the Zig source given in `src`, executes +/// it, runs it, and tests the output against `expected_stdout` +pub fn compareOutput( + ctx: *Cases, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, +) void { + return ctx.addCompareOutput(name, src, expected_stdout); +} + +pub fn addTransform( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + result: [:0]const u8, +) void { + ctx.addObj(name, target).addTransform(src, result); +} + +/// Adds a test case that compiles the Zig given in `src` to ZIR and tests +/// the ZIR against `result` +pub fn transform( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + result: [:0]const u8, +) void { + ctx.addTransform(name, target, src, result); +} + +pub fn addError( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, +) void { + ctx.addObj(name, target).addError(src, expected_errors); +} + +/// Adds a test case that ensures that the Zig given in `src` fails to +/// compile for the expected reasons, given in sequential order in +/// `expected_errors` in the form `:line:column: error: message`. +pub fn compileError( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, +) void { + ctx.addError(name, target, src, expected_errors); +} + +/// Adds a test case that asserts that the Zig given in `src` compiles +/// without any errors. +pub fn addCompile( + ctx: *Cases, + name: []const u8, + target: CrossTarget, + src: [:0]const u8, +) void { + ctx.addObj(name, target).addCompile(src); +} + +/// Adds a test for each file in the provided directory. +/// Testing strategy (TestStrategy) is inferred automatically from filenames. +/// Recurses nested directories. +/// +/// Each file should include a test manifest as a contiguous block of comments at +/// the end of the file. The first line should be the test type, followed by a set of +/// key-value config values, followed by a blank line, then the expected output. +pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void { + var current_file: []const u8 = "none"; + ctx.addFromDirInner(dir, ¤t_file) catch |err| { + std.debug.panic("test harness failed to process file '{s}': {s}\n", .{ + current_file, @errorName(err), + }); + }; +} + +fn addFromDirInner( + ctx: *Cases, + iterable_dir: std.fs.IterableDir, + /// This is kept up to date with the currently being processed file so + /// that if any errors occur the caller knows it happened during this file. + current_file: *[]const u8, +) !void { + var it = try iterable_dir.walk(ctx.arena); + var filenames = std.ArrayList([]const u8).init(ctx.arena); + + while (try it.next()) |entry| { + if (entry.kind != .File) continue; + + // Ignore stuff such as .swp files + switch (Compilation.classifyFileExt(entry.basename)) { + .unknown => continue, + else => {}, + } + try filenames.append(try ctx.arena.dupe(u8, entry.path)); + } + + // Sort filenames, so that incremental tests are contiguous and in-order + sortTestFilenames(filenames.items); + + var test_it = TestIterator{ .filenames = filenames.items }; + while (test_it.next()) |maybe_batch| { + const batch = maybe_batch orelse break; + const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent; + const filename = batch[0]; + current_file.* = filename; + if (strategy == .incremental) { + try ctx.incremental_cases.append(.{ .base_path = filename }); + continue; + } + + const max_file_size = 10 * 1024 * 1024; + const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0); + + // Parse the manifest + var manifest = try TestManifest.parse(ctx.arena, src); + + const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); + const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget); + const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); + const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); + + var cases = std.ArrayList(usize).init(ctx.arena); + + // Cross-product to get all possible test combinations + for (backends) |backend| { + for (targets) |target| { + const next = ctx.cases.items.len; + try ctx.cases.append(.{ + .name = std.fs.path.stem(filename), + .target = target, + .backend = backend, + .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), + .is_test = is_test, + .output_mode = output_mode, + .link_libc = backend == .llvm, + .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + }); + try cases.append(next); + } + } + + for (cases.items) |case_index| { + const case = &ctx.cases.items[case_index]; + switch (manifest.type) { + .compile => { + case.addCompile(src); + }, + .@"error" => { + const errors = try manifest.trailingAlloc(ctx.arena); + case.addError(src, errors); + }, + .run => { + var output = std.ArrayList(u8).init(ctx.arena); + var trailing_it = manifest.trailing(); + while (trailing_it.next()) |line| { + try output.appendSlice(line); + try output.append('\n'); + } + if (output.items.len > 0) { + try output.resize(output.items.len - 1); + } + case.addCompareOutput(src, try output.toOwnedSlice()); + }, + .cli => @panic("TODO cli tests"), + } + } + } else |err| { + // make sure the current file is set to the file that produced an error + current_file.* = test_it.currentFilename(); + return err; + } +} + +pub fn init(gpa: Allocator, arena: Allocator) Cases { + return .{ + .gpa = gpa, + .cases = std.ArrayList(Case).init(gpa), + .incremental_cases = std.ArrayList(IncrementalCase).init(gpa), + .arena = arena, + }; +} + +pub fn lowerToBuildSteps( + self: *Cases, + b: *std.Build, + parent_step: *std.Build.Step, + opt_test_filter: ?[]const u8, + cases_dir_path: []const u8, + incremental_exe: *std.Build.CompileStep, +) void { + for (self.incremental_cases.items) |incr_case| { + if (opt_test_filter) |test_filter| { + if (std.mem.indexOf(u8, incr_case.base_path, test_filter) == null) continue; + } + const case_base_path_with_dir = std.fs.path.join(b.allocator, &.{ + cases_dir_path, incr_case.base_path, + }) catch @panic("OOM"); + const run = b.addRunArtifact(incremental_exe); + run.setName(incr_case.base_path); + run.addArgs(&.{ + case_base_path_with_dir, + b.zig_exe, + }); + run.expectStdOutEqual(""); + parent_step.dependOn(&run.step); + } + + for (self.cases.items) |case| { + if (case.updates.items.len != 1) continue; // handled with incremental_cases above + assert(case.updates.items.len == 1); + const update = case.updates.items[0]; + + if (opt_test_filter) |test_filter| { + if (std.mem.indexOf(u8, case.name, test_filter) == null) continue; + } + + const writefiles = b.addWriteFiles(); + for (update.files.items) |file| { + writefiles.add(file.path, file.src); + } + const root_source_file = writefiles.getFileSource(update.files.items[0].path).?; + + const artifact = switch (case.output_mode) { + .Obj => b.addObject(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }), + .Lib => b.addStaticLibrary(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }), + .Exe => if (case.is_test) b.addTest(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }) else b.addExecutable(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }), + }; + + if (case.link_libc) artifact.linkLibC(); + + switch (case.backend) { + .stage1 => continue, + .stage2 => { + artifact.use_llvm = false; + artifact.use_lld = false; + }, + .llvm => { + artifact.use_llvm = true; + }, + } + + for (case.deps.items) |dep| { + artifact.addAnonymousModule(dep.name, .{ + .source_file = writefiles.getFileSource(dep.path).?, + }); + } + + switch (update.case) { + .Compile => { + parent_step.dependOn(&artifact.step); + }, + .CompareObjectFile => |expected_output| { + const check = b.addCheckFile(artifact.getOutputSource(), .{ + .expected_exact = expected_output, + }); + + parent_step.dependOn(&check.step); + }, + .Error => |expected_msgs| { + assert(expected_msgs.len != 0); + artifact.expect_errors = expected_msgs; + parent_step.dependOn(&artifact.step); + }, + .Execution => |expected_stdout| { + if (case.is_test) { + parent_step.dependOn(&artifact.step); + } else { + const run = b.addRunArtifact(artifact); + run.skip_foreign_checks = true; + run.expectStdOutEqual(expected_stdout); + + parent_step.dependOn(&run.step); + } + }, + .Header => @panic("TODO"), + } + } +} + +/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig", +/// "foo.1.zig", etc.) are contiguous and appear in numerical order. +fn sortTestFilenames(filenames: [][]const u8) void { + const Context = struct { + pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool { + const a_parts = getTestFileNameParts(a); + const b_parts = getTestFileNameParts(b); + + // Sort ".X." based on "" and "" first + return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) { + .lt => true, + .gt => false, + .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) { + .lt => true, + .gt => false, + .eq => { + // a and b differ only in their ".X" part + + // Sort "." before any ".X." + if (a_parts.test_index) |a_index| { + if (b_parts.test_index) |b_index| { + // Make sure that incremental tests appear in linear order + return a_index < b_index; + } else { + return false; + } + } else { + return b_parts.test_index != null; + } + }, + }, + }; + } + }; + std.sort.sort([]const u8, filenames, Context{}, Context.lessThan); +} + +/// Iterates a set of filenames extracting batches that are either incremental +/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.). +/// Assumes filenames are sorted. +const TestIterator = struct { + start: usize = 0, + end: usize = 0, + filenames: []const []const u8, + /// reset on each call to `next` + index: usize = 0, + + const Error = error{InvalidIncrementalTestIndex}; + + fn next(it: *TestIterator) Error!?[]const []const u8 { + try it.nextInner(); + if (it.start == it.end) return null; + return it.filenames[it.start..it.end]; + } + + fn nextInner(it: *TestIterator) Error!void { + it.start = it.end; + if (it.end == it.filenames.len) return; + if (it.end + 1 == it.filenames.len) { + it.end += 1; + return; + } + + const remaining = it.filenames[it.end..]; + it.index = 0; + while (it.index < remaining.len - 1) : (it.index += 1) { + // First, check if this file is part of an incremental update sequence + // Split filename into ".." + const prev_parts = getTestFileNameParts(remaining[it.index]); + const new_parts = getTestFileNameParts(remaining[it.index + 1]); + + // If base_name and file_ext match, these files are in the same test sequence + // and the new one should be the incremented version of the previous test + if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and + std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext)) + { + // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1 + if (prev_parts.test_index == null) + return error.InvalidIncrementalTestIndex; + if (new_parts.test_index == null) + return error.InvalidIncrementalTestIndex; + if (new_parts.test_index.? != prev_parts.test_index.? + 1) + return error.InvalidIncrementalTestIndex; + } else { + // This is not the same test sequence, so the new file must be the first file + // in a new sequence ("*.0.zig") or an independent test file ("*.zig") + if (new_parts.test_index != null and new_parts.test_index.? != 0) + return error.InvalidIncrementalTestIndex; + + it.end += it.index + 1; + break; + } + } else { + it.end += remaining.len; + } + } + + /// In the event of an `error.InvalidIncrementalTestIndex`, this function can + /// be used to find the current filename that was being processed. + /// Asserts the iterator hasn't reached the end. + fn currentFilename(it: TestIterator) []const u8 { + assert(it.end != it.filenames.len); + const remaining = it.filenames[it.end..]; + return remaining[it.index + 1]; + } +}; + +/// For a filename in the format ".X." or ".", returns +/// "", "" and X parsed as a decimal number. If X is not present, or +/// cannot be parsed as a decimal number, it is treated as part of +fn getTestFileNameParts(name: []const u8) struct { + base_name: []const u8, + file_ext: []const u8, + test_index: ?usize, +} { + const file_ext = std.fs.path.extension(name); + const trimmed = name[0 .. name.len - file_ext.len]; // Trim off "." + const maybe_index = std.fs.path.extension(trimmed); // Extract ".X" + + // Attempt to parse index + const index: ?usize = if (maybe_index.len > 0) + std.fmt.parseInt(usize, maybe_index[1..], 10) catch null + else + null; + + // Adjust "" extent based on parsing success + const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0; + return .{ + .base_name = name[0..base_name_end], + .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext, + .test_index = index, + }; +} + +const TestStrategy = enum { + /// Execute tests as independent compilations, unless they are explicitly + /// incremental ("foo.0.zig", "foo.1.zig", etc.) + independent, + /// Execute all tests as incremental updates to a single compilation. Explicitly + /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order + incremental, +}; + +/// Default config values for known test manifest key-value pairings. +/// Currently handled defaults are: +/// * backend +/// * target +/// * output_mode +/// * is_test +const TestManifestConfigDefaults = struct { + /// Asserts if the key doesn't exist - yep, it's an oversight alright. + fn get(@"type": TestManifest.Type, key: []const u8) []const u8 { + if (std.mem.eql(u8, key, "backend")) { + return "stage2"; + } else if (std.mem.eql(u8, key, "target")) { + if (@"type" == .@"error") { + return "native"; + } + comptime { + var defaults: []const u8 = ""; + // TODO should we only return "mainstream" targets by default here? + // TODO we should also specify ABIs explicitly as the backends are + // getting more and more complete + // Linux + inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| { + defaults = defaults ++ arch ++ "-linux" ++ ","; + } + // macOS + inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| { + defaults = defaults ++ arch ++ "-macos" ++ ","; + } + // Windows + defaults = defaults ++ "x86_64-windows" ++ ","; + // Wasm + defaults = defaults ++ "wasm32-wasi"; + return defaults; + } + } else if (std.mem.eql(u8, key, "output_mode")) { + return switch (@"type") { + .@"error" => "Obj", + .run => "Exe", + .compile => "Obj", + .cli => @panic("TODO test harness for CLI tests"), + }; + } else if (std.mem.eql(u8, key, "is_test")) { + return "0"; + } else unreachable; + } +}; + +/// Manifest syntax example: +/// (see https://github.com/ziglang/zig/issues/11288) +/// +/// error +/// backend=stage1,stage2 +/// output_mode=exe +/// +/// :3:19: error: foo +/// +/// run +/// target=x86_64-linux,aarch64-macos +/// +/// I am expected stdout! Hello! +/// +/// cli +/// +/// build test +const TestManifest = struct { + type: Type, + config_map: std.StringHashMap([]const u8), + trailing_bytes: []const u8 = "", + + const Type = enum { + @"error", + run, + cli, + compile, + }; + + const TrailingIterator = struct { + inner: std.mem.TokenIterator(u8), + + fn next(self: *TrailingIterator) ?[]const u8 { + const next_inner = self.inner.next() orelse return null; + return std.mem.trim(u8, next_inner[2..], " \t"); + } + }; + + fn ConfigValueIterator(comptime T: type) type { + return struct { + inner: std.mem.SplitIterator(u8), + + fn next(self: *@This()) !?T { + const next_raw = self.inner.next() orelse return null; + const parseFn = getDefaultParser(T); + return try parseFn(next_raw); + } + }; + } + + fn parse(arena: Allocator, bytes: []const u8) !TestManifest { + // The manifest is the last contiguous block of comments in the file + // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" + var start: ?usize = null; + var end: usize = bytes.len; + if (bytes.len > 0) { + var cursor: usize = bytes.len - 1; + while (true) { + // Move to beginning of line + while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1; + + if (std.mem.startsWith(u8, bytes[cursor..], "//")) { + start = cursor; // Contiguous comment line, include in manifest + } else { + if (start != null) break; // Encountered non-comment line, end of manifest + + // We ignore all-whitespace lines following the comment block, but anything else + // means that there is no manifest present. + if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) { + end = cursor; + } else break; // If it's not whitespace, there is no manifest + } + + // Move to previous line + if (cursor != 0) cursor -= 1 else break; + } + } + + const actual_start = start orelse return error.MissingTestManifest; + const manifest_bytes = bytes[actual_start..end]; + + var it = std.mem.tokenize(u8, manifest_bytes, "\r\n"); + + // First line is the test type + const tt: Type = blk: { + const line = it.next() orelse return error.MissingTestCaseType; + const raw = std.mem.trim(u8, line[2..], " \t"); + if (std.mem.eql(u8, raw, "error")) { + break :blk .@"error"; + } else if (std.mem.eql(u8, raw, "run")) { + break :blk .run; + } else if (std.mem.eql(u8, raw, "cli")) { + break :blk .cli; + } else if (std.mem.eql(u8, raw, "compile")) { + break :blk .compile; + } else { + std.log.warn("unknown test case type requested: {s}", .{raw}); + return error.UnknownTestCaseType; + } + }; + + var manifest: TestManifest = .{ + .type = tt, + .config_map = std.StringHashMap([]const u8).init(arena), + }; + + // Any subsequent line until a blank comment line is key=value(s) pair + while (it.next()) |line| { + const trimmed = std.mem.trim(u8, line[2..], " \t"); + if (trimmed.len == 0) break; + + // Parse key=value(s) + var kv_it = std.mem.split(u8, trimmed, "="); + const key = kv_it.first(); + try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig); + } + + // Finally, trailing is expected output + manifest.trailing_bytes = manifest_bytes[it.index..]; + + return manifest; + } + + fn getConfigForKey( + self: TestManifest, + key: []const u8, + comptime T: type, + ) ConfigValueIterator(T) { + const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key); + return ConfigValueIterator(T){ + .inner = std.mem.split(u8, bytes, ","), + }; + } + + fn getConfigForKeyAlloc( + self: TestManifest, + allocator: Allocator, + key: []const u8, + comptime T: type, + ) ![]const T { + var out = std.ArrayList(T).init(allocator); + defer out.deinit(); + var it = self.getConfigForKey(key, T); + while (try it.next()) |item| { + try out.append(item); + } + return try out.toOwnedSlice(); + } + + fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T { + var it = self.getConfigForKey(key, T); + const res = (try it.next()) orelse unreachable; + assert((try it.next()) == null); + return res; + } + + fn trailing(self: TestManifest) TrailingIterator { + return .{ + .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"), + }; + } + + fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 { + var out = std.ArrayList([]const u8).init(allocator); + defer out.deinit(); + var it = self.trailing(); + while (it.next()) |line| { + try out.append(line); + } + return try out.toOwnedSlice(); + } + + fn ParseFn(comptime T: type) type { + return fn ([]const u8) anyerror!T; + } + + fn getDefaultParser(comptime T: type) ParseFn(T) { + if (T == CrossTarget) return struct { + fn parse(str: []const u8) anyerror!T { + var opts = CrossTarget.ParseOptions{ + .arch_os_abi = str, + }; + return try CrossTarget.parse(opts); + } + }.parse; + + switch (@typeInfo(T)) { + .Int => return struct { + fn parse(str: []const u8) anyerror!T { + return try std.fmt.parseInt(T, str, 0); + } + }.parse, + .Bool => return struct { + fn parse(str: []const u8) anyerror!T { + const as_int = try std.fmt.parseInt(u1, str, 0); + return as_int > 0; + } + }.parse, + .Enum => return struct { + fn parse(str: []const u8) anyerror!T { + return std.meta.stringToEnum(T, str) orelse { + std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str }); + return error.UnknownEnumVariant; + }; + } + }.parse, + .Struct => @compileError("no default parser for " ++ @typeName(T)), + else => @compileError("no default parser for " ++ @typeName(T)), + } + } +}; + +const Cases = @This(); +const builtin = @import("builtin"); +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const CrossTarget = std.zig.CrossTarget; +const Compilation = @import("../../src/Compilation.zig"); +const zig_h = @import("../../src/link.zig").File.C.zig_h; +const introspect = @import("../../src/introspect.zig"); +const ThreadPool = std.Thread.Pool; +const WaitGroup = std.Thread.WaitGroup; +const build_options = @import("build_options"); +const Package = @import("../../src/Package.zig"); + +pub const std_options = struct { + pub const log_level: std.log.Level = .err; +}; + +var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{ + .stack_trace_frames = build_options.mem_leak_frames, +}){}; + +// TODO: instead of embedding the compiler in this process, spawn the compiler +// as a sub-process and communicate the updates using the compiler protocol. +pub fn main() !void { + const use_gpa = build_options.force_gpa or !builtin.link_libc; + const gpa = gpa: { + if (use_gpa) { + break :gpa general_purpose_allocator.allocator(); + } + // We would prefer to use raw libc allocator here, but cannot + // use it if it won't support the alignment we need. + if (@alignOf(std.c.max_align_t) < @alignOf(i128)) { + break :gpa std.heap.c_allocator; + } + break :gpa std.heap.raw_c_allocator; + }; + + var single_threaded_arena = std.heap.ArenaAllocator.init(gpa); + defer single_threaded_arena.deinit(); + + var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ + .child_allocator = single_threaded_arena.allocator(), + }; + const arena = thread_safe_arena.allocator(); + + const args = try std.process.argsAlloc(arena); + const case_file_path = args[1]; + const zig_exe_path = args[2]; + + var filenames = std.ArrayList([]const u8).init(arena); + + const case_dirname = std.fs.path.dirname(case_file_path).?; + var iterable_dir = try std.fs.cwd().openIterableDir(case_dirname, .{}); + defer iterable_dir.close(); + + if (std.mem.endsWith(u8, case_file_path, ".0.zig")) { + const stem = case_file_path[case_dirname.len + 1 .. case_file_path.len - "0.zig".len]; + var it = iterable_dir.iterate(); + while (try it.next()) |entry| { + if (entry.kind != .File) continue; + if (!std.mem.startsWith(u8, entry.name, stem)) continue; + try filenames.append(try std.fs.path.join(arena, &.{ case_dirname, entry.name })); + } + } else { + try filenames.append(case_file_path); + } + + if (filenames.items.len == 0) { + std.debug.print("failed to find the input source file(s) from '{s}'\n", .{ + case_file_path, + }); + std.process.exit(1); + } + + // Sort filenames, so that incremental tests are contiguous and in-order + sortTestFilenames(filenames.items); + + var ctx = Cases.init(gpa, arena); + + var test_it = TestIterator{ .filenames = filenames.items }; + while (test_it.next()) |maybe_batch| { + const batch = maybe_batch orelse break; + const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent; + var cases = std.ArrayList(usize).init(arena); + + for (batch) |filename| { + const max_file_size = 10 * 1024 * 1024; + const src = try iterable_dir.dir.readFileAllocOptions(arena, filename, max_file_size, null, 1, 0); + + // Parse the manifest + var manifest = try TestManifest.parse(arena, src); + + if (cases.items.len == 0) { + const backends = try manifest.getConfigForKeyAlloc(arena, "backend", Backend); + const targets = try manifest.getConfigForKeyAlloc(arena, "target", CrossTarget); + const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); + const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); + + // Cross-product to get all possible test combinations + for (backends) |backend| { + for (targets) |target| { + const next = ctx.cases.items.len; + try ctx.cases.append(.{ + .name = std.fs.path.stem(filename), + .target = target, + .backend = backend, + .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), + .is_test = is_test, + .output_mode = output_mode, + .link_libc = backend == .llvm, + .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + }); + try cases.append(next); + } + } + } + + for (cases.items) |case_index| { + const case = &ctx.cases.items[case_index]; + switch (manifest.type) { + .compile => { + case.addCompile(src); + }, + .@"error" => { + const errors = try manifest.trailingAlloc(arena); + switch (strategy) { + .independent => { + case.addError(src, errors); + }, + .incremental => { + case.addErrorNamed("update", src, errors); + }, + } + }, + .run => { + var output = std.ArrayList(u8).init(arena); + var trailing_it = manifest.trailing(); + while (trailing_it.next()) |line| { + try output.appendSlice(line); + try output.append('\n'); + } + if (output.items.len > 0) { + try output.resize(output.items.len - 1); + } + case.addCompareOutput(src, try output.toOwnedSlice()); + }, + .cli => @panic("TODO cli tests"), + } + } + } + } else |err| { + return err; + } + + return runCases(&ctx, zig_exe_path); +} + +fn runCases(self: *Cases, zig_exe_path: []const u8) !void { + const host = try std.zig.system.NativeTargetInfo.detect(.{}); + + var progress = std.Progress{}; + const root_node = progress.start("compiler", self.cases.items.len); + progress.terminal = null; + defer root_node.end(); + + var zig_lib_directory = try introspect.findZigLibDir(self.gpa); + defer zig_lib_directory.handle.close(); + defer self.gpa.free(zig_lib_directory.path.?); + + var aux_thread_pool: ThreadPool = undefined; + try aux_thread_pool.init(.{ .allocator = self.gpa }); + defer aux_thread_pool.deinit(); + + // Use the same global cache dir for all the tests, such that we for example don't have to + // rebuild musl libc for every case (when LLVM backend is enabled). + var global_tmp = std.testing.tmpDir(.{}); + defer global_tmp.cleanup(); + + var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path }); + defer self.gpa.free(tmp_dir_path); + + const global_cache_directory: Compilation.Directory = .{ + .handle = cache_dir, + .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }), + }; + defer self.gpa.free(global_cache_directory.path.?); + + { + for (self.cases.items) |*case| { + if (build_options.skip_non_native) { + if (case.target.getCpuArch() != builtin.cpu.arch) + continue; + if (case.target.getObjectFormat() != builtin.object_format) + continue; + } + + // Skip tests that require LLVM backend when it is not available + if (!build_options.have_llvm and case.backend == .llvm) + continue; + + assert(case.backend != .stage1); + + if (build_options.test_filter) |test_filter| { + if (std.mem.indexOf(u8, case.name, test_filter) == null) continue; + } + + var prg_node = root_node.start(case.name, case.updates.items.len); + prg_node.activate(); + defer prg_node.end(); + + try runOneCase( + self.gpa, + &prg_node, + case.*, + zig_lib_directory, + zig_exe_path, + &aux_thread_pool, + global_cache_directory, + host, + ); + } + } +} + +fn runOneCase( + allocator: Allocator, + root_node: *std.Progress.Node, + case: Case, + zig_lib_directory: Compilation.Directory, + zig_exe_path: []const u8, + thread_pool: *ThreadPool, + global_cache_directory: Compilation.Directory, + host: std.zig.system.NativeTargetInfo, +) !void { + const tmp_src_path = "tmp.zig"; + const enable_rosetta = build_options.enable_rosetta; + const enable_qemu = build_options.enable_qemu; + const enable_wine = build_options.enable_wine; + const enable_wasmtime = build_options.enable_wasmtime; + const enable_darling = build_options.enable_darling; + const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir; + + const target_info = try std.zig.system.NativeTargetInfo.detect(case.target); + const target = target_info.target; + + var arena_allocator = std.heap.ArenaAllocator.init(allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + + const tmp_dir_path = try std.fs.path.join( + arena, + &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path }, + ); + const local_cache_path = try std.fs.path.join( + arena, + &[_][]const u8{ tmp_dir_path, "zig-cache" }, + ); + + const zig_cache_directory: Compilation.Directory = .{ + .handle = cache_dir, + .path = local_cache_path, + }; + + var main_pkg: Package = .{ + .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, + .root_src_path = tmp_src_path, + }; + defer { + var it = main_pkg.table.iterator(); + while (it.next()) |kv| { + allocator.free(kv.key_ptr.*); + kv.value_ptr.*.destroy(allocator); + } + main_pkg.table.deinit(allocator); + } + + for (case.deps.items) |dep| { + var pkg = try Package.create( + allocator, + tmp_dir_path, + dep.path, + ); + errdefer pkg.destroy(allocator); + try main_pkg.add(allocator, dep.name, pkg); + } + + const bin_name = try std.zig.binNameAlloc(arena, .{ + .root_name = "test_case", + .target = target, + .output_mode = case.output_mode, + }); + + const emit_directory: Compilation.Directory = .{ + .path = tmp_dir_path, + .handle = tmp.dir, + }; + const emit_bin: Compilation.EmitLoc = .{ + .directory = emit_directory, + .basename = bin_name, + }; + const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{ + .directory = emit_directory, + .basename = "test_case.h", + } else null; + const use_llvm: bool = switch (case.backend) { + .llvm => true, + else => false, + }; + const comp = try Compilation.create(allocator, .{ + .local_cache_directory = zig_cache_directory, + .global_cache_directory = global_cache_directory, + .zig_lib_directory = zig_lib_directory, + .thread_pool = thread_pool, + .root_name = "test_case", + .target = target, + // TODO: support tests for object file building, and library builds + // and linking. This will require a rework to support multi-file + // tests. + .output_mode = case.output_mode, + .is_test = case.is_test, + .optimize_mode = case.optimize_mode, + .emit_bin = emit_bin, + .emit_h = emit_h, + .main_pkg = &main_pkg, + .keep_source_files_loaded = true, + .is_native_os = case.target.isNativeOs(), + .is_native_abi = case.target.isNativeAbi(), + .dynamic_linker = target_info.dynamic_linker.get(), + .link_libc = case.link_libc, + .use_llvm = use_llvm, + .self_exe_path = zig_exe_path, + // TODO instead of turning off color, pass in a std.Progress.Node + .color = .off, + .reference_trace = 0, + // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in + // until the auto-select mechanism deems them worthy + .use_lld = switch (case.backend) { + .stage2 => false, + else => null, + }, + }); + defer comp.destroy(); + + update: for (case.updates.items, 0..) |update, update_index| { + var update_node = root_node.start(update.name, 3); + update_node.activate(); + defer update_node.end(); + + var sync_node = update_node.start("write", 0); + sync_node.activate(); + for (update.files.items) |file| { + try tmp.dir.writeFile(file.path, file.src); + } + sync_node.end(); + + var module_node = update_node.start("parse/analysis/codegen", 0); + module_node.activate(); + try comp.makeBinFileWritable(); + try comp.update(&module_node); + module_node.end(); + + if (update.case != .Error) { + var all_errors = try comp.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + if (all_errors.errorMessageCount() > 0) { + all_errors.renderToStdErr(.{ + .ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()), + }); + // TODO print generated C code + return error.UnexpectedCompileErrors; + } + } + + switch (update.case) { + .Header => |expected_output| { + var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only }); + defer file.close(); + const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); + + try std.testing.expectEqualStrings(expected_output, out); + }, + .CompareObjectFile => |expected_output| { + var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only }); + defer file.close(); + const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024); + + try std.testing.expectEqualStrings(expected_output, out); + }, + .Compile => {}, + .Error => |expected_errors| { + var test_node = update_node.start("assert", 0); + test_node.activate(); + defer test_node.end(); + + var error_bundle = try comp.getAllErrorsAlloc(); + defer error_bundle.deinit(allocator); + + if (error_bundle.errorMessageCount() == 0) { + return error.ExpectedCompilationErrors; + } + + var actual_stderr = std.ArrayList(u8).init(arena); + try error_bundle.renderToWriter(.{ + .ttyconf = .no_color, + .include_reference_trace = false, + .include_source_line = false, + }, actual_stderr.writer()); + + // Render the expected lines into a string that we can compare verbatim. + var expected_generated = std.ArrayList(u8).init(arena); + + var actual_line_it = std.mem.split(u8, actual_stderr.items, "\n"); + for (expected_errors) |expect_line| { + const actual_line = actual_line_it.next() orelse { + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + continue; + }; + if (std.mem.endsWith(u8, actual_line, expect_line)) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + if (std.mem.startsWith(u8, expect_line, ":?:?: ")) { + if (std.mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + } + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + } + + try std.testing.expectEqualStrings(expected_generated.items, actual_stderr.items); + }, + .Execution => |expected_stdout| { + if (!std.process.can_spawn) { + std.debug.print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)}); + continue :update; // Pass test. + } + + update_node.setEstimatedTotalItems(4); + + var argv = std.ArrayList([]const u8).init(allocator); + defer argv.deinit(); + + var exec_result = x: { + var exec_node = update_node.start("execute", 0); + exec_node.activate(); + defer exec_node.end(); + + // We go out of our way here to use the unique temporary directory name in + // the exe_path so that it makes its way into the cache hash, avoiding + // cache collisions from multiple threads doing `zig run` at the same time + // on the same test_case.c input filename. + const ss = std.fs.path.sep_str; + const exe_path = try std.fmt.allocPrint( + arena, + ".." ++ ss ++ "{s}" ++ ss ++ "{s}", + .{ &tmp.sub_path, bin_name }, + ); + if (case.target.ofmt != null and case.target.ofmt.? == .c) { + if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) { + // We wouldn't be able to run the compiled C code. + continue :update; // Pass test. + } + try argv.appendSlice(&[_][]const u8{ + zig_exe_path, + "run", + "-cflags", + "-std=c99", + "-pedantic", + "-Werror", + "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875 + "--", + "-lc", + exe_path, + }); + if (zig_lib_directory.path) |p| { + try argv.appendSlice(&.{ "-I", p }); + } + } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) { + .native => { + if (case.backend == .stage2 and case.target.getCpuArch() == .arm) { + // https://github.com/ziglang/zig/issues/13623 + continue :update; // Pass test. + } + try argv.append(exe_path); + }, + .bad_dl, .bad_os_or_cpu => continue :update, // Pass test. + + .rosetta => if (enable_rosetta) { + try argv.append(exe_path); + } else { + continue :update; // Rosetta not available, pass test. + }, + + .qemu => |qemu_bin_name| if (enable_qemu) { + const need_cross_glibc = target.isGnuLibC() and case.link_libc; + const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc) + glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test + else + null; + try argv.append(qemu_bin_name); + if (glibc_dir_arg) |dir| { + const linux_triple = try target.linuxTriple(arena); + const full_dir = try std.fs.path.join(arena, &[_][]const u8{ + dir, + linux_triple, + }); + + try argv.append("-L"); + try argv.append(full_dir); + } + try argv.append(exe_path); + } else { + continue :update; // QEMU not available; pass test. + }, + + .wine => |wine_bin_name| if (enable_wine) { + try argv.append(wine_bin_name); + try argv.append(exe_path); + } else { + continue :update; // Wine not available; pass test. + }, + + .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) { + try argv.append(wasmtime_bin_name); + try argv.append("--dir=."); + try argv.append(exe_path); + } else { + continue :update; // wasmtime not available; pass test. + }, + + .darling => |darling_bin_name| if (enable_darling) { + try argv.append(darling_bin_name); + // Since we use relative to cwd here, we invoke darling with + // "shell" subcommand. + try argv.append("shell"); + try argv.append(exe_path); + } else { + continue :update; // Darling not available; pass test. + }, + } + + try comp.makeBinFileExecutable(); + + while (true) { + break :x std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = argv.items, + .cwd_dir = tmp.dir, + .cwd = tmp_dir_path, + }) catch |err| switch (err) { + error.FileBusy => { + // There is a fundamental design flaw in Unix systems with how + // ETXTBSY interacts with fork+exec. + // https://github.com/golang/go/issues/22315 + // https://bugs.openjdk.org/browse/JDK-8068370 + // Unfortunately, this could be a real error, but we can't + // tell the difference here. + continue; + }, + else => { + std.debug.print("\n{s}.{d} The following command failed with {s}:\n", .{ + case.name, update_index, @errorName(err), + }); + dumpArgs(argv.items); + return error.ChildProcessExecution; + }, + }; + } + }; + var test_node = update_node.start("test", 0); + test_node.activate(); + defer test_node.end(); + defer allocator.free(exec_result.stdout); + defer allocator.free(exec_result.stderr); + switch (exec_result.term) { + .Exited => |code| { + if (code != 0) { + std.debug.print("\n{s}\n{s}: execution exited with code {d}:\n", .{ + exec_result.stderr, case.name, code, + }); + dumpArgs(argv.items); + return error.ChildProcessExecution; + } + }, + else => { + std.debug.print("\n{s}\n{s}: execution crashed:\n", .{ + exec_result.stderr, case.name, + }); + dumpArgs(argv.items); + return error.ChildProcessExecution; + }, + } + try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout); + // We allow stderr to have garbage in it because wasmtime prints a + // warning about --invoke even though we don't pass it. + //std.testing.expectEqualStrings("", exec_result.stderr); + }, + } + } +} + +fn dumpArgs(argv: []const []const u8) void { + for (argv) |arg| { + std.debug.print("{s} ", .{arg}); + } + std.debug.print("\n", .{}); +} diff --git a/test/tests.zig b/test/tests.zig index 2cd06b18b9..c07b669376 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1055,3 +1055,30 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S } return step; } + +pub fn addCases( + b: *std.Build, + parent_step: *Step, + opt_test_filter: ?[]const u8, + check_case_exe: *std.Build.CompileStep, +) !void { + const arena = b.allocator; + const gpa = b.allocator; + + var cases = @import("src/Cases.zig").init(gpa, arena); + + var dir = try b.build_root.handle.openIterableDir("test/cases", .{}); + defer dir.close(); + + cases.addFromDir(dir); + try @import("cases.zig").addCases(&cases); + + const cases_dir_path = try b.build_root.join(b.allocator, &.{ "test", "cases" }); + cases.lowerToBuildSteps( + b, + parent_step, + opt_test_filter, + cases_dir_path, + check_case_exe, + ); +} From 59f5df3af9c31ffef33862c15bcbce6b4227ff1f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 21:16:53 -0700 Subject: [PATCH 232/294] std.Build: use Cache hash helper for package prefix dirs Previously this code used SipHash(1, 3) directly; now that we have the cache system available in the build system, borrow the same implementation as is being used everywhere else. --- lib/std/Build.zig | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index c091079b38..58cead7e7a 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -384,20 +384,17 @@ fn applyArgs(b: *Build, args: anytype) !void { }, } } - const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); + + // Create an installation directory local to this package. This will be used when + // dependant packages require a standard prefix, such as include directories for C headers. + var hash = b.cache.hash; // Random bytes to make unique. Refresh this with new random bytes when // implementation is modified in a non-backwards-compatible way. - var hash = Hasher.init("ZaEsvQ5ClaA2IdH9"); - hash.update(b.dep_prefix); + hash.add(@as(u32, 0xd8cb0055)); + hash.addBytes(b.dep_prefix); // TODO additionally update the hash with `args`. - - var digest: [16]u8 = undefined; - hash.final(&digest); - var hash_basename: [digest.len * 2]u8 = undefined; - _ = std.fmt.bufPrint(&hash_basename, "{s}", .{std.fmt.fmtSliceHexLower(&digest)}) catch - unreachable; - - const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &hash_basename }); + const digest = hash.final(); + const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest }); b.resolveInstallPrefix(install_prefix, .{}); } From a333bb91ffcf63fbe377ddc82fd7f1633e269430 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 21:17:56 -0700 Subject: [PATCH 233/294] zig objcopy: support the compiler protocol This commit extracts out server code into src/Server.zig and uses it both in the main CLI as well as `zig objcopy`. std.Build.ObjCopyStep now adds `--listen=-` to the CLI for `zig objcopy` and observes the protocol for progress and other kinds of integrations. This fixes the last two test failures of this branch when I run `zig build test` locally. --- CMakeLists.txt | 1 + lib/std/Build/ObjCopyStep.zig | 2 + src/Server.zig | 113 ++++++++++++++++++++++++++++++++++ src/main.zig | 111 ++++++--------------------------- src/objcopy.zig | 47 ++++++++++++-- 5 files changed, 177 insertions(+), 97 deletions(-) create mode 100644 src/Server.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index c77c66add4..7ff8249f4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -623,6 +623,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/print_targets.zig" "${CMAKE_SOURCE_DIR}/src/print_zir.zig" "${CMAKE_SOURCE_DIR}/src/register_manager.zig" + "${CMAKE_SOURCE_DIR}/src/Server.zig" "${CMAKE_SOURCE_DIR}/src/target.zig" "${CMAKE_SOURCE_DIR}/src/tracy.zig" "${CMAKE_SOURCE_DIR}/src/translate_c.zig" diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig index 5f675ed383..608c56591f 100644 --- a/lib/std/Build/ObjCopyStep.zig +++ b/lib/std/Build/ObjCopyStep.zig @@ -113,6 +113,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }; try argv.appendSlice(&.{ full_src_path, full_dest_path }); + + try argv.append("--listen=-"); _ = try step.evalZigProcess(argv.items, prog_node); self.output_file.path = full_dest_path; diff --git a/src/Server.zig b/src/Server.zig new file mode 100644 index 0000000000..a25dc93857 --- /dev/null +++ b/src/Server.zig @@ -0,0 +1,113 @@ +in: std.fs.File, +out: std.fs.File, +receive_fifo: std.fifo.LinearFifo(u8, .Dynamic), + +pub const Options = struct { + gpa: Allocator, + in: std.fs.File, + out: std.fs.File, +}; + +pub fn init(options: Options) !Server { + var s: Server = .{ + .in = options.in, + .out = options.out, + .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa), + }; + try s.serveStringMessage(.zig_version, build_options.version); + return s; +} + +pub fn deinit(s: *Server) void { + s.receive_fifo.deinit(); + s.* = undefined; +} + +pub fn receiveMessage(s: *Server) !InMessage.Header { + const Header = InMessage.Header; + const fifo = &s.receive_fifo; + + while (true) { + const buf = fifo.readableSlice(0); + assert(fifo.readableLength() == buf.len); + if (buf.len >= @sizeOf(Header)) { + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + if (header.bytes_len != 0) + return error.InvalidClientMessage; + const result = header.*; + fifo.discard(@sizeOf(Header)); + return result; + } + + const write_buffer = try fifo.writableWithSize(256); + const amt = try s.in.read(write_buffer); + fifo.update(amt); + } +} + +pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { + return s.serveMessage(.{ + .tag = tag, + .bytes_len = @intCast(u32, msg.len), + }, &.{msg}); +} + +pub fn serveMessage( + s: *const Server, + header: OutMessage.Header, + bufs: []const []const u8, +) !void { + var iovecs: [10]std.os.iovec_const = undefined; + iovecs[0] = .{ + .iov_base = @ptrCast([*]const u8, &header), + .iov_len = @sizeOf(OutMessage.Header), + }; + for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { + iovec.* = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + } + try s.out.writevAll(iovecs[0 .. bufs.len + 1]); +} + +pub fn serveEmitBinPath( + s: *Server, + fs_path: []const u8, + header: std.zig.Server.Message.EmitBinPath, +) !void { + try s.serveMessage(.{ + .tag = .emit_bin_path, + .bytes_len = @intCast(u32, fs_path.len + @sizeOf(std.zig.Server.Message.EmitBinPath)), + }, &.{ + std.mem.asBytes(&header), + fs_path, + }); +} + +pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { + const eb_hdr: std.zig.Server.Message.ErrorBundle = .{ + .extra_len = @intCast(u32, error_bundle.extra.len), + .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), + }; + const bytes_len = @sizeOf(std.zig.Server.Message.ErrorBundle) + + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; + try s.serveMessage(.{ + .tag = .error_bundle, + .bytes_len = @intCast(u32, bytes_len), + }, &.{ + std.mem.asBytes(&eb_hdr), + // TODO: implement @ptrCast between slices changing the length + std.mem.sliceAsBytes(error_bundle.extra), + error_bundle.string_bytes, + }); +} + +const OutMessage = std.zig.Server.Message; +const InMessage = std.zig.Client.Message; + +const Server = @This(); +const std = @import("std"); +const build_options = @import("build_options"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; diff --git a/src/main.zig b/src/main.zig index 03d746af0c..e0283143d0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -26,6 +26,7 @@ const target_util = @import("target.zig"); const crash_report = @import("crash_report.zig"); const Module = @import("Module.zig"); const AstGen = @import("AstGen.zig"); +const Server = @import("Server.zig"); pub const std_options = struct { pub const wasiCwd = wasi_cwd; @@ -3540,11 +3541,14 @@ fn serve( ) !void { const gpa = comp.gpa; - try serveStringMessage(out, .zig_version, build_options.version); + var server = try Server.init(.{ + .gpa = gpa, + .in = in, + .out = out, + }); + defer server.deinit(); var child_pid: ?std.ChildProcess.Id = null; - var receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(gpa); - defer receive_fifo.deinit(); var progress: std.Progress = .{ .terminal = null, @@ -3564,7 +3568,7 @@ fn serve( main_progress_node.context = &progress; while (true) { - const hdr = try receiveMessage(in, &receive_fifo); + const hdr = try server.receiveMessage(); switch (hdr.tag) { .exit => { @@ -3580,7 +3584,7 @@ fn serve( const arena = arena_instance.allocator(); var output: TranslateCOutput = undefined; try cmdTranslateC(comp, arena, &output); - try serveEmitBinPath(out, output.path, .{ + try server.serveEmitBinPath(output.path, .{ .flags = .{ .cache_hit = output.cache_hit }, }); continue; @@ -3594,7 +3598,7 @@ fn serve( var reset: std.Thread.ResetEvent = .{}; var progress_thread = try std.Thread.spawn(.{}, progressThread, .{ - &progress, out, &reset, + &progress, &server, &reset, }); defer { reset.set(); @@ -3605,7 +3609,7 @@ fn serve( } try comp.makeBinFileExecutable(); - try serveUpdateResults(out, comp); + try serveUpdateResults(&server, comp); }, .run => { if (child_pid != null) { @@ -3632,14 +3636,14 @@ fn serve( assert(main_progress_node.recently_updated_child == null); if (child_pid) |pid| { try comp.hotCodeSwap(main_progress_node, pid); - try serveUpdateResults(out, comp); + try serveUpdateResults(&server, comp); } else { if (comp.bin_file.options.output_mode == .Exe) { try comp.makeBinFileWritable(); } try comp.update(main_progress_node); try comp.makeBinFileExecutable(); - try serveUpdateResults(out, comp); + try serveUpdateResults(&server, comp); child_pid = try runOrTestHotSwap( comp, @@ -3659,7 +3663,7 @@ fn serve( } } -fn progressThread(progress: *std.Progress, out: fs.File, reset: *std.Thread.ResetEvent) void { +fn progressThread(progress: *std.Progress, server: *const Server, reset: *std.Thread.ResetEvent) void { while (true) { if (reset.timedWait(500 * std.time.ns_per_ms)) |_| { // The Compilation update has completed. @@ -3705,7 +3709,7 @@ fn progressThread(progress: *std.Progress, out: fs.File, reset: *std.Thread.Rese const progress_string = buf.slice(); - serveMessage(out, .{ + server.serveMessage(.{ .tag = .progress, .bytes_len = @intCast(u32, progress_string.len), }, &.{ @@ -3716,100 +3720,21 @@ fn progressThread(progress: *std.Progress, out: fs.File, reset: *std.Thread.Rese } } -fn serveMessage( - out: fs.File, - header: std.zig.Server.Message.Header, - bufs: []const []const u8, -) !void { - var iovecs: [10]std.os.iovec_const = undefined; - iovecs[0] = .{ - .iov_base = @ptrCast([*]const u8, &header), - .iov_len = @sizeOf(std.zig.Server.Message.Header), - }; - for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { - iovec.* = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - } - try out.writevAll(iovecs[0 .. bufs.len + 1]); -} - -fn serveErrorBundle(out: fs.File, error_bundle: std.zig.ErrorBundle) !void { - const eb_hdr: std.zig.Server.Message.ErrorBundle = .{ - .extra_len = @intCast(u32, error_bundle.extra.len), - .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), - }; - const bytes_len = @sizeOf(std.zig.Server.Message.ErrorBundle) + - 4 * error_bundle.extra.len + error_bundle.string_bytes.len; - try serveMessage(out, .{ - .tag = .error_bundle, - .bytes_len = @intCast(u32, bytes_len), - }, &.{ - std.mem.asBytes(&eb_hdr), - // TODO: implement @ptrCast between slices changing the length - std.mem.sliceAsBytes(error_bundle.extra), - error_bundle.string_bytes, - }); -} - -fn serveUpdateResults(out: fs.File, comp: *Compilation) !void { +fn serveUpdateResults(s: *Server, comp: *Compilation) !void { const gpa = comp.gpa; var error_bundle = try comp.getAllErrorsAlloc(); defer error_bundle.deinit(gpa); if (error_bundle.errorMessageCount() > 0) { - try serveErrorBundle(out, error_bundle); + try s.serveErrorBundle(error_bundle); } else if (comp.bin_file.options.emit) |emit| { const full_path = try emit.directory.join(gpa, &.{emit.sub_path}); defer gpa.free(full_path); - try serveEmitBinPath(out, full_path, .{ + try s.serveEmitBinPath(full_path, .{ .flags = .{ .cache_hit = comp.last_update_was_cache_hit }, }); } } -fn serveEmitBinPath( - out: fs.File, - fs_path: []const u8, - header: std.zig.Server.Message.EmitBinPath, -) !void { - try serveMessage(out, .{ - .tag = .emit_bin_path, - .bytes_len = @intCast(u32, fs_path.len + @sizeOf(std.zig.Server.Message.EmitBinPath)), - }, &.{ - std.mem.asBytes(&header), - fs_path, - }); -} - -fn serveStringMessage(out: fs.File, tag: std.zig.Server.Message.Tag, s: []const u8) !void { - try serveMessage(out, .{ - .tag = tag, - .bytes_len = @intCast(u32, s.len), - }, &.{s}); -} - -fn receiveMessage(in: fs.File, fifo: *std.fifo.LinearFifo(u8, .Dynamic)) !std.zig.Client.Message.Header { - const Header = std.zig.Client.Message.Header; - - while (true) { - const buf = fifo.readableSlice(0); - assert(fifo.readableLength() == buf.len); - if (buf.len >= @sizeOf(Header)) { - const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - if (header.bytes_len != 0) - return error.InvalidClientMessage; - const result = header.*; - fifo.discard(@sizeOf(Header)); - return result; - } - - const write_buffer = try fifo.writableWithSize(256); - const amt = try in.read(write_buffer); - fifo.update(amt); - } -} - const ModuleDepIterator = struct { split: mem.SplitIterator(u8), diff --git a/src/objcopy.zig b/src/objcopy.zig index 31e3d60d0d..e821a94b59 100644 --- a/src/objcopy.zig +++ b/src/objcopy.zig @@ -4,22 +4,25 @@ const fs = std.fs; const elf = std.elf; const Allocator = std.mem.Allocator; const File = std.fs.File; +const assert = std.debug.assert; + const main = @import("main.zig"); const fatal = main.fatal; const cleanExit = main.cleanExit; +const Server = @import("Server.zig"); pub fn cmdObjCopy( gpa: Allocator, arena: Allocator, args: []const []const u8, ) !void { - _ = gpa; var i: usize = 0; var opt_out_fmt: ?std.Target.ObjectFormat = null; var opt_input: ?[]const u8 = null; var opt_output: ?[]const u8 = null; var only_section: ?[]const u8 = null; var pad_to: ?u64 = null; + var listen = false; while (i < args.len) : (i += 1) { const arg = args[i]; if (!mem.startsWith(u8, arg, "-")) { @@ -54,6 +57,8 @@ pub fn cmdObjCopy( i += 1; if (i >= args.len) fatal("expected another argument after '{s}'", .{arg}); only_section = args[i]; + } else if (mem.eql(u8, arg, "--listen=-")) { + listen = true; } else if (mem.startsWith(u8, arg, "--only-section=")) { only_section = arg["--output-target=".len..]; } else if (mem.eql(u8, arg, "--pad-to")) { @@ -102,10 +107,44 @@ pub fn cmdObjCopy( .only_section = only_section, .pad_to = pad_to, }); - return cleanExit(); }, else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}), } + + if (listen) { + var server = try Server.init(.{ + .gpa = gpa, + .in = std.io.getStdIn(), + .out = std.io.getStdOut(), + }); + defer server.deinit(); + + var seen_update = false; + while (true) { + const hdr = try server.receiveMessage(); + switch (hdr.tag) { + .exit => { + return cleanExit(); + }, + .update => { + if (seen_update) { + std.debug.print("zig objcopy only supports 1 update for now\n", .{}); + std.process.exit(1); + } + seen_update = true; + + try server.serveEmitBinPath(output, .{ + .flags = .{ .cache_hit = false }, + }); + }, + else => { + std.debug.print("unsupported message: {s}", .{@tagName(hdr.tag)}); + std.process.exit(1); + }, + } + } + } + return cleanExit(); } const usage = @@ -417,7 +456,7 @@ const HexWriter = struct { } fn Address(address: u32) Record { - std.debug.assert(address > 0xFFFF); + assert(address > 0xFFFF); const segment = @intCast(u16, address / 0x10000); if (address > 0xFFFFF) { return Record{ @@ -460,7 +499,7 @@ const HexWriter = struct { const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len; var outbuf: [BUFSIZE]u8 = undefined; const payload_bytes = self.getPayloadBytes(); - std.debug.assert(payload_bytes.len <= MAX_PAYLOAD_LEN); + assert(payload_bytes.len <= MAX_PAYLOAD_LEN); const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{ @intCast(u8, payload_bytes.len), From f31aeb0010c950430a6a388f6933498cd8e29cbc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:02:37 -0700 Subject: [PATCH 234/294] std.Build.WriteFileStep: add missing step dependencies --- lib/std/Build/WriteFileStep.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index e6ceb4777c..9b033e5ae2 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -83,6 +83,7 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [ wf.files.append(gpa, file) catch @panic("OOM"); wf.maybeUpdateName(); + source.addStepDependencies(&wf.step); } /// A path relative to the package root. @@ -97,6 +98,7 @@ pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub .contents = .{ .copy = source }, .sub_path = sub_path, }) catch @panic("OOM"); + source.addStepDependencies(&wf.step); } /// A path relative to the package root. From 2c491d734ee82947b2a77c08c1c5328e7a5fe771 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:02:53 -0700 Subject: [PATCH 235/294] docgen: don't print progress in dumb terminals --- doc/docgen.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index fae513f8c3..67163ca427 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1270,7 +1270,7 @@ fn genHtml( zig_exe: []const u8, do_code_tests: bool, ) !void { - var progress = Progress{}; + var progress = Progress{ .dont_print_on_dumb = true }; const root_node = progress.start("Generating docgen examples", toc.nodes.len); defer root_node.end(); From f829f848ddc390e6f14e8da3eee452bd7ead5a3a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:03:36 -0700 Subject: [PATCH 236/294] std.Build.InstallFileStep: add missing step dependencies in the creation function, which had to change from init() to create(). --- lib/std/Build.zig | 11 ++--------- lib/std/Build/InstallFileStep.zig | 11 ++++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 58cead7e7a..5f74af3db6 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1220,12 +1220,7 @@ pub fn addInstallFileWithDir( install_dir: InstallDir, dest_rel_path: []const u8, ) *InstallFileStep { - if (dest_rel_path.len == 0) { - panic("dest_rel_path must be non-empty", .{}); - } - const install_step = self.allocator.create(InstallFileStep) catch @panic("OOM"); - install_step.* = InstallFileStep.init(self, source.dupe(self), install_dir, dest_rel_path); - return install_step; + return InstallFileStep.create(self, source.dupe(self), install_dir, dest_rel_path); } pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep { @@ -1685,9 +1680,7 @@ pub const InstallDir = union(enum) { /// Duplicates the install directory including the path if set to custom. pub fn dupe(self: InstallDir, builder: *Build) InstallDir { if (self == .custom) { - // Written with this temporary to avoid RLS problems - const duped_path = builder.dupe(self.custom); - return .{ .custom = duped_path }; + return .{ .custom = builder.dupe(self.custom) }; } else { return self; } diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig index a77fa10b43..011ad48208 100644 --- a/lib/std/Build/InstallFileStep.zig +++ b/lib/std/Build/InstallFileStep.zig @@ -3,6 +3,7 @@ const Step = std.Build.Step; const FileSource = std.Build.FileSource; const InstallDir = std.Build.InstallDir; const InstallFileStep = @This(); +const assert = std.debug.assert; pub const base_id = .install_file; @@ -14,14 +15,16 @@ dest_rel_path: []const u8, /// package but is being installed by another. dest_builder: *std.Build, -pub fn init( +pub fn create( owner: *std.Build, source: FileSource, dir: InstallDir, dest_rel_path: []const u8, -) InstallFileStep { +) *InstallFileStep { + assert(dest_rel_path.len != 0); owner.pushInstalledFile(dir, dest_rel_path); - return InstallFileStep{ + const self = owner.allocator.create(InstallFileStep) catch @panic("OOM"); + self.* = .{ .step = Step.init(.{ .id = base_id, .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), @@ -33,6 +36,8 @@ pub fn init( .dest_rel_path = owner.dupePath(dest_rel_path), .dest_builder = owner, }; + source.addStepDependencies(&self.step); + return self; } fn make(step: *Step, prog_node: *std.Progress.Node) !void { From 857296a9f4bc56c3bf710b11a0868c4cf15b6ff3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:04:09 -0700 Subject: [PATCH 237/294] build.zig: update docgen to modern build system API it still writes the output to zig-cache/langref.html but now it does that explicitly as a legacy step with the intention of having that removed in the future. It also outputs the langref to the install prefix. --- build.zig | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/build.zig b/build.zig index 9ba2b1acef..8cf25cd6fd 100644 --- a/build.zig +++ b/build.zig @@ -40,19 +40,22 @@ pub fn build(b: *std.Build) !void { }); docgen_exe.single_threaded = single_threaded; - const langref_out_path = try b.cache_root.join(b.allocator, &.{"langref.html"}); - const docgen_cmd = docgen_exe.run(); - docgen_cmd.addArgs(&[_][]const u8{ - "--zig", - b.zig_exe, - "doc" ++ fs.path.sep_str ++ "langref.html.in", - langref_out_path, - }); - docgen_cmd.step.dependOn(&docgen_exe.step); + const docgen_cmd = b.addRunArtifact(docgen_exe); + docgen_cmd.addArgs(&.{ "--zig", b.zig_exe }); + docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" }); + const langref_file = docgen_cmd.addOutputFileArg("langref.html"); + const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "langref.html"); + b.getInstallStep().dependOn(&install_langref.step); const docs_step = b.step("docs", "Build documentation"); docs_step.dependOn(&docgen_cmd.step); + // This is for legacy reasons, to be removed after our CI scripts are upgraded to use + // the file from the install prefix instead. + const legacy_write_to_cache = b.addWriteFiles(); + legacy_write_to_cache.addCopyFileToSource(langref_file, "zig-cache/langref.html"); + docs_step.dependOn(&legacy_write_to_cache.step); + const check_case_exe = b.addExecutable(.{ .name = "check-case", .root_source_file = .{ .path = "test/src/Cases.zig" }, From 28bda2eab04e217fe4ba801a9710b2225d141940 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:12:36 -0700 Subject: [PATCH 238/294] make -Dno-lib also skip docgen --- build.zig | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/build.zig b/build.zig index 8cf25cd6fd..21586963bd 100644 --- a/build.zig +++ b/build.zig @@ -31,6 +31,11 @@ pub fn build(b: *std.Build) !void { const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false; const test_step = b.step("test", "Run all the tests"); + const deprecated_skip_install_lib_files = b.option(bool, "skip-install-lib-files", "deprecated. see no-lib") orelse false; + if (deprecated_skip_install_lib_files) { + std.log.warn("-Dskip-install-lib-files is deprecated in favor of -Dno-lib", .{}); + } + const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse deprecated_skip_install_lib_files; const docgen_exe = b.addExecutable(.{ .name = "docgen", @@ -45,7 +50,9 @@ pub fn build(b: *std.Build) !void { docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" }); const langref_file = docgen_cmd.addOutputFileArg("langref.html"); const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "langref.html"); - b.getInstallStep().dependOn(&install_langref.step); + if (!skip_install_lib_files) { + b.getInstallStep().dependOn(&install_langref.step); + } const docs_step = b.step("docs", "Build documentation"); docs_step.dependOn(&docgen_cmd.step); @@ -76,11 +83,6 @@ pub fn build(b: *std.Build) !void { const skip_stage1 = b.option(bool, "skip-stage1", "Main test suite skips stage1 compile error tests") orelse false; const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false; const skip_stage2_tests = b.option(bool, "skip-stage2-tests", "Main test suite skips self-hosted compiler tests") orelse false; - const deprecated_skip_install_lib_files = b.option(bool, "skip-install-lib-files", "deprecated. see no-lib") orelse false; - if (deprecated_skip_install_lib_files) { - std.log.warn("-Dskip-install-lib-files is deprecated in favor of -Dno-lib", .{}); - } - const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files to installation prefix. Useful for development") orelse deprecated_skip_install_lib_files; const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; From bf73620cbdb4b9ae6088d44bb88daa4a7d84ed70 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:53:59 -0800 Subject: [PATCH 239/294] build runner: communicate TTY conf to child procs via env vars --- lib/build_runner.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 8e0ebb4558..e1c3768048 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -272,6 +272,11 @@ pub fn main() !void { const stderr = std.io.getStdErr(); const ttyconf = get_tty_conf(color, stderr); + switch (ttyconf) { + .no_color => try builder.env_map.put("NO_COLOR", "1"), + .escape_codes => try builder.env_map.put("ZIG_DEBUG_COLOR", "1"), + .windows_api => {}, + } var progress: std.Progress = .{ .dont_print_on_dumb = true }; const main_progress_node = progress.start("", 0); From 097bcca069c5c7abdf77f321182eb4a0c54b3a93 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 22:54:32 -0800 Subject: [PATCH 240/294] build.zig: fix how test-cases marked is_test=1 are handled --- test/src/Cases.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 27b0cf6b21..5007939b14 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -485,7 +485,12 @@ pub fn lowerToBuildSteps( } const root_source_file = writefiles.getFileSource(update.files.items[0].path).?; - const artifact = switch (case.output_mode) { + const artifact = if (case.is_test) b.addTest(.{ + .root_source_file = root_source_file, + .name = case.name, + .target = case.target, + .optimize = case.optimize_mode, + }) else switch (case.output_mode) { .Obj => b.addObject(.{ .root_source_file = root_source_file, .name = case.name, @@ -498,12 +503,7 @@ pub fn lowerToBuildSteps( .target = case.target, .optimize = case.optimize_mode, }), - .Exe => if (case.is_test) b.addTest(.{ - .root_source_file = root_source_file, - .name = case.name, - .target = case.target, - .optimize = case.optimize_mode, - }) else b.addExecutable(.{ + .Exe => b.addExecutable(.{ .root_source_file = root_source_file, .name = case.name, .target = case.target, From 7d5bce56e16cd7661b79046d573427972bbb6cf5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 23:56:12 -0700 Subject: [PATCH 241/294] build runner: print to stderr in dumb terminals Terminal progress is suppressed and instead there is an explicit handling of printing to stderr, one line per step make() function call. The output looks very similar to Ninja. A future commit should add a -q to quiet the output. --- lib/build_runner.zig | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index e1c3768048..a1b88f8c0b 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -278,8 +278,12 @@ pub fn main() !void { .windows_api => {}, } - var progress: std.Progress = .{ .dont_print_on_dumb = true }; + var progress: std.Progress = .{}; const main_progress_node = progress.start("", 0); + if (ttyconf == .no_color) { + progress.timer = null; + progress.terminal = null; + } builder.debug_log_scopes = debug_log_scopes.items; builder.resolveInstallPrefix(install_prefix, dir_list); @@ -302,6 +306,9 @@ pub fn main() !void { .enable_summary = enable_summary, .ttyconf = ttyconf, .stderr = stderr, + + .step_index = 0, + .step_count = undefined, }; if (run.max_rss == 0) { @@ -332,6 +339,9 @@ const Run = struct { enable_summary: ?bool, ttyconf: std.debug.TTY.Config, stderr: std.fs.File, + + step_count: usize, + step_index: usize, }; fn runStepNames( @@ -395,7 +405,8 @@ fn runStepNames( { defer parent_prog_node.end(); - var step_prog = parent_prog_node.start("run steps", step_stack.count()); + run.step_count = step_stack.count(); + var step_prog = parent_prog_node.start("run steps", run.step_count); defer step_prog.end(); var wait_group: std.Thread.WaitGroup = .{}; @@ -739,10 +750,27 @@ fn workerMakeOneStep( sub_prog_node.activate(); defer sub_prog_node.end(); - // I suspect we will want to pass `b` to make() in a future modification. - // For example, CompileStep does some sus things with modifying the saved - // *Build object in install header steps that might be able to be removed - // by passing the *Build object through the make() functions. + const stderr = run.stderr; + const ttyconf = run.ttyconf; + + // If we are unable to print a fancy terminal progress bar, then we resort + // to 1 line printed to stderr for each step, similar to Ninja. + if (ttyconf == .no_color) { + var buf: [120]u8 = undefined; + const step_index = @atomicRmw(usize, &run.step_index, .Add, 1, .Monotonic); + const text = std.fmt.bufPrint(&buf, "[{d}/{d}] Making {s}{s}\n", .{ + step_index + 1, run.step_count, s.owner.dep_prefix, s.name, + }) catch |err| switch (err) { + error.NoSpaceLeft => blk: { + buf[buf.len - 4 ..].* = "...\n".*; + break :blk &buf; + }, + }; + std.debug.getStderrMutex().lock(); + defer std.debug.getStderrMutex().unlock(); + stderr.writeAll(text) catch {}; + } + const make_result = s.make(&sub_prog_node); // No matter the result, we want to display error/warning messages. @@ -750,9 +778,6 @@ fn workerMakeOneStep( sub_prog_node.context.lock_stderr(); defer sub_prog_node.context.unlock_stderr(); - const stderr = run.stderr; - const ttyconf = run.ttyconf; - for (s.result_error_msgs.items) |msg| { // Sometimes it feels like you just can't catch a break. Finally, // with Zig, you can. From 20b35332fec73956c97087959dbc0fa2f78e5553 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Mar 2023 23:59:11 -0700 Subject: [PATCH 242/294] build.zig: bump maxrss upper bound for std lib tests --- build.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 21586963bd..b2dce0ae08 100644 --- a/build.zig +++ b/build.zig @@ -471,7 +471,9 @@ pub fn build(b: *std.Build) !void { .skip_libc = skip_libc, .skip_stage1 = skip_stage1, .skip_stage2 = true, // TODO get all these passing - .max_rss = 3 * 1024 * 1024 * 1024, + // I observed a value of 3398275072 on my M1, and multiplied by 1.1 to + // get this amount: + .max_rss = 3738102579, })); try addWasiUpdateStep(b, version); From ba7795913704028f53de44f836b81437afb5e33e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 10 Mar 2023 01:02:31 -0800 Subject: [PATCH 243/294] Revert "build runner: print to stderr in dumb terminals" This reverts commit e6f759e1c64668c50d3ff2d02c64a66c871da0ac. I changed my mind. I don't like the output because it makes it harder to find the actual errors in CI logs. --- lib/build_runner.zig | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index a1b88f8c0b..e28be8274d 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -278,12 +278,8 @@ pub fn main() !void { .windows_api => {}, } - var progress: std.Progress = .{}; + var progress: std.Progress = .{ .dont_print_on_dumb = true }; const main_progress_node = progress.start("", 0); - if (ttyconf == .no_color) { - progress.timer = null; - progress.terminal = null; - } builder.debug_log_scopes = debug_log_scopes.items; builder.resolveInstallPrefix(install_prefix, dir_list); @@ -306,9 +302,6 @@ pub fn main() !void { .enable_summary = enable_summary, .ttyconf = ttyconf, .stderr = stderr, - - .step_index = 0, - .step_count = undefined, }; if (run.max_rss == 0) { @@ -339,9 +332,6 @@ const Run = struct { enable_summary: ?bool, ttyconf: std.debug.TTY.Config, stderr: std.fs.File, - - step_count: usize, - step_index: usize, }; fn runStepNames( @@ -405,8 +395,7 @@ fn runStepNames( { defer parent_prog_node.end(); - run.step_count = step_stack.count(); - var step_prog = parent_prog_node.start("run steps", run.step_count); + var step_prog = parent_prog_node.start("run steps", step_stack.count()); defer step_prog.end(); var wait_group: std.Thread.WaitGroup = .{}; @@ -750,27 +739,6 @@ fn workerMakeOneStep( sub_prog_node.activate(); defer sub_prog_node.end(); - const stderr = run.stderr; - const ttyconf = run.ttyconf; - - // If we are unable to print a fancy terminal progress bar, then we resort - // to 1 line printed to stderr for each step, similar to Ninja. - if (ttyconf == .no_color) { - var buf: [120]u8 = undefined; - const step_index = @atomicRmw(usize, &run.step_index, .Add, 1, .Monotonic); - const text = std.fmt.bufPrint(&buf, "[{d}/{d}] Making {s}{s}\n", .{ - step_index + 1, run.step_count, s.owner.dep_prefix, s.name, - }) catch |err| switch (err) { - error.NoSpaceLeft => blk: { - buf[buf.len - 4 ..].* = "...\n".*; - break :blk &buf; - }, - }; - std.debug.getStderrMutex().lock(); - defer std.debug.getStderrMutex().unlock(); - stderr.writeAll(text) catch {}; - } - const make_result = s.make(&sub_prog_node); // No matter the result, we want to display error/warning messages. @@ -778,6 +746,9 @@ fn workerMakeOneStep( sub_prog_node.context.lock_stderr(); defer sub_prog_node.context.unlock_stderr(); + const stderr = run.stderr; + const ttyconf = run.ttyconf; + for (s.result_error_msgs.items) |msg| { // Sometimes it feels like you just can't catch a break. Finally, // with Zig, you can. From f4428e5804971f4bbb897b5d1d5583073bcb31aa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 10 Mar 2023 13:41:54 -0700 Subject: [PATCH 244/294] fix wasm bootstrapping compilation errors --- build.zig | 1 + lib/std/child_process.zig | 1 + 2 files changed, 2 insertions(+) diff --git a/build.zig b/build.zig index b2dce0ae08..44d8f84c59 100644 --- a/build.zig +++ b/build.zig @@ -508,6 +508,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { exe_options.addOption(bool, "enable_tracy_callstack", false); exe_options.addOption(bool, "enable_tracy_allocation", false); exe_options.addOption(bool, "value_tracing", false); + exe_options.addOption(bool, "omit_pkg_fetching_code", true); const run_opt = b.addSystemCommand(&.{ "wasm-opt", "-Oz", "--enable-bulk-memory" }); run_opt.addArtifactArg(exe); diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index f9b2007b3e..3748ca6877 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -22,6 +22,7 @@ const is_darwin = builtin.target.isDarwin(); pub const ChildProcess = struct { pub const Id = switch (builtin.os.tag) { .windows => windows.HANDLE, + .wasi => void, else => os.pid_t, }; From 2c326c87b16a0b8febf5347292ba61a592467d8c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 10 Mar 2023 14:22:54 -0700 Subject: [PATCH 245/294] build.zig: install the langref to $prefix/doc/langref.html and update the CI scripts to match. --- build.zig | 2 +- ci/aarch64-linux-debug.sh | 2 +- ci/aarch64-linux-release.sh | 2 +- ci/x86_64-linux-debug.sh | 2 +- ci/x86_64-linux-release.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.zig b/build.zig index 44d8f84c59..a0c4ca7fb4 100644 --- a/build.zig +++ b/build.zig @@ -49,7 +49,7 @@ pub fn build(b: *std.Build) !void { docgen_cmd.addArgs(&.{ "--zig", b.zig_exe }); docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" }); const langref_file = docgen_cmd.addOutputFileArg("langref.html"); - const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "langref.html"); + const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html"); if (!skip_install_lib_files) { b.getInstallStep().dependOn(&install_langref.step); } diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index 94f40c557b..ba4bcc0c9d 100644 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -67,7 +67,7 @@ stage3-debug/bin/zig build test docs \ --zig-lib-dir "$(pwd)/../lib" # Look for HTML errors. -tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html" +tidy --drop-empty-elements no -qe "stage3-debug/doc/langref.html" # Produce the experimental std lib documentation. stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index 65d6063f25..b3837c4942 100644 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \ --zig-lib-dir "$(pwd)/../lib" # Look for HTML errors. -tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html" +tidy --drop-empty-elements no -qe "stage3-release/doc/langref.html" # Produce the experimental std lib documentation. stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index 7f2382f04a..37dcacb86d 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -66,7 +66,7 @@ stage3-debug/bin/zig build test docs \ --zig-lib-dir "$(pwd)/../lib" # Look for HTML errors. -tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html" +tidy --drop-empty-elements no -qe "stage3-debug/doc/langref.html" # Produce the experimental std lib documentation. stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index cdb24e4a6f..01088a793c 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \ --zig-lib-dir "$(pwd)/../lib" # Look for HTML errors. -tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html" +tidy --drop-empty-elements no -qe "stage3-release/doc/langref.html" # Produce the experimental std lib documentation. stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib From 2956232b427276c6ad1e52678e1cf3651976ec39 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 10 Mar 2023 14:32:49 -0700 Subject: [PATCH 246/294] standalone tests: avoid running on strange target Without this, aarch64-linux tried to compile this test for aarch64-windows with the same CPU settings, which is not an intended test combination. --- test/standalone.zig | 4 +++- test/tests.zig | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/standalone.zig b/test/standalone.zig index 6e0adcaa00..29a32d878f 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -5,6 +5,8 @@ pub const SimpleCase = struct { target: std.zig.CrossTarget = .{}, is_test: bool = false, is_exe: bool = true, + /// Run only on this OS. + os_filter: ?std.Target.Os.Tag = null, }; pub const BuildCase = struct { @@ -50,7 +52,7 @@ pub const simple_cases = [_]SimpleCase{ .{ .src_path = "test/standalone/issue_9402/main.zig", - .target = .{ .os_tag = .windows }, + .os_filter = .windows, .link_libc = true, }, diff --git a/test/tests.zig b/test/tests.zig index c07b669376..9ba9639e2a 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -571,6 +571,9 @@ pub fn addStandaloneTests( for (standalone.simple_cases) |case| { for (optimize_modes) |optimize| { if (!case.all_modes and optimize != .Debug) continue; + if (case.os_filter) |os_tag| { + if (os_tag != builtin.os.tag) continue; + } if (case.is_exe) { const exe = b.addExecutable(.{ From 1a3c1fe820ad9eeaa7b30a1fc6e1b8b8e2b871b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 10 Mar 2023 14:46:35 -0700 Subject: [PATCH 247/294] test-link: add names to headerpad test --- test/link/macho/headerpad/build.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/link/macho/headerpad/build.zig b/test/link/macho/headerpad/build.zig index 97161e8e60..0c9275b8d8 100644 --- a/test/link/macho/headerpad/build.zig +++ b/test/link/macho/headerpad/build.zig @@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void { fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { { // Test -headerpad_max_install_names - const exe = simpleExe(b, optimize); + const exe = simpleExe(b, optimize, "headerpad_max_install_names"); exe.headerpad_max_install_names = true; const check = exe.checkObject(); @@ -42,7 +42,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize { // Test -headerpad - const exe = simpleExe(b, optimize); + const exe = simpleExe(b, optimize, "headerpad"); exe.headerpad_size = 0x10000; const check = exe.checkObject(); @@ -58,7 +58,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize { // Test both flags with -headerpad overriding -headerpad_max_install_names - const exe = simpleExe(b, optimize); + const exe = simpleExe(b, optimize, "headerpad_overriding"); exe.headerpad_max_install_names = true; exe.headerpad_size = 0x10000; @@ -75,7 +75,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize { // Test both flags with -headerpad_max_install_names overriding -headerpad - const exe = simpleExe(b, optimize); + const exe = simpleExe(b, optimize, "headerpad_max_install_names_overriding"); exe.headerpad_size = 0x1000; exe.headerpad_max_install_names = true; @@ -100,9 +100,13 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize } } -fn simpleExe(b: *std.Build, optimize: std.builtin.OptimizeMode) *std.Build.CompileStep { +fn simpleExe( + b: *std.Build, + optimize: std.builtin.OptimizeMode, + name: []const u8, +) *std.Build.CompileStep { const exe = b.addExecutable(.{ - .name = "main", + .name = name, .optimize = optimize, }); exe.addCSourceFile("main.c", &.{}); From fbce6a749da9e3714d5e0fb662a3849b34cc9f89 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Mar 2023 00:24:19 -0700 Subject: [PATCH 248/294] disable failing aarch64 backend behavior tests --- test/behavior/array.zig | 1 + test/behavior/ptrcast.zig | 2 ++ test/behavior/slice.zig | 1 + 3 files changed, 4 insertions(+) diff --git a/test/behavior/array.zig b/test/behavior/array.zig index 6c68d50fda..c78bf4ab85 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -84,6 +84,7 @@ test "array concat with tuple" { } test "array init with concat" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const a = 'a'; var i: [4]u8 = [2]u8{ a, 'b' } ++ [2]u8{ 'c', 'd' }; try expect(std.mem.eql(u8, &i, "abcd")); diff --git a/test/behavior/ptrcast.zig b/test/behavior/ptrcast.zig index 599d13be1d..845ea3751e 100644 --- a/test/behavior/ptrcast.zig +++ b/test/behavior/ptrcast.zig @@ -170,6 +170,7 @@ test "lower reinterpreted comptime field ptr" { test "reinterpret struct field at comptime" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const numNative = comptime Bytes.init(0x12345678); if (native_endian != .Little) { @@ -232,6 +233,7 @@ test "ptrcast of const integer has the correct object size" { test "implicit optional pointer to optional anyopaque pointer" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO var buf: [4]u8 = "aoeu".*; var x: ?[*]u8 = &buf; diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index d749697ec5..2a0944a5b6 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -227,6 +227,7 @@ fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 { test "C pointer" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf"; var len: u32 = 10; From 53fb59ea9b148a9d1f694039898eb60a1df75535 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Mar 2023 00:25:00 -0700 Subject: [PATCH 249/294] std.fifo: make toOwnedSlice support head != 0 --- lib/std/fifo.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index ad929cde8a..5a72b56269 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -385,6 +385,7 @@ pub fn LinearFifo( } pub fn toOwnedSlice(self: *Self) Allocator.Error![]T { + if (self.head != 0) self.realign(); assert(self.head == 0); assert(self.count <= self.buf.len); const allocator = self.allocator; From 98299e7787146e1572cd2038654dbbcf84fa32d1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Mar 2023 00:34:11 -0700 Subject: [PATCH 250/294] add std.process.cleanExit --- lib/std/process.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/std/process.zig b/lib/std/process.zig index 8652dd1bbc..d9bf09ee2a 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1204,3 +1204,16 @@ fn totalSystemMemoryLinux() !usize { const kilobytes = try std.fmt.parseInt(usize, int_text, 10); return kilobytes * 1024; } + +/// Indicate that we are now terminating with a successful exit code. +/// In debug builds, this is a no-op, so that the calling code's +/// cleanup mechanisms are tested and so that external tools that +/// check for resource leaks can be accurate. In release builds, this +/// calls exit(0), and does not return. +pub fn cleanExit() void { + if (builtin.mode == .Debug) { + return; + } else { + exit(0); + } +} From ef5f8bd7c62f929b5cc210caa816ce4a8c8f8538 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Mar 2023 00:38:01 -0700 Subject: [PATCH 251/294] getExternalExecutor: fix aarch64 windows std.zig.system.NativeTargetInfo.getExternalExecutor previously would incorrectly communicate that wine could be used to run aarch64-windows binaries on x86_64-linux, and x86_64-windows binaries on aarch64-linux. Neither of these things are true. --- lib/std/zig/system/NativeTargetInfo.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig index dbbebb43c9..987358ed5a 100644 --- a/lib/std/zig/system/NativeTargetInfo.zig +++ b/lib/std/zig/system/NativeTargetInfo.zig @@ -1090,6 +1090,11 @@ pub fn getExternalExecutor( switch (candidate.target.os.tag) { .windows => { if (options.allow_wine) { + // x86_64 wine does not support emulating aarch64-windows and + // vice versa. + if (candidate.target.cpu.arch != builtin.cpu.arch) { + return bad_result; + } switch (candidate.target.cpu.arch.ptrBitWidth()) { 32 => return Executor{ .wine = "wine" }, 64 => return Executor{ .wine = "wine64" }, From ede5dcffea5a3a5fc9fd14e4e180464633402fae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Mar 2023 00:39:21 -0700 Subject: [PATCH 252/294] make the build runner and test runner talk to each other std.Build.addTest creates a CompileStep as before, however, this kind of step no longer actually runs the unit tests. Instead it only compiles it, and one must additionally create a RunStep from the CompileStep in order to actually run the tests. RunStep gains integration with the default test runner, which now supports the standard --listen=- argument in order to communicate over stdin and stdout. It also reports test statistics; how many passed, failed, and leaked, as well as directly associating the relevant stderr with the particular test name that failed. This separation of CompileStep and RunStep means that `CompileStep.Kind.test_exe` is no longer needed, and therefore has been removed in this commit. * build runner: show unit test statistics in build summary * added Step.writeManifest since many steps want to treat it as a warning and emit the same message if it fails. * RunStep: fixed error message that prints the failed command printing the original argv and not the adjusted argv in case an interpreter was used. * RunStep: fixed not passing the command line arguments to the interpreter. * move src/Server.zig to std.zig.Server so that the default test runner can use it. * the simpler test runner function which is used by work-in-progress backends now no longer prints to stderr, which is necessary in order for the build runner to not print the stderr as a warning message. --- CMakeLists.txt | 2 +- lib/build_runner.zig | 59 ++- lib/std/Build.zig | 10 +- lib/std/Build/CompileStep.zig | 93 +---- lib/std/Build/InstallArtifactStep.zig | 5 +- lib/std/Build/RunStep.zig | 355 +++++++++++++++--- lib/std/Build/Step.zig | 33 +- lib/std/Build/WriteFileStep.zig | 2 +- lib/std/zig/Client.zig | 7 + lib/std/zig/Server.zig | 200 ++++++++++ lib/test_runner.zig | 172 ++++++--- src/Server.zig | 113 ------ src/main.zig | 21 +- src/objcopy.zig | 9 +- test/link/common_symbols/build.zig | 2 +- test/link/common_symbols_alignment/build.zig | 2 +- .../interdependent_static_c_libs/build.zig | 2 +- test/link/macho/tls/build.zig | 5 +- test/src/Cases.zig | 11 +- test/standalone/emit_asm_and_bin/build.zig | 2 +- test/standalone/global_linkage/build.zig | 2 +- test/standalone/issue_13970/build.zig | 6 +- test/standalone/main_pkg_path/build.zig | 2 +- test/standalone/options/build.zig | 2 +- test/standalone/pie/build.zig | 2 +- test/standalone/static_c_lib/build.zig | 2 +- .../test_runner_module_imports/build.zig | 2 +- test/standalone/test_runner_path/build.zig | 1 - test/standalone/use_alias/build.zig | 2 +- test/tests.zig | 27 +- 30 files changed, 780 insertions(+), 373 deletions(-) delete mode 100644 src/Server.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ff8249f4d..b0867c220b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,6 +518,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig" + "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig" @@ -623,7 +624,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/print_targets.zig" "${CMAKE_SOURCE_DIR}/src/print_zir.zig" "${CMAKE_SOURCE_DIR}/src/register_manager.zig" - "${CMAKE_SOURCE_DIR}/src/Server.zig" "${CMAKE_SOURCE_DIR}/src/target.zig" "${CMAKE_SOURCE_DIR}/src/tracy.zig" "${CMAKE_SOURCE_DIR}/src/translate_c.zig" diff --git a/lib/build_runner.zig b/lib/build_runner.zig index e28be8274d..bb341574a7 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -416,6 +416,12 @@ fn runStepNames( } assert(run.memory_blocked_steps.items.len == 0); + var test_skip_count: usize = 0; + var test_fail_count: usize = 0; + var test_pass_count: usize = 0; + var test_leak_count: usize = 0; + var test_count: usize = 0; + var success_count: usize = 0; var skipped_count: usize = 0; var failure_count: usize = 0; @@ -425,6 +431,12 @@ fn runStepNames( defer compile_error_steps.deinit(gpa); for (step_stack.keys()) |s| { + test_fail_count += s.test_results.fail_count; + test_skip_count += s.test_results.skip_count; + test_leak_count += s.test_results.leak_count; + test_pass_count += s.test_results.passCount(); + test_count += s.test_results.test_count; + switch (s.state) { .precheck_unstarted => unreachable, .precheck_started => unreachable, @@ -468,6 +480,11 @@ fn runStepNames( if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {}; if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {}; + if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; + if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {}; + if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {}; + if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {}; + if (run.enable_summary == null) { ttyconf.setColor(stderr, .Dim) catch {}; stderr.writeAll(" (disable with -fno-summary)") catch {}; @@ -566,6 +583,13 @@ fn printTreeStep( try ttyconf.setColor(stderr, .Green); if (s.result_cached) { try stderr.writeAll(" cached"); + } else if (s.test_results.test_count > 0) { + const pass_count = s.test_results.passCount(); + try stderr.writer().print(" {d} passed", .{pass_count}); + if (s.test_results.skip_count > 0) { + try ttyconf.setColor(stderr, .Yellow); + try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count}); + } } else { try stderr.writeAll(" success"); } @@ -609,15 +633,46 @@ fn printTreeStep( }, .failure => { - try ttyconf.setColor(stderr, .Red); if (s.result_error_bundle.errorMessageCount() > 0) { + try ttyconf.setColor(stderr, .Red); try stderr.writer().print(" {d} errors\n", .{ s.result_error_bundle.errorMessageCount(), }); + try ttyconf.setColor(stderr, .Reset); + } else if (!s.test_results.isSuccess()) { + try stderr.writer().print(" {d}/{d} passed", .{ + s.test_results.passCount(), s.test_results.test_count, + }); + if (s.test_results.fail_count > 0) { + try stderr.writeAll(", "); + try ttyconf.setColor(stderr, .Red); + try stderr.writer().print("{d} failed", .{ + s.test_results.fail_count, + }); + try ttyconf.setColor(stderr, .Reset); + } + if (s.test_results.skip_count > 0) { + try stderr.writeAll(", "); + try ttyconf.setColor(stderr, .Yellow); + try stderr.writer().print("{d} skipped", .{ + s.test_results.skip_count, + }); + try ttyconf.setColor(stderr, .Reset); + } + if (s.test_results.leak_count > 0) { + try stderr.writeAll(", "); + try ttyconf.setColor(stderr, .Red); + try stderr.writer().print("{d} leaked", .{ + s.test_results.leak_count, + }); + try ttyconf.setColor(stderr, .Reset); + } + try stderr.writeAll("\n"); } else { + try ttyconf.setColor(stderr, .Red); try stderr.writeAll(" failure\n"); + try ttyconf.setColor(stderr, .Reset); } - try ttyconf.setColor(stderr, .Reset); }, } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 5f74af3db6..279dd765b5 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -531,7 +531,6 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep { pub const TestOptions = struct { name: []const u8 = "test", - kind: CompileStep.Kind = .@"test", root_source_file: FileSource, target: CrossTarget = .{}, optimize: std.builtin.Mode = .Debug, @@ -542,7 +541,7 @@ pub const TestOptions = struct { pub fn addTest(b: *Build, options: TestOptions) *CompileStep { return CompileStep.create(b, .{ .name = options.name, - .kind = options.kind, + .kind = .@"test", .root_source_file = options.root_source_file, .target = options.target, .optimize = options.optimize, @@ -626,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep { /// Creates a `RunStep` with an executable built with `addExecutable`. /// Add command line arguments with methods of `RunStep`. pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep { - assert(exe.kind == .exe or exe.kind == .test_exe); - // It doesn't have to be native. We catch that if you actually try to run it. // Consider that this is declarative; the run step may not be run unless a user // option is supplied. const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name})); run_step.addArtifactArg(exe); - if (exe.kind == .test_exe) { - run_step.addArg(b.zig_exe); + if (exe.kind == .@"test") { + run_step.stdio = .zig_test; + run_step.addArgs(&.{"--listen=-"}); } if (exe.vcpkg_bin_path) |path| { diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 5c44ab82d3..5753641966 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -289,7 +289,6 @@ pub const Kind = enum { lib, obj, @"test", - test_exe, }; pub const Linkage = enum { dynamic, static }; @@ -328,7 +327,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .exe => "zig build-exe", .lib => "zig build-lib", .obj => "zig build-obj", - .test_exe, .@"test" => "zig test", + .@"test" => "zig test", }, name_adjusted, @tagName(options.optimize), @@ -410,7 +409,7 @@ fn computeOutFileNames(self: *CompileStep) void { .output_mode = switch (self.kind) { .lib => .Lib, .obj => .Obj, - .exe, .@"test", .test_exe => .Exe, + .exe, .@"test" => .Exe, }, .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) { .dynamic => .Dynamic, @@ -621,7 +620,7 @@ pub fn producesPdbFile(self: *CompileStep) bool { if (!self.target.isWindows() and !self.target.isUefi()) return false; if (self.target.getObjectFormat() == .c) return false; if (self.strip == true) return false; - return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe; + return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test"; } pub fn linkLibC(self: *CompileStep) void { @@ -850,19 +849,19 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { pub fn setNamePrefix(self: *CompileStep, text: []const u8) void { const b = self.step.owner; - assert(self.kind == .@"test" or self.kind == .test_exe); + assert(self.kind == .@"test"); self.name_prefix = b.dupe(text); } pub fn setFilter(self: *CompileStep, text: ?[]const u8) void { const b = self.step.owner; - assert(self.kind == .@"test" or self.kind == .test_exe); + assert(self.kind == .@"test"); self.filter = if (text) |t| b.dupe(t) else null; } pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void { const b = self.step.owner; - assert(self.kind == .@"test" or self.kind == .test_exe); + assert(self.kind == .@"test"); self.test_runner = if (path) |p| b.dupePath(p) else null; } @@ -938,7 +937,7 @@ pub fn getOutputLibSource(self: *CompileStep) FileSource { /// Returns the generated header file. /// This function can only be called for libraries or object files which have `emit_h` set. pub fn getOutputHSource(self: *CompileStep) FileSource { - assert(self.kind != .exe and self.kind != .test_exe and self.kind != .@"test"); + assert(self.kind != .exe and self.kind != .@"test"); assert(self.emit_h); return .{ .generated = &self.output_h_path_source }; } @@ -1243,7 +1242,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .exe => "build-exe", .obj => "build-obj", .@"test" => "test", - .test_exe => "test", }; try zig_args.append(cmd); @@ -1293,7 +1291,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { .other_step => |other| switch (other.kind) { .exe => @panic("Cannot link with an executable build artifact"), - .test_exe => @panic("Cannot link with an executable build artifact"), .@"test" => @panic("Cannot link with a test"), .obj => { try zig_args.append(other.getOutputSource().getPath(b)); @@ -1661,83 +1658,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append("--test-cmd-bin"); } } - } else { - const need_cross_glibc = self.target.isGnuLibC() and transitive_deps.is_linking_libc; - - switch (b.host.getExternalExecutor(self.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, - .link_libc = transitive_deps.is_linking_libc, - })) { - .native => {}, - .bad_dl, .bad_os_or_cpu => { - try zig_args.append("--test-no-exec"); - }, - .rosetta => if (b.enable_rosetta) { - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - .qemu => |bin_name| ok: { - if (b.enable_qemu) qemu: { - const glibc_dir_arg = if (need_cross_glibc) - b.glibc_runtimes_dir orelse break :qemu - else - null; - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - if (glibc_dir_arg) |dir| { - // TODO look into making this a call to `linuxTriple`. This - // needs the directory to be called "i686" rather than - // "x86" which is why we do it manually here. - const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; - const cpu_arch = self.target.getCpuArch(); - const os_tag = self.target.getOsTag(); - const abi = self.target.getAbi(); - const cpu_arch_name: []const u8 = if (cpu_arch == .x86) - "i686" - else - @tagName(cpu_arch); - const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ - dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), - }); - - try zig_args.append("--test-cmd"); - try zig_args.append("-L"); - try zig_args.append("--test-cmd"); - try zig_args.append(full_dir); - } - try zig_args.append("--test-cmd-bin"); - break :ok; - } - try zig_args.append("--test-no-exec"); - }, - .wine => |bin_name| if (b.enable_wine) { - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - .wasmtime => |bin_name| if (b.enable_wasmtime) { - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - try zig_args.append("--test-cmd"); - try zig_args.append("--dir=."); - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - .darling => |bin_name| if (b.enable_darling) { - try zig_args.append("--test-cmd"); - try zig_args.append(bin_name); - try zig_args.append("--test-cmd-bin"); - } else { - try zig_args.append("--test-no-exec"); - }, - } } - } else if (self.kind == .test_exe) { - try zig_args.append("--test-no-exec"); } try self.appendModuleArgs(&zig_args); diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig index 803998a619..445f1e8ea8 100644 --- a/lib/std/Build/InstallArtifactStep.zig +++ b/lib/std/Build/InstallArtifactStep.zig @@ -32,12 +32,11 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { .artifact = artifact, .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { .obj => @panic("Cannot install a .obj build artifact."), - .@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."), - .exe, .test_exe => InstallDir{ .bin = {} }, + .exe, .@"test" => InstallDir{ .bin = {} }, .lib => InstallDir{ .lib = {} }, }, .pdb_dir = if (artifact.producesPdbFile()) blk: { - if (artifact.kind == .exe or artifact.kind == .test_exe) { + if (artifact.kind == .exe or artifact.kind == .@"test") { break :blk InstallDir{ .bin = {} }; } else { break :blk InstallDir{ .lib = {} }; diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 9a1c887d7d..484600bf9b 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -92,6 +92,9 @@ pub const StdIo = union(enum) { /// Note that an explicit check for exit code 0 needs to be added to this /// list if such a check is desireable. check: std.ArrayList(Check), + /// This RunStep is running a zig unit test binary and will communicate + /// extra metadata over the IPC protocol. + zig_test, pub const Check = union(enum) { expect_stderr_exact: []const u8, @@ -324,6 +327,7 @@ fn hasSideEffects(self: RunStep) bool { .infer_from_args => !self.hasAnyOutputArgs(), .inherit => true, .check => false, + .zig_test => false, }; } @@ -366,11 +370,6 @@ fn checksContainStderr(checks: []const StdIo.Check) bool { } fn make(step: *Step, prog_node: *std.Progress.Node) !void { - // Unfortunately we have no way to collect progress from arbitrary programs. - // Perhaps in the future Zig could offer some kind of opt-in IPC mechanism that - // processes could use to supply progress updates. - _ = prog_node; - const b = step.owner; const arena = b.allocator; const self = @fieldParentPtr(RunStep, "step", step); @@ -439,7 +438,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { hashStdIo(&man.hash, self.stdio); if (has_side_effects) { - try runCommand(self, argv_list.items, has_side_effects, null); + try runCommand(self, argv_list.items, has_side_effects, null, prog_node); return; } @@ -492,8 +491,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { argv_list.items[placeholder.index] = cli_arg; } - try runCommand(self, argv_list.items, has_side_effects, &digest); - try man.writeManifest(); + try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node); + + try step.writeManifest(&man); } fn formatTerm( @@ -546,6 +546,7 @@ fn runCommand( argv: []const []const u8, has_side_effects: bool, digest: ?*const [std.Build.Cache.hex_digest_len]u8, + prog_node: *std.Progress.Node, ) !void { const step = &self.step; const b = step.owner; @@ -554,7 +555,15 @@ fn runCommand( try step.handleChildProcUnsupported(self.cwd, argv); try Step.handleVerbose(step.owner, self.cwd, argv); - const result = spawnChildAndCollect(self, argv, has_side_effects) catch |err| term: { + const allow_skip = switch (self.stdio) { + .check, .zig_test => self.skip_foreign_checks, + else => false, + }; + + var interp_argv = std.ArrayList([]const u8).init(b.allocator); + defer interp_argv.deinit(); + + const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: { // InvalidExe: cpu arch mismatch // FileNotFound: can happen with a wrong dynamic linker path if (err == error.InvalidExe or err == error.FileNotFound) interpret: { @@ -566,10 +575,10 @@ fn runCommand( .artifact => |exe| exe, else => break :interpret, }; - if (exe.kind != .exe) break :interpret; - - var interp_argv = std.ArrayList([]const u8).init(b.allocator); - defer interp_argv.deinit(); + switch (exe.kind) { + .exe, .@"test" => {}, + else => break :interpret, + } const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; switch (b.host.getExternalExecutor(exe.target_info, .{ @@ -577,14 +586,13 @@ fn runCommand( .link_libc = exe.is_linking_libc, })) { .native, .rosetta => { - if (self.stdio == .check and self.skip_foreign_checks) - return error.MakeSkipped; - + if (allow_skip) return error.MakeSkipped; break :interpret; }, .wine => |bin_name| { if (b.enable_wine) { try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); } else { return failForeign(self, "-fwine", argv[0], exe); } @@ -617,6 +625,8 @@ fn runCommand( try interp_argv.append("-L"); try interp_argv.append(full_dir); } + + try interp_argv.appendSlice(argv); } else { return failForeign(self, "-fqemu", argv[0], exe); } @@ -624,6 +634,7 @@ fn runCommand( .darling => |bin_name| { if (b.enable_darling) { try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); } else { return failForeign(self, "-fdarling", argv[0], exe); } @@ -632,13 +643,15 @@ fn runCommand( if (b.enable_wasmtime) { try interp_argv.append(bin_name); try interp_argv.append("--dir=."); + try interp_argv.append(argv[0]); + try interp_argv.append("--"); + try interp_argv.appendSlice(argv[1..]); } else { return failForeign(self, "-fwasmtime", argv[0], exe); } }, .bad_dl => |foreign_dl| { - if (self.stdio == .check and self.skip_foreign_checks) - return error.MakeSkipped; + if (allow_skip) return error.MakeSkipped; const host_dl = b.host.dynamic_linker.get() orelse "(none)"; @@ -650,8 +663,7 @@ fn runCommand( , .{ host_dl, foreign_dl }); }, .bad_os_or_cpu => { - if (self.stdio == .check and self.skip_foreign_checks) - return error.MakeSkipped; + if (allow_skip) return error.MakeSkipped; const host_name = try b.host.target.zigTriple(b.allocator); const foreign_name = try exe.target.zigTriple(b.allocator); @@ -667,11 +679,9 @@ fn runCommand( RunStep.addPathForDynLibsInternal(&self.step, b, exe); } - try interp_argv.append(argv[0]); - try Step.handleVerbose(step.owner, self.cwd, interp_argv.items); - break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects) catch |e| { + break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { return step.fail("unable to spawn {s}: {s}", .{ interp_argv.items[0], @errorName(e), }); @@ -683,6 +693,7 @@ fn runCommand( step.result_duration_ns = result.elapsed_ns; step.result_peak_rss = result.peak_rss; + step.test_results = result.stdio.test_results; // Capture stdout and stderr to GeneratedFile objects. const Stream = struct { @@ -693,13 +704,13 @@ fn runCommand( for ([_]Stream{ .{ .captured = self.captured_stdout, - .is_null = result.stdout_null, - .bytes = result.stdout, + .is_null = result.stdio.stdout_null, + .bytes = result.stdio.stdout, }, .{ .captured = self.captured_stderr, - .is_null = result.stderr_null, - .bytes = result.stderr, + .is_null = result.stdio.stderr_null, + .bytes = result.stdio.stderr, }, }) |stream| { if (stream.captured) |output| { @@ -724,11 +735,13 @@ fn runCommand( } } + const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; + switch (self.stdio) { .check => |checks| for (checks.items) |check| switch (check) { .expect_stderr_exact => |expected_bytes| { - assert(!result.stderr_null); - if (!mem.eql(u8, expected_bytes, result.stderr)) { + assert(!result.stdio.stderr_null); + if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) { return step.fail( \\ \\========= expected this stderr: ========= @@ -739,14 +752,14 @@ fn runCommand( \\{s} , .{ expected_bytes, - result.stderr, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stderr, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, .expect_stderr_match => |match| { - assert(!result.stderr_null); - if (mem.indexOf(u8, result.stderr, match) == null) { + assert(!result.stdio.stderr_null); + if (mem.indexOf(u8, result.stdio.stderr, match) == null) { return step.fail( \\ \\========= expected to find in stderr: ========= @@ -757,14 +770,14 @@ fn runCommand( \\{s} , .{ match, - result.stderr, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stderr, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, .expect_stdout_exact => |expected_bytes| { - assert(!result.stdout_null); - if (!mem.eql(u8, expected_bytes, result.stdout)) { + assert(!result.stdio.stdout_null); + if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) { return step.fail( \\ \\========= expected this stdout: ========= @@ -775,14 +788,14 @@ fn runCommand( \\{s} , .{ expected_bytes, - result.stdout, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stdout, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, .expect_stdout_match => |match| { - assert(!result.stdout_null); - if (mem.indexOf(u8, result.stdout, match) == null) { + assert(!result.stdio.stdout_null); + if (mem.indexOf(u8, result.stdio.stdout, match) == null) { return step.fail( \\ \\========= expected to find in stdout: ========= @@ -793,8 +806,8 @@ fn runCommand( \\{s} , .{ match, - result.stdout, - try Step.allocPrintCmd(arena, self.cwd, argv), + result.stdio.stdout, + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, @@ -803,33 +816,46 @@ fn runCommand( return step.fail("the following command {} (expected {}):\n{s}", .{ fmtTerm(result.term), fmtTerm(expected_term), - try Step.allocPrintCmd(arena, self.cwd, argv), + try Step.allocPrintCmd(arena, self.cwd, final_argv), }); } }, }, + .zig_test => { + const expected_term: std.process.Child.Term = .{ .Exited = 0 }; + if (!termMatches(expected_term, result.term)) { + return step.fail("the following command {} (expected {}):\n{s}", .{ + fmtTerm(result.term), + fmtTerm(expected_term), + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + if (!result.stdio.test_results.isSuccess()) { + return step.fail( + "the following test command failed:\n{s}", + .{try Step.allocPrintCmd(arena, self.cwd, final_argv)}, + ); + } + }, else => { - try step.handleChildProcessTerm(result.term, self.cwd, argv); + try step.handleChildProcessTerm(result.term, self.cwd, final_argv); }, } } const ChildProcResult = struct { - // These use boolean flags instead of optionals as a workaround for - // https://github.com/ziglang/zig/issues/14783 - stdout: []const u8, - stderr: []const u8, - stdout_null: bool, - stderr_null: bool, term: std.process.Child.Term, elapsed_ns: u64, peak_rss: usize, + + stdio: StdIoResult, }; fn spawnChildAndCollect( self: *RunStep, argv: []const []const u8, has_side_effects: bool, + prog_node: *std.Progress.Node, ) !ChildProcResult { const b = self.step.owner; const arena = b.allocator; @@ -848,16 +874,19 @@ fn spawnChildAndCollect( .infer_from_args => if (has_side_effects) .Inherit else .Close, .inherit => .Inherit, .check => .Close, + .zig_test => .Pipe, }; child.stdout_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Ignore, .inherit => .Inherit, .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, + .zig_test => .Pipe, }; child.stderr_behavior = switch (self.stdio) { .infer_from_args => if (has_side_effects) .Inherit else .Pipe, .inherit => .Inherit, .check => .Pipe, + .zig_test => .Pipe, }; if (self.captured_stdout != null) child.stdout_behavior = .Pipe; if (self.captured_stderr != null) child.stderr_behavior = .Pipe; @@ -871,6 +900,219 @@ fn spawnChildAndCollect( }); var timer = try std.time.Timer.start(); + const result = if (self.stdio == .zig_test) + evalZigTest(self, &child, prog_node) + else + evalGeneric(self, &child); + + const term = try child.wait(); + const elapsed_ns = timer.read(); + + return .{ + .stdio = try result, + .term = term, + .elapsed_ns = elapsed_ns, + .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, + }; +} + +const StdIoResult = struct { + // These use boolean flags instead of optionals as a workaround for + // https://github.com/ziglang/zig/issues/14783 + stdout: []const u8, + stderr: []const u8, + stdout_null: bool, + stderr_null: bool, + test_results: Step.TestResults, +}; + +fn evalZigTest( + self: *RunStep, + child: *std.process.Child, + prog_node: *std.Progress.Node, +) !StdIoResult { + const gpa = self.step.owner.allocator; + const arena = self.step.owner.allocator; + + var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ + .stdout = child.stdout.?, + .stderr = child.stderr.?, + }); + defer poller.deinit(); + + try sendMessage(child.stdin.?, .query_test_metadata); + + const Header = std.zig.Server.Message.Header; + + const stdout = poller.fifo(.stdout); + const stderr = poller.fifo(.stderr); + + var fail_count: u32 = 0; + var skip_count: u32 = 0; + var leak_count: u32 = 0; + var test_count: u32 = 0; + + var metadata: ?TestMetadata = null; + + var sub_prog_node: ?std.Progress.Node = null; + defer if (sub_prog_node) |*n| n.end(); + + poll: while (try poller.poll()) { + while (true) { + const buf = stdout.readableSlice(0); + assert(stdout.readableLength() == buf.len); + if (buf.len < @sizeOf(Header)) continue :poll; + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + const header_and_msg_len = header.bytes_len + @sizeOf(Header); + if (buf.len < header_and_msg_len) continue :poll; + const body = buf[@sizeOf(Header)..][0..header.bytes_len]; + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return self.step.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .test_metadata => { + const TmHdr = std.zig.Server.Message.TestMetadata; + const tm_hdr = @ptrCast(*align(1) const TmHdr, body); + test_count = tm_hdr.tests_len; + + const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; + const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; + + const names = std.mem.bytesAsSlice(u32, names_bytes); + const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); + const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); + const names_aligned = try arena.alloc(u32, names.len); + for (names_aligned, names) |*dest, src| dest.* = src; + + const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); + for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; + + const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); + for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; + + prog_node.setEstimatedTotalItems(names.len); + metadata = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .names = names_aligned, + .async_frame_lens = async_frame_lens_aligned, + .expected_panic_msgs = expected_panic_msgs_aligned, + .next_index = 0, + .prog_node = prog_node, + }; + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + .test_results => { + const md = metadata.?; + + const TrHdr = std.zig.Server.Message.TestResults; + const tr_hdr = @ptrCast(*align(1) const TrHdr, body); + fail_count += @boolToInt(tr_hdr.flags.fail); + skip_count += @boolToInt(tr_hdr.flags.skip); + leak_count += @boolToInt(tr_hdr.flags.leak); + + if (tr_hdr.flags.fail or tr_hdr.flags.leak) { + const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); + const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); + const label = if (tr_hdr.flags.fail) "failed" else "leaked"; + if (msg.len > 0) { + try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); + } else { + try self.step.addError("'{s}' {s}", .{ name, label }); + } + stderr.discard(msg.len); + } + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + else => {}, // ignore other messages + } + stdout.discard(header_and_msg_len); + } + } + + if (stderr.readableLength() > 0) { + const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n"); + if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg); + } + + // Send EOF to stdin. + child.stdin.?.close(); + child.stdin = null; + + return .{ + .stdout = &.{}, + .stderr = &.{}, + .stdout_null = true, + .stderr_null = true, + .test_results = .{ + .test_count = test_count, + .fail_count = fail_count, + .skip_count = skip_count, + .leak_count = leak_count, + }, + }; +} + +const TestMetadata = struct { + names: []const u32, + async_frame_lens: []const u32, + expected_panic_msgs: []const u32, + string_bytes: []const u8, + next_index: u32, + prog_node: *std.Progress.Node, + + fn testName(tm: TestMetadata, index: u32) []const u8 { + return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); + } +}; + +fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { + while (metadata.next_index < metadata.names.len) { + const i = metadata.next_index; + metadata.next_index += 1; + + if (metadata.async_frame_lens[i] != 0) continue; + if (metadata.expected_panic_msgs[i] != 0) continue; + + const name = metadata.testName(i); + if (sub_prog_node.*) |*n| n.end(); + sub_prog_node.* = metadata.prog_node.start(name, 0); + + try sendRunTestMessage(in, i); + return; + } else { + try sendMessage(in, .exit); + } +} + +fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(std.mem.asBytes(&header)); +} + +fn sendRunTestMessage(file: std.fs.File, index: u32) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = .run_test, + .bytes_len = 4, + }; + const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); + try file.writeAll(full_msg); +} + +fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { + const arena = self.step.owner.allocator; + if (self.stdin) |stdin| { child.stdin.?.writeAll(stdin) catch |err| { return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); @@ -925,17 +1167,12 @@ fn spawnChildAndCollect( } } - const term = try child.wait(); - const elapsed_ns = timer.read(); - return .{ .stdout = stdout_bytes, .stderr = stderr_bytes, .stdout_null = stdout_null, .stderr_null = stderr_null, - .term = term, - .elapsed_ns = elapsed_ns, - .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, + .test_results = .{}, }; } @@ -966,7 +1203,7 @@ fn failForeign( exe: *CompileStep, ) error{ MakeFailed, MakeSkipped, OutOfMemory } { switch (self.stdio) { - .check => { + .check, .zig_test => { if (self.skip_foreign_checks) return error.MakeSkipped; @@ -987,7 +1224,7 @@ fn failForeign( fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { switch (stdio) { - .infer_from_args, .inherit => {}, + .infer_from_args, .inherit, .zig_test => {}, .check => |checks| for (checks.items) |check| { hh.add(@as(std.meta.Tag(StdIo.Check), check)); switch (check) { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 45aa635972..05c4faa52d 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -35,11 +35,27 @@ result_cached: bool, result_duration_ns: ?u64, /// 0 means unavailable or not reported. result_peak_rss: usize, +test_results: TestResults, /// The return addresss associated with creation of this step that can be useful /// to print along with debugging messages. debug_stack_trace: [n_debug_stack_frames]usize, +pub const TestResults = struct { + fail_count: u32 = 0, + skip_count: u32 = 0, + leak_count: u32 = 0, + test_count: u32 = 0, + + pub fn isSuccess(tr: TestResults) bool { + return tr.fail_count == 0 and tr.leak_count == 0; + } + + pub fn passCount(tr: TestResults) u32 { + return tr.test_count - tr.fail_count - tr.skip_count; + } +}; + pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void; const n_debug_stack_frames = 4; @@ -134,6 +150,7 @@ pub fn init(options: Options) Step { .result_cached = false, .result_duration_ns = null, .result_peak_rss = 0, + .test_results = .{}, }; } @@ -152,6 +169,10 @@ pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkip }, }; + if (!s.test_results.isSuccess()) { + return error.MakeFailed; + } + if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) { const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{ s.result_peak_rss, s.max_rss, @@ -346,9 +367,7 @@ pub fn evalZigProcess( s.result_cached = ebp_hdr.flags.cache_hit; result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); }, - _ => { - // Unrecognized message. - }, + else => {}, // ignore other messages } stdout.discard(header_and_msg_len); } @@ -475,3 +494,11 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path }); } + +pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void { + if (s.test_results.isSuccess()) { + man.writeManifest() catch |err| { + try s.addError("unable to write cache manifest: {s}", .{@errorName(err)}); + }; + } +} diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig index 9b033e5ae2..dee79af5be 100644 --- a/lib/std/Build/WriteFileStep.zig +++ b/lib/std/Build/WriteFileStep.zig @@ -282,7 +282,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }); } - try man.writeManifest(); + try step.writeManifest(&man); } const std = @import("../std.zig"); diff --git a/lib/std/zig/Client.zig b/lib/std/zig/Client.zig index a68c189e57..af4c29d37d 100644 --- a/lib/std/zig/Client.zig +++ b/lib/std/zig/Client.zig @@ -26,6 +26,13 @@ pub const Message = struct { /// swap. /// No body. hot_update, + /// Ask the test runner for metadata about all the unit tests that can + /// be run. Server will respond with a `test_metadata` message. + /// No body. + query_test_metadata, + /// Ask the test runner to run a particular test. + /// The message body is a u32 test index. + run_test, _, }; diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index d34b2193e9..3238f22043 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -1,3 +1,7 @@ +in: std.fs.File, +out: std.fs.File, +receive_fifo: std.fifo.LinearFifo(u8, .Dynamic), + pub const Message = struct { pub const Header = extern struct { tag: Tag, @@ -14,6 +18,11 @@ pub const Message = struct { progress, /// Body is a EmitBinPath. emit_bin_path, + /// Body is a TestMetadata + test_metadata, + /// Body is a TestResults + test_results, + _, }; @@ -26,6 +35,33 @@ pub const Message = struct { string_bytes_len: u32, }; + /// Trailing: + /// * name: [tests_len]u32 + /// - null-terminated string_bytes index + /// * async_frame_len: [tests_len]u32, + /// - 0 means not async + /// * expected_panic_msg: [tests_len]u32, + /// - null-terminated string_bytes index + /// - 0 means does not expect pani + /// * string_bytes: [string_bytes_len]u8, + pub const TestMetadata = extern struct { + string_bytes_len: u32, + tests_len: u32, + }; + + pub const TestResults = extern struct { + index: u32, + flags: Flags, + + pub const Flags = packed struct(u8) { + fail: bool, + skip: bool, + leak: bool, + + reserved: u5 = 0, + }; + }; + /// Trailing: /// * the file system path the emitted binary can be found pub const EmitBinPath = extern struct { @@ -37,3 +73,167 @@ pub const Message = struct { }; }; }; + +pub const Options = struct { + gpa: Allocator, + in: std.fs.File, + out: std.fs.File, + zig_version: []const u8, +}; + +pub fn init(options: Options) !Server { + var s: Server = .{ + .in = options.in, + .out = options.out, + .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa), + }; + try s.serveStringMessage(.zig_version, options.zig_version); + return s; +} + +pub fn deinit(s: *Server) void { + s.receive_fifo.deinit(); + s.* = undefined; +} + +pub fn receiveMessage(s: *Server) !InMessage.Header { + const Header = InMessage.Header; + const fifo = &s.receive_fifo; + + while (true) { + const buf = fifo.readableSlice(0); + assert(fifo.readableLength() == buf.len); + if (buf.len >= @sizeOf(Header)) { + const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + + if (buf.len - @sizeOf(Header) >= header.bytes_len) { + const result = header.*; + fifo.discard(@sizeOf(Header)); + return result; + } else { + const needed = header.bytes_len - (buf.len - @sizeOf(Header)); + const write_buffer = try fifo.writableWithSize(needed); + const amt = try s.in.read(write_buffer); + fifo.update(amt); + continue; + } + } + + const write_buffer = try fifo.writableWithSize(256); + const amt = try s.in.read(write_buffer); + fifo.update(amt); + } +} + +pub fn receiveBody_u32(s: *Server) !u32 { + const fifo = &s.receive_fifo; + const buf = fifo.readableSlice(0); + const result = @ptrCast(*align(1) const u32, buf[0..4]).*; + fifo.discard(4); + return result; +} + +pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { + return s.serveMessage(.{ + .tag = tag, + .bytes_len = @intCast(u32, msg.len), + }, &.{msg}); +} + +pub fn serveMessage( + s: *const Server, + header: OutMessage.Header, + bufs: []const []const u8, +) !void { + var iovecs: [10]std.os.iovec_const = undefined; + iovecs[0] = .{ + .iov_base = @ptrCast([*]const u8, &header), + .iov_len = @sizeOf(OutMessage.Header), + }; + for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { + iovec.* = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + } + try s.out.writevAll(iovecs[0 .. bufs.len + 1]); +} + +pub fn serveEmitBinPath( + s: *Server, + fs_path: []const u8, + header: OutMessage.EmitBinPath, +) !void { + try s.serveMessage(.{ + .tag = .emit_bin_path, + .bytes_len = @intCast(u32, fs_path.len + @sizeOf(OutMessage.EmitBinPath)), + }, &.{ + std.mem.asBytes(&header), + fs_path, + }); +} + +pub fn serveTestResults( + s: *Server, + msg: OutMessage.TestResults, +) !void { + try s.serveMessage(.{ + .tag = .test_results, + .bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)), + }, &.{ + std.mem.asBytes(&msg), + }); +} + +pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { + const eb_hdr: OutMessage.ErrorBundle = .{ + .extra_len = @intCast(u32, error_bundle.extra.len), + .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), + }; + const bytes_len = @sizeOf(OutMessage.ErrorBundle) + + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; + try s.serveMessage(.{ + .tag = .error_bundle, + .bytes_len = @intCast(u32, bytes_len), + }, &.{ + std.mem.asBytes(&eb_hdr), + // TODO: implement @ptrCast between slices changing the length + std.mem.sliceAsBytes(error_bundle.extra), + error_bundle.string_bytes, + }); +} + +pub const TestMetadata = struct { + names: []const u32, + async_frame_sizes: []const u32, + expected_panic_msgs: []const u32, + string_bytes: []const u8, +}; + +pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { + const header: OutMessage.TestMetadata = .{ + .tests_len = @intCast(u32, test_metadata.names.len), + .string_bytes_len = @intCast(u32, test_metadata.string_bytes.len), + }; + const bytes_len = @sizeOf(OutMessage.TestMetadata) + + 3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len; + return s.serveMessage(.{ + .tag = .test_metadata, + .bytes_len = @intCast(u32, bytes_len), + }, &.{ + std.mem.asBytes(&header), + // TODO: implement @ptrCast between slices changing the length + std.mem.sliceAsBytes(test_metadata.names), + std.mem.sliceAsBytes(test_metadata.async_frame_sizes), + std.mem.sliceAsBytes(test_metadata.expected_panic_msgs), + test_metadata.string_bytes, + }); +} + +const OutMessage = std.zig.Server.Message; +const InMessage = std.zig.Client.Message; + +const Server = @This(); +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; diff --git a/lib/test_runner.zig b/lib/test_runner.zig index 5968fdaa54..47ababbc2c 100644 --- a/lib/test_runner.zig +++ b/lib/test_runner.zig @@ -8,14 +8,130 @@ pub const std_options = struct { }; var log_err_count: usize = 0; +var cmdline_buffer: [4096]u8 = undefined; +var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer); pub fn main() void { - if (builtin.zig_backend != .stage1 and - builtin.zig_backend != .stage2_llvm and - builtin.zig_backend != .stage2_c) + if (builtin.zig_backend == .stage2_wasm or + builtin.zig_backend == .stage2_x86_64 or + builtin.zig_backend == .stage2_aarch64) { - return main2() catch @panic("test failure"); + return mainSimple() catch @panic("test failure"); } + + const args = std.process.argsAlloc(fba.allocator()) catch + @panic("unable to parse command line args"); + + var listen = false; + + for (args[1..]) |arg| { + if (std.mem.eql(u8, arg, "--listen=-")) { + listen = true; + } else { + @panic("unrecognized command line argument"); + } + } + + if (listen) { + return mainServer(); + } else { + return mainTerminal(); + } +} + +fn mainServer() void { + return mainServerFallible() catch @panic("internal test runner failure"); +} + +fn mainServerFallible() !void { + var server = try std.zig.Server.init(.{ + .gpa = fba.allocator(), + .in = std.io.getStdIn(), + .out = std.io.getStdOut(), + .zig_version = builtin.zig_version_string, + }); + defer server.deinit(); + + while (true) { + const hdr = try server.receiveMessage(); + switch (hdr.tag) { + .exit => { + return std.process.exit(0); + }, + .query_test_metadata => { + std.testing.allocator_instance = .{}; + defer if (std.testing.allocator_instance.deinit()) { + @panic("internal test runner memory leak"); + }; + + var string_bytes: std.ArrayListUnmanaged(u8) = .{}; + defer string_bytes.deinit(std.testing.allocator); + try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null. + + const test_fns = builtin.test_functions; + const names = try std.testing.allocator.alloc(u32, test_fns.len); + defer std.testing.allocator.free(names); + const async_frame_sizes = try std.testing.allocator.alloc(u32, test_fns.len); + defer std.testing.allocator.free(async_frame_sizes); + const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len); + defer std.testing.allocator.free(expected_panic_msgs); + + for (test_fns, names, async_frame_sizes, expected_panic_msgs) |test_fn, *name, *async_frame_size, *expected_panic_msg| { + name.* = @intCast(u32, string_bytes.items.len); + try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1); + string_bytes.appendSliceAssumeCapacity(test_fn.name); + string_bytes.appendAssumeCapacity(0); + + async_frame_size.* = @intCast(u32, test_fn.async_frame_size orelse 0); + expected_panic_msg.* = 0; + } + + try server.serveTestMetadata(.{ + .names = names, + .async_frame_sizes = async_frame_sizes, + .expected_panic_msgs = expected_panic_msgs, + .string_bytes = string_bytes.items, + }); + }, + + .run_test => { + std.testing.allocator_instance = .{}; + const index = try server.receiveBody_u32(); + const test_fn = builtin.test_functions[index]; + if (test_fn.async_frame_size != null) + @panic("TODO test runner implement async tests"); + var fail = false; + var skip = false; + var leak = false; + test_fn.func() catch |err| switch (err) { + error.SkipZigTest => skip = true, + else => { + fail = true; + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + }, + }; + leak = std.testing.allocator_instance.deinit(); + try server.serveTestResults(.{ + .index = index, + .flags = .{ + .fail = fail, + .skip = skip, + .leak = leak, + }, + }); + }, + + else => { + std.debug.print("unsupported message: {x}", .{@enumToInt(hdr.tag)}); + std.process.exit(1); + }, + } + } +} + +fn mainTerminal() void { const test_fn_list = builtin.test_functions; var ok_count: usize = 0; var skip_count: usize = 0; @@ -118,51 +234,17 @@ pub fn log( } } -pub fn main2() anyerror!void { - var skipped: usize = 0; - var failed: usize = 0; - // Simpler main(), exercising fewer language features, so that stage2 can handle it. +/// Simpler main(), exercising fewer language features, so that +/// work-in-progress backends can handle it. +pub fn mainSimple() anyerror!void { + //const stderr = std.io.getStdErr(); for (builtin.test_functions) |test_fn| { test_fn.func() catch |err| { if (err != error.SkipZigTest) { - failed += 1; - } else { - skipped += 1; + //stderr.writeAll(test_fn.name) catch {}; + //stderr.writeAll("\n") catch {}; + return err; } }; } - if (builtin.zig_backend == .stage2_wasm or - builtin.zig_backend == .stage2_x86_64 or - builtin.zig_backend == .stage2_aarch64 or - builtin.zig_backend == .stage2_llvm or - builtin.zig_backend == .stage2_c) - { - const passed = builtin.test_functions.len - skipped - failed; - const stderr = std.io.getStdErr(); - writeInt(stderr, passed) catch {}; - stderr.writeAll(" passed; ") catch {}; - writeInt(stderr, skipped) catch {}; - stderr.writeAll(" skipped; ") catch {}; - writeInt(stderr, failed) catch {}; - stderr.writeAll(" failed.\n") catch {}; - } - if (failed != 0) { - return error.TestsFailed; - } -} - -fn writeInt(stderr: std.fs.File, int: usize) anyerror!void { - const base = 10; - var buf: [100]u8 = undefined; - var a: usize = int; - var index: usize = buf.len; - while (true) { - const digit = a % base; - index -= 1; - buf[index] = std.fmt.digitToChar(@intCast(u8, digit), .lower); - a /= base; - if (a == 0) break; - } - const slice = buf[index..]; - try stderr.writeAll(slice); } diff --git a/src/Server.zig b/src/Server.zig deleted file mode 100644 index a25dc93857..0000000000 --- a/src/Server.zig +++ /dev/null @@ -1,113 +0,0 @@ -in: std.fs.File, -out: std.fs.File, -receive_fifo: std.fifo.LinearFifo(u8, .Dynamic), - -pub const Options = struct { - gpa: Allocator, - in: std.fs.File, - out: std.fs.File, -}; - -pub fn init(options: Options) !Server { - var s: Server = .{ - .in = options.in, - .out = options.out, - .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa), - }; - try s.serveStringMessage(.zig_version, build_options.version); - return s; -} - -pub fn deinit(s: *Server) void { - s.receive_fifo.deinit(); - s.* = undefined; -} - -pub fn receiveMessage(s: *Server) !InMessage.Header { - const Header = InMessage.Header; - const fifo = &s.receive_fifo; - - while (true) { - const buf = fifo.readableSlice(0); - assert(fifo.readableLength() == buf.len); - if (buf.len >= @sizeOf(Header)) { - const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - if (header.bytes_len != 0) - return error.InvalidClientMessage; - const result = header.*; - fifo.discard(@sizeOf(Header)); - return result; - } - - const write_buffer = try fifo.writableWithSize(256); - const amt = try s.in.read(write_buffer); - fifo.update(amt); - } -} - -pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { - return s.serveMessage(.{ - .tag = tag, - .bytes_len = @intCast(u32, msg.len), - }, &.{msg}); -} - -pub fn serveMessage( - s: *const Server, - header: OutMessage.Header, - bufs: []const []const u8, -) !void { - var iovecs: [10]std.os.iovec_const = undefined; - iovecs[0] = .{ - .iov_base = @ptrCast([*]const u8, &header), - .iov_len = @sizeOf(OutMessage.Header), - }; - for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { - iovec.* = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - } - try s.out.writevAll(iovecs[0 .. bufs.len + 1]); -} - -pub fn serveEmitBinPath( - s: *Server, - fs_path: []const u8, - header: std.zig.Server.Message.EmitBinPath, -) !void { - try s.serveMessage(.{ - .tag = .emit_bin_path, - .bytes_len = @intCast(u32, fs_path.len + @sizeOf(std.zig.Server.Message.EmitBinPath)), - }, &.{ - std.mem.asBytes(&header), - fs_path, - }); -} - -pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { - const eb_hdr: std.zig.Server.Message.ErrorBundle = .{ - .extra_len = @intCast(u32, error_bundle.extra.len), - .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len), - }; - const bytes_len = @sizeOf(std.zig.Server.Message.ErrorBundle) + - 4 * error_bundle.extra.len + error_bundle.string_bytes.len; - try s.serveMessage(.{ - .tag = .error_bundle, - .bytes_len = @intCast(u32, bytes_len), - }, &.{ - std.mem.asBytes(&eb_hdr), - // TODO: implement @ptrCast between slices changing the length - std.mem.sliceAsBytes(error_bundle.extra), - error_bundle.string_bytes, - }); -} - -const OutMessage = std.zig.Server.Message; -const InMessage = std.zig.Client.Message; - -const Server = @This(); -const std = @import("std"); -const build_options = @import("build_options"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; diff --git a/src/main.zig b/src/main.zig index e0283143d0..9602a5cd31 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,6 +10,7 @@ const ArrayList = std.ArrayList; const Ast = std.zig.Ast; const warn = std.log.warn; const ThreadPool = std.Thread.Pool; +const cleanExit = std.process.cleanExit; const tracy = @import("tracy.zig"); const Compilation = @import("Compilation.zig"); @@ -26,7 +27,7 @@ const target_util = @import("target.zig"); const crash_report = @import("crash_report.zig"); const Module = @import("Module.zig"); const AstGen = @import("AstGen.zig"); -const Server = @import("Server.zig"); +const Server = std.zig.Server; pub const std_options = struct { pub const wasiCwd = wasi_cwd; @@ -3545,6 +3546,7 @@ fn serve( .gpa = gpa, .in = in, .out = out, + .zig_version = build_options.version, }); defer server.deinit(); @@ -3656,8 +3658,8 @@ fn serve( ); } }, - _ => { - @panic("TODO unrecognized message from client"); + else => { + fatal("unrecognized message from client: 0x{x}", .{@enumToInt(hdr.tag)}); }, } } @@ -5624,19 +5626,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat return std.zig.system.NativeTargetInfo.detect(cross_target); } -/// Indicate that we are now terminating with a successful exit code. -/// In debug builds, this is a no-op, so that the calling code's -/// cleanup mechanisms are tested and so that external tools that -/// check for resource leaks can be accurate. In release builds, this -/// calls exit(0), and does not return. -pub fn cleanExit() void { - if (builtin.mode == .Debug) { - return; - } else { - process.exit(0); - } -} - const usage_ast_check = \\Usage: zig ast-check [file] \\ diff --git a/src/objcopy.zig b/src/objcopy.zig index e821a94b59..c3305e8c04 100644 --- a/src/objcopy.zig +++ b/src/objcopy.zig @@ -8,8 +8,8 @@ const assert = std.debug.assert; const main = @import("main.zig"); const fatal = main.fatal; -const cleanExit = main.cleanExit; -const Server = @import("Server.zig"); +const Server = std.zig.Server; +const build_options = @import("build_options"); pub fn cmdObjCopy( gpa: Allocator, @@ -116,6 +116,7 @@ pub fn cmdObjCopy( .gpa = gpa, .in = std.io.getStdIn(), .out = std.io.getStdOut(), + .zig_version = build_options.version, }); defer server.deinit(); @@ -124,7 +125,7 @@ pub fn cmdObjCopy( const hdr = try server.receiveMessage(); switch (hdr.tag) { .exit => { - return cleanExit(); + return std.process.cleanExit(); }, .update => { if (seen_update) { @@ -144,7 +145,7 @@ pub fn cmdObjCopy( } } } - return cleanExit(); + return std.process.cleanExit(); } const usage = diff --git a/test/link/common_symbols/build.zig b/test/link/common_symbols/build.zig index e3c302f0f7..a8c276d1f3 100644 --- a/test/link/common_symbols/build.zig +++ b/test/link/common_symbols/build.zig @@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize }); test_exe.linkLibrary(lib_a); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/link/common_symbols_alignment/build.zig b/test/link/common_symbols_alignment/build.zig index 7d1d813447..83548f2d8a 100644 --- a/test/link/common_symbols_alignment/build.zig +++ b/test/link/common_symbols_alignment/build.zig @@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize }); test_exe.linkLibrary(lib_a); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/link/interdependent_static_c_libs/build.zig b/test/link/interdependent_static_c_libs/build.zig index c4118c1ca4..0d06410a79 100644 --- a/test/link/interdependent_static_c_libs/build.zig +++ b/test/link/interdependent_static_c_libs/build.zig @@ -35,5 +35,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize test_exe.linkLibrary(lib_b); test_exe.addIncludePath("."); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/link/macho/tls/build.zig b/test/link/macho/tls/build.zig index e91f40bd59..5981fea194 100644 --- a/test/link/macho/tls/build.zig +++ b/test/link/macho/tls/build.zig @@ -32,5 +32,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize test_exe.linkLibrary(lib); test_exe.linkLibC(); - test_step.dependOn(&test_exe.step); + const run = test_exe.run(); + run.skip_foreign_checks = true; + + test_step.dependOn(&run.step); } diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 5007939b14..6affe968d1 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -547,15 +547,12 @@ pub fn lowerToBuildSteps( parent_step.dependOn(&artifact.step); }, .Execution => |expected_stdout| { - if (case.is_test) { - parent_step.dependOn(&artifact.step); - } else { - const run = b.addRunArtifact(artifact); - run.skip_foreign_checks = true; + const run = b.addRunArtifact(artifact); + run.skip_foreign_checks = true; + if (!case.is_test) { run.expectStdOutEqual(expected_stdout); - - parent_step.dependOn(&run.step); } + parent_step.dependOn(&run.step); }, .Header => @panic("TODO"), } diff --git a/test/standalone/emit_asm_and_bin/build.zig b/test/standalone/emit_asm_and_bin/build.zig index 9bdfadc33d..594bf6552e 100644 --- a/test/standalone/emit_asm_and_bin/build.zig +++ b/test/standalone/emit_asm_and_bin/build.zig @@ -11,5 +11,5 @@ pub fn build(b: *std.Build) void { main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") }; main.emit_bin = .{ .emit_to = b.pathFromRoot("main") }; - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/global_linkage/build.zig b/test/standalone/global_linkage/build.zig index 2cf1b248a5..ddcddd612a 100644 --- a/test/standalone/global_linkage/build.zig +++ b/test/standalone/global_linkage/build.zig @@ -28,5 +28,5 @@ pub fn build(b: *std.Build) void { main.linkLibrary(obj1); main.linkLibrary(obj2); - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/issue_13970/build.zig b/test/standalone/issue_13970/build.zig index bbaaac5886..1eb8a5a121 100644 --- a/test/standalone/issue_13970/build.zig +++ b/test/standalone/issue_13970/build.zig @@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void { test2.setTestRunner("src/main.zig"); test3.setTestRunner("src/main.zig"); - test_step.dependOn(&test1.step); - test_step.dependOn(&test2.step); - test_step.dependOn(&test3.step); + test_step.dependOn(&test1.run().step); + test_step.dependOn(&test2.run().step); + test_step.dependOn(&test3.run().step); } diff --git a/test/standalone/main_pkg_path/build.zig b/test/standalone/main_pkg_path/build.zig index cd49573692..a4dd301c43 100644 --- a/test/standalone/main_pkg_path/build.zig +++ b/test/standalone/main_pkg_path/build.zig @@ -9,5 +9,5 @@ pub fn build(b: *std.Build) void { }); test_exe.setMainPkgPath("."); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/standalone/options/build.zig b/test/standalone/options/build.zig index 3f1e823359..5e894102a7 100644 --- a/test/standalone/options/build.zig +++ b/test/standalone/options/build.zig @@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void { options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?); const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/pie/build.zig b/test/standalone/pie/build.zig index 615111b6c2..546b4a922f 100644 --- a/test/standalone/pie/build.zig +++ b/test/standalone/pie/build.zig @@ -17,5 +17,5 @@ pub fn build(b: *std.Build) void { }); main.pie = true; - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig index 5996c978d8..794b813b75 100644 --- a/test/standalone/static_c_lib/build.zig +++ b/test/standalone/static_c_lib/build.zig @@ -21,5 +21,5 @@ pub fn build(b: *std.Build) void { test_exe.linkLibrary(foo); test_exe.addIncludePath("."); - test_step.dependOn(&test_exe.step); + test_step.dependOn(&test_exe.run().step); } diff --git a/test/standalone/test_runner_module_imports/build.zig b/test/standalone/test_runner_module_imports/build.zig index 973365e495..73c5536dc6 100644 --- a/test/standalone/test_runner_module_imports/build.zig +++ b/test/standalone/test_runner_module_imports/build.zig @@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void { t.addModule("module2", module2); const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&t.step); + test_step.dependOn(&t.run().step); } diff --git a/test/standalone/test_runner_path/build.zig b/test/standalone/test_runner_path/build.zig index 40aad42b21..ce5b668054 100644 --- a/test/standalone/test_runner_path/build.zig +++ b/test/standalone/test_runner_path/build.zig @@ -8,7 +8,6 @@ pub fn build(b: *std.Build) void { const test_exe = b.addTest(.{ .root_source_file = .{ .path = "test.zig" }, - .kind = .test_exe, }); test_exe.test_runner = "test_runner.zig"; diff --git a/test/standalone/use_alias/build.zig b/test/standalone/use_alias/build.zig index 947db0828d..db47fe6692 100644 --- a/test/standalone/use_alias/build.zig +++ b/test/standalone/use_alias/build.zig @@ -12,5 +12,5 @@ pub fn build(b: *std.Build) void { }); main.addIncludePath("."); - test_step.dependOn(&main.step); + test_step.dependOn(&main.run().step); } diff --git a/test/tests.zig b/test/tests.zig index 9ba9639e2a..76ea537bb2 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -596,7 +596,7 @@ pub fn addStandaloneTests( }); if (case.link_libc) exe.linkLibC(); - step.dependOn(&exe.step); + step.dependOn(&exe.run().step); } } } @@ -981,14 +981,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { }); const single_threaded_txt = if (test_target.single_threaded) "single" else "multi"; const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default"; - these_tests.setNamePrefix(b.fmt("{s}-{s}-{s}-{s}-{s}-{s} ", .{ - options.name, - triple_prefix, - @tagName(test_target.optimize_mode), - libc_prefix, - single_threaded_txt, - backend_txt, - })); these_tests.single_threaded = test_target.single_threaded; these_tests.setFilter(options.test_filter); if (test_target.link_libc) { @@ -1014,7 +1006,18 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { }, }; - step.dependOn(&these_tests.step); + const run = these_tests.run(); + run.skip_foreign_checks = true; + run.setName(b.fmt("run test {s}-{s}-{s}-{s}-{s}-{s}", .{ + options.name, + triple_prefix, + @tagName(test_target.optimize_mode), + libc_prefix, + single_threaded_txt, + backend_txt, + })); + + step.dependOn(&run.step); } return step; } @@ -1053,7 +1056,9 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S @tagName(optimize_mode), })); - step.dependOn(&test_step.step); + const run = test_step.run(); + run.skip_foreign_checks = true; + step.dependOn(&run.step); } } return step; From 8a8f148c8cb1254a3989584c54181218434b56c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 12:13:37 -0700 Subject: [PATCH 253/294] test-stack-trace: set env to disable color The tests rely on the absence of terminal escape codes. --- test/src/StackTrace.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 5709b9d29e..c32720a210 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -82,6 +82,8 @@ fn addExpect( }); const run = b.addRunArtifact(exe); + run.removeEnvironmentVariable("ZIG_DEBUG_COLOR"); + run.setEnvironmentVariable("NO_COLOR", "1"); run.expectExitCode(1); run.expectStdOutEqual(""); From 61d7e31078fc54010009494d894e2461b41eeee2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 12:15:45 -0700 Subject: [PATCH 254/294] remove bad unit test from std lib This unit test tested the command line arguments which were passed to the test runner, which is not really something that unit tests are supposed to observe. The proper way to test command line argument parsing is with a standalone test, where the set of command line arguments being tested for are also being controlled by the test itself. --- lib/std/process.zig | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index d9bf09ee2a..d06a012af2 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -828,24 +828,6 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator return ArgIterator.initWithAllocator(allocator); } -test "args iterator" { - var ga = std.testing.allocator; - var it = try argsWithAllocator(ga); - defer it.deinit(); // no-op unless WASI or Windows - - const prog_name = it.next() orelse unreachable; - const expected_suffix = switch (builtin.os.tag) { - .wasi => "test.wasm", - .windows => "test.exe", - else => "test", - }; - const given_suffix = std.fs.path.basename(prog_name); - - try testing.expect(mem.eql(u8, expected_suffix, given_suffix)); - try testing.expect(it.next() == null); - try testing.expect(!it.skip()); -} - /// Caller must call argsFree on result. pub fn argsAlloc(allocator: Allocator) ![][:0]u8 { // TODO refactor to only make 1 allocation. From a0dd2919eb3b708909275ebdd5b07c5e828e622f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 12:21:31 -0700 Subject: [PATCH 255/294] std.build.RunStep: clean up some leftover mess * Remove some functions that are no longer needed since EmulateableRunStep is gone. * Add removeEnvironmentVariable function. * Support printing environment variables in --verbose mode. --- lib/std/Build/RunStep.zig | 53 ++++++++++++++++----------------------- lib/std/Build/Step.zig | 38 ++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index 484600bf9b..b6bc0c43a4 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -206,40 +206,30 @@ pub fn clearEnvironment(self: *RunStep) void { } pub fn addPathDir(self: *RunStep, search_path: []const u8) void { - addPathDirInternal(&self.step, self.step.owner, search_path); -} - -/// For internal use only, users of `RunStep` should use `addPathDir` directly. -pub fn addPathDirInternal(step: *Step, builder: *std.Build, search_path: []const u8) void { - const env_map = getEnvMapInternal(step, builder.allocator); + const b = self.step.owner; + const env_map = getEnvMapInternal(self); const key = "PATH"; var prev_path = env_map.get(key); if (prev_path) |pp| { - const new_path = builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); + const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); env_map.put(key, new_path) catch @panic("OOM"); } else { - env_map.put(key, builder.dupePath(search_path)) catch @panic("OOM"); + env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); } } pub fn getEnvMap(self: *RunStep) *EnvMap { - return getEnvMapInternal(&self.step, self.step.owner.allocator); + return getEnvMapInternal(self); } -fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap { - const maybe_env_map = switch (step.id) { - .run => step.cast(RunStep).?.env_map, - else => unreachable, - }; - return maybe_env_map orelse { - const env_map = allocator.create(EnvMap) catch @panic("OOM"); - env_map.* = process.getEnvMap(allocator) catch @panic("unhandled error"); - switch (step.id) { - .run => step.cast(RunStep).?.env_map = env_map, - else => unreachable, - } +fn getEnvMapInternal(self: *RunStep) *EnvMap { + const arena = self.step.owner.allocator; + return self.env_map orelse { + const env_map = arena.create(EnvMap) catch @panic("OOM"); + env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); + self.env_map = env_map; return env_map; }; } @@ -250,6 +240,10 @@ pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); } +pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void { + self.getEnvMap().remove(key); +} + /// Adds a check for exact stderr match. Does not add any other checks. pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; @@ -553,7 +547,7 @@ fn runCommand( const arena = b.allocator; try step.handleChildProcUnsupported(self.cwd, argv); - try Step.handleVerbose(step.owner, self.cwd, argv); + try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv); const allow_skip = switch (self.stdio) { .check, .zig_test => self.skip_foreign_checks, @@ -676,10 +670,10 @@ fn runCommand( if (exe.target.isWindows()) { // On Windows we don't have rpaths so we have to add .dll search paths to PATH - RunStep.addPathForDynLibsInternal(&self.step, b, exe); + self.addPathForDynLibs(exe); } - try Step.handleVerbose(step.owner, self.cwd, interp_argv.items); + try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items); break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { return step.fail("unable to spawn {s}: {s}", .{ @@ -1177,18 +1171,13 @@ fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { } fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { - addPathForDynLibsInternal(&self.step, self.step.owner, artifact); -} - -/// This should only be used for internal usage, this is called automatically -/// for the user. -pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *CompileStep) void { + const b = self.step.owner; for (artifact.link_objects.items) |link_object| { switch (link_object) { .other_step => |other| { if (other.target.isWindows() and other.isDynamicLibrary()) { - addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?); - addPathForDynLibsInternal(step, builder, other); + addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?); + addPathForDynLibs(self, other); } }, else => {}, diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 05c4faa52d..890babc74e 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -427,11 +427,20 @@ pub fn handleVerbose( b: *Build, opt_cwd: ?[]const u8, argv: []const []const u8, +) error{OutOfMemory}!void { + return handleVerbose2(b, opt_cwd, null, argv); +} + +pub fn handleVerbose2( + b: *Build, + opt_cwd: ?[]const u8, + opt_env: ?*const std.process.EnvMap, + argv: []const []const u8, ) error{OutOfMemory}!void { if (b.verbose) { // Intention of verbose is to print all sub-process command lines to // stderr before spawning them. - const text = try allocPrintCmd(b.allocator, opt_cwd, argv); + const text = try allocPrintCmd2(b.allocator, opt_cwd, opt_env, argv); std.debug.print("{s}\n", .{text}); } } @@ -474,9 +483,34 @@ pub fn handleChildProcessTerm( } } -pub fn allocPrintCmd(arena: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 { +pub fn allocPrintCmd( + arena: Allocator, + opt_cwd: ?[]const u8, + argv: []const []const u8, +) ![]u8 { + return allocPrintCmd2(arena, opt_cwd, null, argv); +} + +pub fn allocPrintCmd2( + arena: Allocator, + opt_cwd: ?[]const u8, + opt_env: ?*const std.process.EnvMap, + argv: []const []const u8, +) ![]u8 { var buf: std.ArrayListUnmanaged(u8) = .{}; if (opt_cwd) |cwd| try buf.writer(arena).print("cd {s} && ", .{cwd}); + if (opt_env) |env| { + const process_env_map = try std.process.getEnvMap(arena); + var it = env.iterator(); + while (it.next()) |entry| { + const key = entry.key_ptr.*; + const value = entry.value_ptr.*; + if (process_env_map.get(key)) |process_value| { + if (std.mem.eql(u8, value, process_value)) continue; + } + try buf.writer(arena).print("{s}={s} ", .{ key, value }); + } + } for (argv) |arg| { try buf.writer(arena).print("{s} ", .{arg}); } From 8e6d46bca593088af7c7ec078f5534631e2a937d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 12:21:42 -0700 Subject: [PATCH 256/294] test runner: remove one superfluous stack frame --- lib/test_runner.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/test_runner.zig b/lib/test_runner.zig index 47ababbc2c..3d87264851 100644 --- a/lib/test_runner.zig +++ b/lib/test_runner.zig @@ -33,17 +33,13 @@ pub fn main() void { } if (listen) { - return mainServer(); + return mainServer() catch @panic("internal test runner failure"); } else { return mainTerminal(); } } -fn mainServer() void { - return mainServerFallible() catch @panic("internal test runner failure"); -} - -fn mainServerFallible() !void { +fn mainServer() !void { var server = try std.zig.Server.init(.{ .gpa = fba.allocator(), .in = std.io.getStdIn(), From bc79328dcf3a8653e26c7fe7b4cd8fc7fff6c421 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 13:03:58 -0700 Subject: [PATCH 257/294] fix endianness when using test-runner in qemu --- lib/std/zig/Server.zig | 75 +++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 3238f22043..011139f7c6 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -105,13 +105,17 @@ pub fn receiveMessage(s: *Server) !InMessage.Header { assert(fifo.readableLength() == buf.len); if (buf.len >= @sizeOf(Header)) { const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); + const bytes_len = bswap(header.bytes_len); + const tag = bswap(header.tag); - if (buf.len - @sizeOf(Header) >= header.bytes_len) { - const result = header.*; + if (buf.len - @sizeOf(Header) >= bytes_len) { fifo.discard(@sizeOf(Header)); - return result; + return .{ + .tag = tag, + .bytes_len = bytes_len, + }; } else { - const needed = header.bytes_len - (buf.len - @sizeOf(Header)); + const needed = bytes_len - (buf.len - @sizeOf(Header)); const write_buffer = try fifo.writableWithSize(needed); const amt = try s.in.read(write_buffer); fifo.update(amt); @@ -130,7 +134,7 @@ pub fn receiveBody_u32(s: *Server) !u32 { const buf = fifo.readableSlice(0); const result = @ptrCast(*align(1) const u32, buf[0..4]).*; fifo.discard(4); - return result; + return bswap(result); } pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { @@ -146,8 +150,9 @@ pub fn serveMessage( bufs: []const []const u8, ) !void { var iovecs: [10]std.os.iovec_const = undefined; + const header_le = bswap(header); iovecs[0] = .{ - .iov_base = @ptrCast([*]const u8, &header), + .iov_base = @ptrCast([*]const u8, &header_le), .iov_len = @sizeOf(OutMessage.Header), }; for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { @@ -177,11 +182,12 @@ pub fn serveTestResults( s: *Server, msg: OutMessage.TestResults, ) !void { + const msg_le = bswap(msg); try s.serveMessage(.{ .tag = .test_results, .bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)), }, &.{ - std.mem.asBytes(&msg), + std.mem.asBytes(&msg_le), }); } @@ -204,19 +210,31 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { } pub const TestMetadata = struct { - names: []const u32, - async_frame_sizes: []const u32, - expected_panic_msgs: []const u32, + names: []u32, + async_frame_sizes: []u32, + expected_panic_msgs: []u32, string_bytes: []const u8, }; pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { const header: OutMessage.TestMetadata = .{ - .tests_len = @intCast(u32, test_metadata.names.len), - .string_bytes_len = @intCast(u32, test_metadata.string_bytes.len), + .tests_len = bswap(@intCast(u32, test_metadata.names.len)), + .string_bytes_len = bswap(@intCast(u32, test_metadata.string_bytes.len)), }; const bytes_len = @sizeOf(OutMessage.TestMetadata) + 3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len; + + if (need_bswap) { + bswap_u32_array(test_metadata.names); + bswap_u32_array(test_metadata.async_frame_sizes); + bswap_u32_array(test_metadata.expected_panic_msgs); + } + defer if (need_bswap) { + bswap_u32_array(test_metadata.names); + bswap_u32_array(test_metadata.async_frame_sizes); + bswap_u32_array(test_metadata.expected_panic_msgs); + }; + return s.serveMessage(.{ .tag = .test_metadata, .bytes_len = @intCast(u32, bytes_len), @@ -230,10 +248,43 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { }); } +fn bswap(x: anytype) @TypeOf(x) { + if (!need_bswap) return x; + + const T = @TypeOf(x); + switch (@typeInfo(T)) { + .Enum => return @intToEnum(T, @byteSwap(@enumToInt(x))), + .Int => return @byteSwap(x), + .Struct => |info| switch (info.layout) { + .Extern => { + var result: T = undefined; + inline for (info.fields) |field| { + @field(result, field.name) = bswap(@field(x, field.name)); + } + return result; + }, + .Packed => { + const I = info.backing_integer.?; + return @bitCast(T, @byteSwap(@bitCast(I, x))); + }, + .Auto => @compileError("auto layout struct"), + }, + else => @compileError("bswap on type " ++ @typeName(T)), + } +} + +fn bswap_u32_array(slice: []u32) void { + comptime assert(need_bswap); + for (slice) |*elem| elem.* = @byteSwap(elem.*); +} + const OutMessage = std.zig.Server.Message; const InMessage = std.zig.Client.Message; const Server = @This(); +const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const native_endian = builtin.target.cpu.arch.endian(); +const need_bswap = native_endian != .Little; From 149aa9afb7b57340c5dd7be8032b843fe439b668 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 14:35:59 -0700 Subject: [PATCH 258/294] add a workaround for miscompilation regarding alignment See tracking issue #14904 --- lib/std/zig/Server.zig | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 011139f7c6..788e361782 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -105,8 +105,10 @@ pub fn receiveMessage(s: *Server) !InMessage.Header { assert(fifo.readableLength() == buf.len); if (buf.len >= @sizeOf(Header)) { const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - const bytes_len = bswap(header.bytes_len); - const tag = bswap(header.tag); + // workaround for https://github.com/ziglang/zig/issues/14904 + const bytes_len = bswap_and_workaround_u32(&header.bytes_len); + // workaround for https://github.com/ziglang/zig/issues/14904 + const tag = bswap_and_workaround_tag(&header.tag); if (buf.len - @sizeOf(Header) >= bytes_len) { fifo.discard(@sizeOf(Header)); @@ -278,6 +280,19 @@ fn bswap_u32_array(slice: []u32) void { for (slice) |*elem| elem.* = @byteSwap(elem.*); } +/// workaround for https://github.com/ziglang/zig/issues/14904 +fn bswap_and_workaround_u32(x: *align(1) const u32) u32 { + const bytes_ptr = @ptrCast(*const [4]u8, x); + return std.mem.readIntLittle(u32, bytes_ptr); +} + +/// workaround for https://github.com/ziglang/zig/issues/14904 +fn bswap_and_workaround_tag(x: *align(1) const InMessage.Tag) InMessage.Tag { + const bytes_ptr = @ptrCast(*const [4]u8, x); + const int = std.mem.readIntLittle(u32, bytes_ptr); + return @intToEnum(InMessage.Tag, int); +} + const OutMessage = std.zig.Server.Message; const InMessage = std.zig.Client.Message; From c5cdc0262b727e87d81fe4a92780234bf8125bd9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 14:43:21 -0700 Subject: [PATCH 259/294] add the new extern test to standalone tests This was from master branch commit c93e0d86187cb589d6726acd36f741f3d87a96be. Since standalone test are completely reworked, I had to resolve the merge conflict later, in this commit. --- test/standalone.zig | 4 ++++ test/standalone/extern/build.zig | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/standalone.zig b/test/standalone.zig index 29a32d878f..4cf795a85f 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -147,6 +147,10 @@ pub const build_cases = [_]BuildCase{ .build_root = "test/standalone/embed_generated_file", .import = @import("standalone/embed_generated_file/build.zig"), }, + .{ + .build_root = "test/standalone/extern", + .import = @import("standalone/extern/build.zig"), + }, .{ .build_root = "test/standalone/dep_diamond", .import = @import("standalone/dep_diamond/build.zig"), diff --git a/test/standalone/extern/build.zig b/test/standalone/extern/build.zig index 8a44a6ca8f..153380e91d 100644 --- a/test/standalone/extern/build.zig +++ b/test/standalone/extern/build.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const optimize = b.standardOptimizeOption(.{}); + const optimize: std.builtin.OptimizeMode = .Debug; const obj = b.addObject(.{ .name = "exports", From 66eb910fe44af7f16046bd14de76f2356f565992 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 17:26:26 -0700 Subject: [PATCH 260/294] remove redundant link test --- test/link.zig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/link.zig b/test/link.zig index 610cbd4a3d..aa0ed4817e 100644 --- a/test/link.zig +++ b/test/link.zig @@ -84,10 +84,6 @@ pub const cases = [_]Case{ .build_root = "test/link/macho/bugs/13457", .import = @import("link/macho/bugs/13457/build.zig"), }, - .{ - .build_root = "test/link/macho/bugs/13457", - .import = @import("link/macho/bugs/13457/build.zig"), - }, .{ .build_root = "test/link/macho/dead_strip", .import = @import("link/macho/dead_strip/build.zig"), From fa9108c3d4cb279e2ce1412fc75eb694b5c6baaf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 17:26:38 -0700 Subject: [PATCH 261/294] add skip_foreign_checks=true on a standalone test --- test/standalone/pie/build.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/standalone/pie/build.zig b/test/standalone/pie/build.zig index 546b4a922f..e7ef5f97cc 100644 --- a/test/standalone/pie/build.zig +++ b/test/standalone/pie/build.zig @@ -17,5 +17,8 @@ pub fn build(b: *std.Build) void { }); main.pie = true; - test_step.dependOn(&main.run().step); + const run = main.run(); + run.skip_foreign_checks = true; + + test_step.dependOn(&run.step); } From a26a2e1a178b2f2f971bbe3ed8873ac77319befe Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 17:27:05 -0700 Subject: [PATCH 262/294] build runner: fix compilation errors on windows --- lib/std/Build/Step.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 890babc74e..123122a74b 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -487,7 +487,7 @@ pub fn allocPrintCmd( arena: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8, -) ![]u8 { +) Allocator.Error![]u8 { return allocPrintCmd2(arena, opt_cwd, null, argv); } @@ -496,11 +496,11 @@ pub fn allocPrintCmd2( opt_cwd: ?[]const u8, opt_env: ?*const std.process.EnvMap, argv: []const []const u8, -) ![]u8 { +) Allocator.Error![]u8 { var buf: std.ArrayListUnmanaged(u8) = .{}; if (opt_cwd) |cwd| try buf.writer(arena).print("cd {s} && ", .{cwd}); if (opt_env) |env| { - const process_env_map = try std.process.getEnvMap(arena); + const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena); var it = env.iterator(); while (it.next()) |entry| { const key = entry.key_ptr.*; From b1299d515351acbfa1f169c8e65a3fa2b3e39f1a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 18:21:37 -0700 Subject: [PATCH 263/294] build runner: tweak progress bar display --- lib/build_runner.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index bb341574a7..5009bd6cf0 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -395,7 +395,7 @@ fn runStepNames( { defer parent_prog_node.end(); - var step_prog = parent_prog_node.start("run steps", step_stack.count()); + var step_prog = parent_prog_node.start("steps", step_stack.count()); defer step_prog.end(); var wait_group: std.Thread.WaitGroup = .{}; From e098b287e18b8a7a4df0fdb48d32fb4376daba07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 18:23:17 -0700 Subject: [PATCH 264/294] std.fs.File.writevAll: fix behavior for 0-length vectors The OS layer expects pointer addresses to be inside the application's address space even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer addresses when the length is zero. So this function now modifies the iov_base fields when the length is zero. This is a companion commit to b4893eb05565b2cb033c6ed88617d73faf878455. --- lib/std/fs/file.zig | 17 +++++++++++++++-- lib/std/os.zig | 7 +++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index f81841a662..8235b8aecf 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -1196,13 +1196,26 @@ pub const File = struct { } } - /// The `iovecs` parameter is mutable because this function needs to mutate the fields in - /// order to handle partial writes from the underlying OS layer. + /// The `iovecs` parameter is mutable because: + /// * This function needs to mutate the fields in order to handle partial + /// writes from the underlying OS layer. + /// * The OS layer expects pointer addresses to be inside the application's address space + /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer + /// addresses when the length is zero. So this function modifies the iov_base fields + /// when the length is zero. /// See https://github.com/ziglang/zig/issues/7699 /// See equivalent function: `std.net.Stream.writevAll`. pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { if (iovecs.len == 0) return; + // We use the address of this local variable for all zero-length + // vectors so that the OS does not complain that we are giving it + // addresses outside the application's address space. + var garbage: [1]u8 = undefined; + for (iovecs) |*v| { + if (v.iov_len == 0) v.iov_base = &garbage; + } + var i: usize = 0; while (true) { var amt = try self.writev(iovecs[i..]); diff --git a/lib/std/os.zig b/lib/std/os.zig index b11aac493f..722028b3f8 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -767,8 +767,8 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { /// * Windows /// On these systems, the read races with concurrent writes to the same file descriptor. /// -/// This function assumes that all zero-length vectors have a pointer within the address -/// space of the application. +/// This function assumes that all vectors, including zero-length vectors, have +/// a pointer within the address space of the application. pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { if (builtin.os.tag == .windows) { // TODO improve this to use ReadFileScatter @@ -1170,6 +1170,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. /// /// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. +/// +/// This function assumes that all vectors, including zero-length vectors, have +/// a pointer within the address space of the application. pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { if (builtin.os.tag == .windows) { // TODO improve this to use WriteFileScatter From 1dbb616e73404b789b5c85491eb905b193b9b4cb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 18:23:53 -0700 Subject: [PATCH 265/294] Module: handle incremental update from ZIR with AST errors --- src/Module.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Module.zig b/src/Module.zig index 1520a7d1b2..c47e4fc234 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3867,6 +3867,9 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { const gpa = mod.gpa; const new_zir = file.zir; + // The root decl will be null if the previous ZIR had AST errors. + const root_decl = file.root_decl.unwrap() orelse return; + // Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which // creates a namespace, gets mapped from old to new here. var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}; @@ -3884,7 +3887,6 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { var decl_stack: ArrayListUnmanaged(Decl.Index) = .{}; defer decl_stack.deinit(gpa); - const root_decl = file.root_decl.unwrap().?; try decl_stack.append(gpa, root_decl); file.deleted_decls.clearRetainingCapacity(); From 171977dc1c9d15a405b70bfa980c7d188410979e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 18:24:17 -0700 Subject: [PATCH 266/294] test-cases: fix incorrectly linking libc when backend is llvm Now link_libc=1 must be used to link with libc, instead of the test harness assuming that using the llvm backend means additionally linking with libc. --- test/cases/f32_passed_to_variadic_fn.zig | 3 ++- test/cases/fn_typeinfo_passed_to_comptime_fn.zig | 1 + test/cases/llvm/hello_world.zig | 1 + test/src/Cases.zig | 5 ++++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/cases/f32_passed_to_variadic_fn.zig b/test/cases/f32_passed_to_variadic_fn.zig index c029b4b69f..4ae1d2cf08 100644 --- a/test/cases/f32_passed_to_variadic_fn.zig +++ b/test/cases/f32_passed_to_variadic_fn.zig @@ -9,7 +9,8 @@ pub fn main() void { // run // backend=llvm // target=x86_64-linux-gnu +// link_libc=1 // // f64: 2.000000 // f32: 10.000000 -// \ No newline at end of file +// diff --git a/test/cases/fn_typeinfo_passed_to_comptime_fn.zig b/test/cases/fn_typeinfo_passed_to_comptime_fn.zig index 31673e5b81..fb64788126 100644 --- a/test/cases/fn_typeinfo_passed_to_comptime_fn.zig +++ b/test/cases/fn_typeinfo_passed_to_comptime_fn.zig @@ -14,4 +14,5 @@ fn foo(comptime info: std.builtin.Type) !void { // run // is_test=1 +// backend=llvm // diff --git a/test/cases/llvm/hello_world.zig b/test/cases/llvm/hello_world.zig index 4243191b0f..0f75f624ec 100644 --- a/test/cases/llvm/hello_world.zig +++ b/test/cases/llvm/hello_world.zig @@ -7,6 +7,7 @@ pub fn main() void { // run // backend=llvm // target=x86_64-linux,x86_64-macos +// link_libc=1 // // hello world! // diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 6affe968d1..c3a4c1df47 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -382,6 +382,7 @@ fn addFromDirInner( const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend); const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget); const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool); + const link_libc = try manifest.getConfigForKeyAssertSingle("link_libc", bool); const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode); var cases = std.ArrayList(usize).init(ctx.arena); @@ -397,7 +398,7 @@ fn addFromDirInner( .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), .is_test = is_test, .output_mode = output_mode, - .link_libc = backend == .llvm, + .link_libc = link_libc, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), }); try cases.append(next); @@ -745,6 +746,8 @@ const TestManifestConfigDefaults = struct { }; } else if (std.mem.eql(u8, key, "is_test")) { return "0"; + } else if (std.mem.eql(u8, key, "link_libc")) { + return "0"; } else unreachable; } }; From 0f88ad8c72797b1bc8899e2eb1d1c1c07a39addf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 18:52:18 -0700 Subject: [PATCH 267/294] std.Build.CompileStep: proper step dependency on headers Rather than calling make() from within make(). --- lib/std/Build/CompileStep.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 5753641966..92492e9593 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -1112,6 +1112,10 @@ fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void { self.step.dependOn(&other.step); self.link_objects.append(.{ .other_step = other }) catch @panic("OOM"); self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM"); + + for (other.installed_headers.items) |install_step| { + self.step.dependOn(install_step); + } } fn appendModuleArgs( @@ -1699,9 +1703,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(fs.path.dirname(h_path).?); } if (other.installed_headers.items.len > 0) { - for (other.installed_headers.items) |install_step| { - try install_step.make(prog_node); - } try zig_args.append("-I"); try zig_args.append(b.pathJoin(&.{ other.step.owner.install_prefix, "include", From bde12930939c50fbfa344c3701a01885e6dd06d4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 19:11:36 -0700 Subject: [PATCH 268/294] CLI: remove the experimental --watch flag The compiler REPL will move to an external process that communicates with the compiler over the binary protocol. --- src/main.zig | 142 ++++----------------------------------------------- 1 file changed, 9 insertions(+), 133 deletions(-) diff --git a/src/main.zig b/src/main.zig index 9602a5cd31..19f1a6e52c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -365,7 +365,6 @@ const usage_build_generic = \\ \\General Options: \\ -h, --help Print this help and exit - \\ --watch Enable compiler REPL \\ --color [auto|off|on] Enable or disable colored error messages \\ -femit-bin[=path] (default) Output machine code \\ -fno-emit-bin Do not output machine code @@ -700,7 +699,6 @@ fn buildOutputType( var formatted_panics: ?bool = null; var function_sections = false; var no_builtin = false; - var watch = false; var listen: Listen = .none; var debug_compile_errors = false; var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK"); @@ -1163,7 +1161,6 @@ fn buildOutputType( const next_arg = args_iter.nextOrFatal(); if (mem.eql(u8, next_arg, "-")) { listen = .stdio; - watch = true; } else { if (build_options.omit_pkg_fetching_code) unreachable; // example: --listen 127.0.0.1:9000 @@ -1174,11 +1171,9 @@ fn buildOutputType( fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) }); listen = .{ .ip4 = std.net.Ip4Address.parse(host, port) catch |err| fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }) }; - watch = true; } } else if (mem.eql(u8, arg, "--listen=-")) { listen = .stdio; - watch = true; } else if (mem.eql(u8, arg, "--debug-link-snapshot")) { if (!build_options.enable_link_snapshots) { std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{}); @@ -1207,8 +1202,6 @@ fn buildOutputType( test_evented_io = true; } else if (mem.eql(u8, arg, "--test-no-exec")) { test_no_exec = true; - } else if (mem.eql(u8, arg, "--watch")) { - watch = true; } else if (mem.eql(u8, arg, "-ftime-report")) { time_report = true; } else if (mem.eql(u8, arg, "-fstack-report")) { @@ -3355,7 +3348,7 @@ fn buildOutputType( }; updateModule(gpa, comp, hook) catch |err| switch (err) { - error.SemanticAnalyzeFail => if (!watch) process.exit(1), + error.SemanticAnalyzeFail => if (listen == .none) process.exit(1), else => |e| return e, }; if (build_options.only_c) return cleanExit(); @@ -3411,7 +3404,6 @@ fn buildOutputType( self_exe_path.?, arg_mode, target_info, - watch, &comp_destroyed, all_args, runtime_args_start, @@ -3419,113 +3411,6 @@ fn buildOutputType( ); } - // TODO move this REPL implementation to the standard library / build - // system and have it be a CLI abstraction layer on top of the real, actual - // binary protocol of the compiler. Make it actually interface through the - // server protocol. This way the REPL does not have any special powers that - // an IDE couldn't also have. - - const stdin = std.io.getStdIn().reader(); - const stderr = std.io.getStdErr().writer(); - var repl_buf: [1024]u8 = undefined; - - const ReplCmd = enum { - update, - help, - run, - update_and_run, - }; - - var last_cmd: ReplCmd = .help; - - while (watch) { - try stderr.print("(zig) ", .{}); - try comp.makeBinFileExecutable(); - if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| { - try stderr.print("\nUnable to parse command: {s}\n", .{@errorName(err)}); - continue; - }) |line| { - const actual_line = mem.trimRight(u8, line, "\r\n "); - const cmd: ReplCmd = blk: { - if (mem.eql(u8, actual_line, "update")) { - break :blk .update; - } else if (mem.eql(u8, actual_line, "exit")) { - break; - } else if (mem.eql(u8, actual_line, "help")) { - break :blk .help; - } else if (mem.eql(u8, actual_line, "run")) { - break :blk .run; - } else if (mem.eql(u8, actual_line, "update-and-run")) { - break :blk .update_and_run; - } else if (actual_line.len == 0) { - break :blk last_cmd; - } else { - try stderr.print("unknown command: {s}\n", .{actual_line}); - continue; - } - }; - last_cmd = cmd; - switch (cmd) { - .update => { - tracy.frameMark(); - if (output_mode == .Exe) { - try comp.makeBinFileWritable(); - } - updateModule(gpa, comp, hook) catch |err| switch (err) { - error.SemanticAnalyzeFail => continue, - else => |e| return e, - }; - }, - .help => { - try stderr.writeAll(repl_help); - }, - .run => { - tracy.frameMark(); - try runOrTest( - comp, - gpa, - arena, - test_exec_args.items, - self_exe_path.?, - arg_mode, - target_info, - watch, - &comp_destroyed, - all_args, - runtime_args_start, - link_libc, - ); - }, - .update_and_run => { - tracy.frameMark(); - if (output_mode == .Exe) { - try comp.makeBinFileWritable(); - } - updateModule(gpa, comp, hook) catch |err| switch (err) { - error.SemanticAnalyzeFail => continue, - else => |e| return e, - }; - try comp.makeBinFileExecutable(); - try runOrTest( - comp, - gpa, - arena, - test_exec_args.items, - self_exe_path.?, - arg_mode, - target_info, - watch, - &comp_destroyed, - all_args, - runtime_args_start, - link_libc, - ); - }, - } - } else { - break; - } - } // Skip resource deallocation in release builds; let the OS do it. return cleanExit(); } @@ -3822,7 +3707,6 @@ fn runOrTest( self_exe_path: []const u8, arg_mode: ArgMode, target_info: std.zig.system.NativeTargetInfo, - watch: bool, comp_destroyed: *bool, all_args: []const []const u8, runtime_args_start: ?usize, @@ -3853,7 +3737,7 @@ fn runOrTest( // We do not execve for tests because if the test fails we want to print // the error message and invocation below. - if (std.process.can_execv and arg_mode == .run and !watch) { + if (std.process.can_execv and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. const err = std.process.execve(gpa, argv.items, &env_map); try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); @@ -3866,12 +3750,10 @@ fn runOrTest( child.stdout_behavior = .Inherit; child.stderr_behavior = .Inherit; - if (!watch) { - // Here we release all the locks associated with the Compilation so - // that whatever this child process wants to do won't deadlock. - comp.destroy(); - comp_destroyed.* = true; - } + // Here we release all the locks associated with the Compilation so + // that whatever this child process wants to do won't deadlock. + comp.destroy(); + comp_destroyed.* = true; const term = child.spawnAndWait() catch |err| { try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc); @@ -3883,19 +3765,13 @@ fn runOrTest( switch (term) { .Exited => |code| { if (code == 0) { - if (!watch) return cleanExit(); - } else if (watch) { - warn("process exited with code {d}", .{code}); + return cleanExit(); } else { process.exit(code); } }, else => { - if (watch) { - warn("process aborted abnormally", .{}); - } else { - process.exit(1); - } + process.exit(1); }, } }, @@ -3903,7 +3779,7 @@ fn runOrTest( switch (term) { .Exited => |code| { if (code == 0) { - if (!watch) return cleanExit(); + return cleanExit(); } else { const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd }); From 2b0929929d67e222ca6a9523a3a594ed456c4a51 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 19:23:08 -0700 Subject: [PATCH 269/294] std.Build.Cache: handle ENOENT on createFile race There are no dir components, so you would think that this was unreachable, however we have observed on macOS two processes racing to do openat() with O_CREAT manifest in ENOENT. --- lib/std/Build/Cache.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index bd2b8a8927..6c5c876c8b 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -439,7 +439,10 @@ pub const Manifest = struct { self.manifest_file = manifest_file; self.have_exclusive_lock = true; } else |err| switch (err) { - error.WouldBlock => { + // There are no dir components, so you would think that this was + // unreachable, however we have observed on macOS two processes racing + // to do openat() with O_CREAT manifest in ENOENT. + error.WouldBlock, error.FileNotFound => { self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{ .lock = .Shared, }); From 22d94eaf32bc303ff757f442991f570b264b24c3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 23:16:28 -0700 Subject: [PATCH 270/294] disable std lib unit tests that hard code port numbers See tracking issue #14907 --- lib/std/os/linux/io_uring.zig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig index 61bf39105f..a302f2cdf1 100644 --- a/lib/std/os/linux/io_uring.zig +++ b/lib/std/os/linux/io_uring.zig @@ -1976,6 +1976,11 @@ test "close" { test "accept/connect/send/recv" { if (builtin.os.tag != .linux) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/14907 + return error.SkipZigTest; + } + var ring = IO_Uring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2017,6 +2022,11 @@ test "accept/connect/send/recv" { test "sendmsg/recvmsg" { if (builtin.os.tag != .linux) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/14907 + return error.SkipZigTest; + } + var ring = IO_Uring.init(2, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2024,6 +2034,7 @@ test "sendmsg/recvmsg" { }; defer ring.deinit(); + if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907 const address_server = try net.Address.parseIp4("127.0.0.1", 3131); const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0); @@ -2223,6 +2234,11 @@ test "timeout_remove" { test "accept/connect/recv/link_timeout" { if (builtin.os.tag != .linux) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/14907 + return error.SkipZigTest; + } + var ring = IO_Uring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2372,6 +2388,11 @@ test "statx" { test "accept/connect/recv/cancel" { if (builtin.os.tag != .linux) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/14907 + return error.SkipZigTest; + } + var ring = IO_Uring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2509,6 +2530,11 @@ test "register_files_update" { test "shutdown" { if (builtin.os.tag != .linux) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/14907 + return error.SkipZigTest; + } + var ring = IO_Uring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -2516,6 +2542,7 @@ test "shutdown" { }; defer ring.deinit(); + if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907 const address = try net.Address.parseIp4("127.0.0.1", 3131); // Socket bound, expect shutdown to work @@ -3060,6 +3087,11 @@ test "remove_buffers" { test "provide_buffers: accept/connect/send/recv" { if (builtin.os.tag != .linux) return error.SkipZigTest; + if (true) { + // https://github.com/ziglang/zig/issues/14907 + return error.SkipZigTest; + } + var ring = IO_Uring.init(16, 0) catch |err| switch (err) { error.SystemOutdated => return error.SkipZigTest, error.PermissionDenied => return error.SkipZigTest, @@ -3236,6 +3268,7 @@ const SocketTestHarness = struct { fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness { // Create a TCP server socket + if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907 const address = try net.Address.parseIp4("127.0.0.1", 3131); const kernel_backlog = 1; const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0); From 941cae4331ff4922ccef08a5d3d5879a0d1fa786 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 23:41:41 -0700 Subject: [PATCH 271/294] revert a change to C ABI tests See tracking issue #14908 --- test/tests.zig | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 76ea537bb2..7af1ba6c3b 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1045,9 +1045,13 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S } test_step.linkLibC(); test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"}); - // This test is intentionally trying to check if the external ABI is - // done properly. LTO would be a hindrance to this. - test_step.want_lto = false; + + // test-c-abi should test both with LTO on and with LTO off. Only + // some combinations are passing currently: + // https://github.com/ziglang/zig/issues/14908 + if (c_abi_target.isWindows() and (c_abi_target.getCpuArch() == .x86 or builtin.target.os.tag == .linux)) { + test_step.want_lto = false; + } const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM"); test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{ From 3e328c89b7016cb688c88ddb11a8949851500928 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Mar 2023 23:42:15 -0700 Subject: [PATCH 272/294] std.Build.CompileStep: remove setNamePrefix and add setName --- lib/std/Build/CompileStep.zig | 11 ++--------- test/tests.zig | 6 ++---- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 92492e9593..d73e5d3b41 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -83,7 +83,6 @@ c_std: std.Build.CStd, zig_lib_dir: ?[]const u8, main_pkg_path: ?[]const u8, exec_cmd_args: ?[]const ?[]const u8, -name_prefix: []const u8, filter: ?[]const u8, test_evented_io: bool = false, test_runner: ?[]const u8, @@ -374,7 +373,6 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .zig_lib_dir = null, .main_pkg_path = null, .exec_cmd_args = null, - .name_prefix = "", .filter = null, .test_runner = null, .disable_stack_probing = false, @@ -847,10 +845,10 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { }) catch @panic("OOM"); } -pub fn setNamePrefix(self: *CompileStep, text: []const u8) void { +pub fn setName(self: *CompileStep, text: []const u8) void { const b = self.step.owner; assert(self.kind == .@"test"); - self.name_prefix = b.dupe(text); + self.name = b.dupe(text); } pub fn setFilter(self: *CompileStep, text: ?[]const u8) void { @@ -1424,11 +1422,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append("--test-evented-io"); } - if (self.name_prefix.len != 0) { - try zig_args.append("--test-name-prefix"); - try zig_args.append(self.name_prefix); - } - if (self.test_runner) |test_runner| { try zig_args.append("--test-runner"); try zig_args.append(b.pathFromRoot(test_runner)); diff --git a/test/tests.zig b/test/tests.zig index 7af1ba6c3b..737cf5e291 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1054,10 +1054,8 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S } const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM"); - test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{ - "test-c-abi", - triple_prefix, - @tagName(optimize_mode), + test_step.setName(b.fmt("test-c-abi-{s}-{s} ", .{ + triple_prefix, @tagName(optimize_mode), })); const run = test_step.run(); From 63bd0fe58e80742d273a962c03145ce598f060b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 11:41:37 -0700 Subject: [PATCH 273/294] use DEC graphics instead of Unicode for box drawing --- lib/build_runner.zig | 21 +++++++++++++++------ lib/std/debug.zig | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 5009bd6cf0..5f5601a68d 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -532,14 +532,17 @@ const PrintNode = struct { last: bool = false, }; -fn printPrefix(node: *PrintNode, stderr: std.fs.File) !void { +fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.debug.TTY.Config) !void { const parent = node.parent orelse return; if (parent.parent == null) return; - try printPrefix(parent, stderr); + try printPrefix(parent, stderr, ttyconf); if (parent.last) { try stderr.writeAll(" "); } else { - try stderr.writeAll("│ "); + try stderr.writeAll(switch (ttyconf) { + .no_color, .windows_api => "| ", + .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ + }); } } @@ -552,14 +555,20 @@ fn printTreeStep( step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { const first = step_stack.swapRemove(s); - try printPrefix(parent_node, stderr); + try printPrefix(parent_node, stderr, ttyconf); if (!first) try ttyconf.setColor(stderr, .Dim); if (parent_node.parent != null) { if (parent_node.last) { - try stderr.writeAll("└─ "); + try stderr.writeAll(switch (ttyconf) { + .no_color, .windows_api => "+- ", + .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ + }); } else { - try stderr.writeAll("├─ "); + try stderr.writeAll(switch (ttyconf) { + .no_color, .windows_api => "+- ", + .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ + }); } } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 3c5f6d2edf..6abceed9b8 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -685,6 +685,36 @@ pub const TTY = struct { }, }; } + + pub fn writeDEC(conf: Config, writer: anytype, codepoint: u8) !void { + const bytes = switch (conf) { + .no_color, .windows_api => switch (codepoint) { + 0x50...0x5e => @as(*const [1]u8, &codepoint), + 0x6a => "+", // ┘ + 0x6b => "+", // ┐ + 0x6c => "+", // ┌ + 0x6d => "+", // └ + 0x6e => "+", // ┼ + 0x71 => "-", // ─ + 0x74 => "+", // ├ + 0x75 => "+", // ┤ + 0x76 => "+", // ┴ + 0x77 => "+", // ┬ + 0x78 => "|", // │ + else => " ", // TODO + }, + .escape_codes => switch (codepoint) { + // Here we avoid writing the DEC beginning sequence and + // ending sequence in separate syscalls by putting the + // beginning and ending sequence into the same string + // literals, to prevent terminals ending up in bad states + // in case a crash happens between syscalls. + inline 0x50...0x7f => |x| "\x1B\x28\x30" ++ [1]u8{x} ++ "\x1B\x28\x42", + else => unreachable, + }, + }; + return writer.writeAll(bytes); + } }; }; From 11de55d0ddd54afabb3e26a85adba0a1f0bbe35b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 11:42:00 -0700 Subject: [PATCH 274/294] std.Build.Cache: handle ENOENT on createFile race Companion commit to 628fec41593a2d2eca8b504e4fe90de9823aeded --- lib/std/Build/Cache.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 6c5c876c8b..92e20b1f9d 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -422,7 +422,11 @@ pub const Manifest = struct { self.have_exclusive_lock = true; return false; // cache miss; exclusive lock already held } else |err| switch (err) { - error.WouldBlock => continue, + // There are no dir components, so you would think + // that this was unreachable, however we have + // observed on macOS two processes racing to do + // openat() with O_CREAT manifest in ENOENT. + error.WouldBlock, error.FileNotFound => continue, else => |e| return e, } }, From 37a7d2c78d27a327c70bb6865e1c6d114459a3af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 14:36:34 -0700 Subject: [PATCH 275/294] std.Build.RunStep: fix handling spawn failure The error was caught and created a Step failure rather than bubbling up so that the interpreter logic could handle it. Fixes hundreds of test failures on Windows. --- lib/std/Build/RunStep.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index b6bc0c43a4..d2fb20bdae 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -676,7 +676,7 @@ fn runCommand( try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items); break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { - return step.fail("unable to spawn {s}: {s}", .{ + return step.fail("unable to spawn interpreter {s}: {s}", .{ interp_argv.items[0], @errorName(e), }); }; @@ -889,9 +889,7 @@ fn spawnChildAndCollect( child.stdin_behavior = .Pipe; } - child.spawn() catch |err| return self.step.fail("unable to spawn {s}: {s}", .{ - argv[0], @errorName(err), - }); + try child.spawn(); var timer = try std.time.Timer.start(); const result = if (self.stdio == .zig_test) From ed33901218363b9d1baee4c37e85f573dc034e06 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 14:44:38 -0700 Subject: [PATCH 276/294] test-cli: fix expected stderr on windows Needed to account for backward slashes in file system paths. --- test/tests.zig | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 737cf5e291..af0fcea2d5 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -660,20 +660,19 @@ pub fn addLinkTests( pub fn addCliTests(b: *std.Build) *Step { const step = b.step("test-cli", "Test the command line interface"); + const s = std.fs.path.sep_str; { + // Test `zig init-lib`. const tmp_path = b.makeTempPath(); const init_lib = b.addSystemCommand(&.{ b.zig_exe, "init-lib" }); init_lib.cwd = tmp_path; init_lib.setName("zig init-lib"); init_lib.expectStdOutEqual(""); - init_lib.expectStdErrEqual( - \\info: Created build.zig - \\info: Created src/main.zig - \\info: Next, try `zig build --help` or `zig build test` - \\ - ); + init_lib.expectStdErrEqual("info: Created build.zig\n" ++ + "info: Created src" ++ s ++ "main.zig\n" ++ + "info: Next, try `zig build --help` or `zig build test`\n"); const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" }); run_test.cwd = tmp_path; @@ -694,15 +693,11 @@ pub fn addCliTests(b: *std.Build) *Step { init_exe.cwd = tmp_path; init_exe.setName("zig init-exe"); init_exe.expectStdOutEqual(""); - init_exe.expectStdErrEqual( - \\info: Created build.zig - \\info: Created src/main.zig - \\info: Next, try `zig build --help` or `zig build run` - \\ - ); + init_exe.expectStdErrEqual("info: Created build.zig\n" ++ + "info: Created src" ++ s ++ "main.zig\n" ++ + "info: Next, try `zig build --help` or `zig build run`\n"); // Test missing output path. - const s = std.fs.path.sep_str; const bad_out_arg = "-femit-bin=does" ++ s ++ "not" ++ s ++ "exist" ++ s ++ "foo.exe"; const ok_src_arg = "src" ++ s ++ "main.zig"; const expected = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n"; @@ -785,7 +780,6 @@ pub fn addCliTests(b: *std.Build) *Step { // owners. const tmp_path = b.makeTempPath(); const unformatted_code = " // no reason for indent"; - const s = std.fs.path.sep_str; var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled"); defer dir.close(); From 363d4a107de3af95620f82e851e8777aa9f576c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 16:12:24 -0700 Subject: [PATCH 277/294] add compile log output to build runner --- lib/std/zig/ErrorBundle.zig | 29 ++++++++++++++++++++++++++++- src/Compilation.zig | 8 ++------ src/Sema.zig | 2 +- src/main.zig | 18 +++++++----------- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 845e9d8ff5..ffe748203e 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -32,6 +32,8 @@ pub const SourceLocationIndex = enum(u32) { pub const ErrorMessageList = struct { len: u32, start: u32, + /// null-terminated string index. 0 means no compile log text. + compile_log_text: u32, }; /// Trailing: @@ -110,6 +112,10 @@ pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex { return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]); } +pub fn getCompileLogOutput(eb: ErrorBundle) [:0]const u8 { + return nullTerminatedString(eb, getErrorMessageList(eb).compile_log_text); +} + /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, end: usize } { @@ -145,6 +151,7 @@ pub const RenderOptions = struct { ttyconf: std.debug.TTY.Config, include_reference_trace: bool = true, include_source_line: bool = true, + include_log_text: bool = true, }; pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void { @@ -158,6 +165,14 @@ pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) for (eb.getMessages()) |err_msg| { try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0); } + + if (options.include_log_text) { + const log_text = eb.getCompileLogOutput(); + if (log_text.len != 0) { + try writer.writeAll("\nCompile Log Output:\n"); + try writer.writeAll(log_text); + } + } } fn renderErrorMessageToWriter( @@ -314,6 +329,7 @@ pub const Wip = struct { assert(0 == try addExtra(wip, ErrorMessageList{ .len = 0, .start = 0, + .compile_log_text = 0, })); } @@ -325,9 +341,10 @@ pub const Wip = struct { wip.* = undefined; } - pub fn toOwnedBundle(wip: *Wip) !ErrorBundle { + pub fn toOwnedBundle(wip: *Wip, compile_log_text: []const u8) !ErrorBundle { const gpa = wip.gpa; if (wip.root_list.items.len == 0) { + assert(compile_log_text.len == 0); // Special encoding when there are no errors. wip.deinit(); wip.* = .{ @@ -338,9 +355,19 @@ pub const Wip = struct { }; return empty; } + + const compile_log_str_index = if (compile_log_text.len == 0) 0 else str: { + const str = @intCast(u32, wip.string_bytes.items.len); + try wip.string_bytes.ensureUnusedCapacity(gpa, compile_log_text.len + 1); + wip.string_bytes.appendSliceAssumeCapacity(compile_log_text); + wip.string_bytes.appendAssumeCapacity(0); + break :str str; + }; + wip.setExtra(0, ErrorMessageList{ .len = @intCast(u32, wip.root_list.items.len), .start = @intCast(u32, wip.extra.items.len), + .compile_log_text = compile_log_str_index, }); try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items)); wip.root_list.clearAndFree(gpa); diff --git a/src/Compilation.zig b/src/Compilation.zig index ec21d2c483..89512ce744 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2705,7 +2705,8 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { assert(self.totalErrorCount() == bundle.root_list.items.len); - return bundle.toOwnedBundle(); + const compile_log_text = if (self.bin_file.options.module) |m| m.compile_log_text.items else ""; + return bundle.toOwnedBundle(compile_log_text); } pub const ErrorNoteHashContext = struct { @@ -2954,11 +2955,6 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void { } } -pub fn getCompileLogOutput(self: *Compilation) []const u8 { - const module = self.bin_file.options.module orelse return &[0]u8{}; - return module.compile_log_text.items; -} - pub fn performAllTheWork( comp: *Compilation, main_progress_node: *std.Progress.Node, diff --git a/src/Sema.zig b/src/Sema.zig index c9f4f27fe2..fc39fbb9fc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2219,7 +2219,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { wip_errors.init(gpa) catch unreachable; Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable; std.debug.print("compile error during Sema:\n", .{}); - var error_bundle = wip_errors.toOwnedBundle() catch unreachable; + var error_bundle = wip_errors.toOwnedBundle("") catch unreachable; error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); crash_report.compilerPanic("unexpected compile error occurred", null, null); } diff --git a/src/main.zig b/src/main.zig index 19f1a6e52c..e7d5c647b5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3886,10 +3886,6 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void if (errors.errorMessageCount() > 0) { errors.renderToStdErr(renderOptions(comp.color)); - const log_text = comp.getCompileLogOutput(); - if (log_text.len != 0) { - std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); - } return error.SemanticAnalyzeFail; } else switch (hook) { .none => {}, @@ -4512,7 +4508,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi &all_modules, ); if (wip_errors.root_list.items.len > 0) { - var errors = try wip_errors.toOwnedBundle(); + var errors = try wip_errors.toOwnedBundle(""); defer errors.deinit(gpa); errors.renderToStdErr(renderOptions(color)); process.exit(1); @@ -4775,7 +4771,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - var error_bundle = try wip_errors.toOwnedBundle(); + var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(renderOptions(color)); process.exit(2); @@ -4981,7 +4977,7 @@ fn fmtPathFile( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - var error_bundle = try wip_errors.toOwnedBundle(); + var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(renderOptions(fmt.color)); fmt.any_error = true; @@ -5018,7 +5014,7 @@ fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Co try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors); - var error_bundle = try wip_errors.toOwnedBundle(); + var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(renderOptions(color)); } @@ -5622,7 +5618,7 @@ pub fn cmdAstCheck( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - var error_bundle = try wip_errors.toOwnedBundle(); + var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); @@ -5739,7 +5735,7 @@ pub fn cmdChangelist( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - var error_bundle = try wip_errors.toOwnedBundle(); + var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); @@ -5774,7 +5770,7 @@ pub fn cmdChangelist( try wip_errors.init(gpa); defer wip_errors.deinit(); try Compilation.addZirErrorMessages(&wip_errors, &file); - var error_bundle = try wip_errors.toOwnedBundle(); + var error_bundle = try wip_errors.toOwnedBundle(""); defer error_bundle.deinit(gpa); error_bundle.renderToStdErr(renderOptions(color)); process.exit(1); From 717e2c8718a3161d6b86317b6132fa296325a88c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 16:38:14 -0700 Subject: [PATCH 278/294] std.Build.Cache: make unit tests not depend on cwd This makes them more resilient to being run multiple times by multiple different processes at the same time. --- lib/std/Build/Cache.zig | 78 ++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 92e20b1f9d..b25e349168 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -976,16 +976,16 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void { } // Create/Write a file, close it, then grab its stat.mtime timestamp. -fn testGetCurrentFileTimestamp() !i128 { +fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 { const test_out_file = "test-filetimestamp.tmp"; - var file = try fs.cwd().createFile(test_out_file, .{ + var file = try dir.createFile(test_out_file, .{ .read = true, .truncate = true, }); defer { file.close(); - fs.cwd().deleteFile(test_out_file) catch {}; + dir.deleteFile(test_out_file) catch {}; } return (try file.stat()).mtime; @@ -997,16 +997,17 @@ test "cache file and then recall it" { return error.SkipZigTest; } - const cwd = fs.cwd(); + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); const temp_file = "test.txt"; const temp_manifest_dir = "temp_manifest_dir"; - try cwd.writeFile(temp_file, "Hello, world!\n"); + try tmp.dir.writeFile(temp_file, "Hello, world!\n"); // Wait for file timestamps to tick - const initial_time = try testGetCurrentFileTimestamp(); - while ((try testGetCurrentFileTimestamp()) == initial_time) { + const initial_time = try testGetCurrentFileTimestamp(tmp.dir); + while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) { std.time.sleep(1); } @@ -1016,9 +1017,9 @@ test "cache file and then recall it" { { var cache = Cache{ .gpa = testing.allocator, - .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(); { @@ -1054,9 +1055,6 @@ test "cache file and then recall it" { try testing.expectEqual(digest1, digest2); } - - try cwd.deleteTree(temp_manifest_dir); - try cwd.deleteFile(temp_file); } test "check that changing a file makes cache fail" { @@ -1064,21 +1062,19 @@ test "check that changing a file makes cache fail" { // https://github.com/ziglang/zig/issues/5437 return error.SkipZigTest; } - const cwd = fs.cwd(); + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); const temp_file = "cache_hash_change_file_test.txt"; const temp_manifest_dir = "cache_hash_change_file_manifest_dir"; const original_temp_file_contents = "Hello, world!\n"; const updated_temp_file_contents = "Hello, world; but updated!\n"; - try cwd.deleteTree(temp_manifest_dir); - try cwd.deleteTree(temp_file); - - try cwd.writeFile(temp_file, original_temp_file_contents); + try tmp.dir.writeFile(temp_file, original_temp_file_contents); // Wait for file timestamps to tick - const initial_time = try testGetCurrentFileTimestamp(); - while ((try testGetCurrentFileTimestamp()) == initial_time) { + const initial_time = try testGetCurrentFileTimestamp(tmp.dir); + while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) { std.time.sleep(1); } @@ -1088,9 +1084,9 @@ test "check that changing a file makes cache fail" { { var cache = Cache{ .gpa = testing.allocator, - .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(); { @@ -1110,7 +1106,7 @@ test "check that changing a file makes cache fail" { try ch.writeManifest(); } - try cwd.writeFile(temp_file, updated_temp_file_contents); + try tmp.dir.writeFile(temp_file, updated_temp_file_contents); { var ch = cache.obtain(); @@ -1132,9 +1128,6 @@ test "check that changing a file makes cache fail" { try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..])); } - - try cwd.deleteTree(temp_manifest_dir); - try cwd.deleteTree(temp_file); } test "no file inputs" { @@ -1142,18 +1135,20 @@ test "no file inputs" { // https://github.com/ziglang/zig/issues/5437 return error.SkipZigTest; } - const cwd = fs.cwd(); + + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + const temp_manifest_dir = "no_file_inputs_manifest_dir"; - defer cwd.deleteTree(temp_manifest_dir) catch {}; var digest1: [hex_digest_len]u8 = undefined; var digest2: [hex_digest_len]u8 = undefined; var cache = Cache{ .gpa = testing.allocator, - .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(); { @@ -1188,18 +1183,19 @@ test "Manifest with files added after initial hash work" { // https://github.com/ziglang/zig/issues/5437 return error.SkipZigTest; } - const cwd = fs.cwd(); + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); const temp_file1 = "cache_hash_post_file_test1.txt"; const temp_file2 = "cache_hash_post_file_test2.txt"; const temp_manifest_dir = "cache_hash_post_file_manifest_dir"; - try cwd.writeFile(temp_file1, "Hello, world!\n"); - try cwd.writeFile(temp_file2, "Hello world the second!\n"); + try tmp.dir.writeFile(temp_file1, "Hello, world!\n"); + try tmp.dir.writeFile(temp_file2, "Hello world the second!\n"); // Wait for file timestamps to tick - const initial_time = try testGetCurrentFileTimestamp(); - while ((try testGetCurrentFileTimestamp()) == initial_time) { + const initial_time = try testGetCurrentFileTimestamp(tmp.dir); + while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) { std.time.sleep(1); } @@ -1210,9 +1206,9 @@ test "Manifest with files added after initial hash work" { { var cache = Cache{ .gpa = testing.allocator, - .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}), + .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), }; - cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(); { @@ -1245,11 +1241,11 @@ test "Manifest with files added after initial hash work" { try testing.expect(mem.eql(u8, &digest1, &digest2)); // Modify the file added after initial hash - try cwd.writeFile(temp_file2, "Hello world the second, updated\n"); + try tmp.dir.writeFile(temp_file2, "Hello world the second, updated\n"); // Wait for file timestamps to tick - const initial_time2 = try testGetCurrentFileTimestamp(); - while ((try testGetCurrentFileTimestamp()) == initial_time2) { + const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir); + while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) { std.time.sleep(1); } @@ -1272,8 +1268,4 @@ test "Manifest with files added after initial hash work" { try testing.expect(!mem.eql(u8, &digest1, &digest3)); } - - try cwd.deleteTree(temp_manifest_dir); - try cwd.deleteFile(temp_file1); - try cwd.deleteFile(temp_file2); } From 6d6f6a4ac6f1f2666fa2d4500ef4634fdfe0d947 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 17:21:29 -0700 Subject: [PATCH 279/294] std.os.windows.OpenFile: handle DELETE_PENDING This error means that there *was* a file in this location on the file system, but it was deleted. However, the OS is not finished with the deletion operation, and so this CreateFile call has failed. There is not really a sane way to handle this other than retrying the creation after the OS finishes the deletion. --- lib/std/os/windows.zig | 82 ++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b63fdb9f92..5576200ea5 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -105,41 +105,53 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT. const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT; - const rc = ntdll.NtCreateFile( - &result, - options.access_mask, - &attr, - &io, - null, - FILE_ATTRIBUTE_NORMAL, - options.share_access, - options.creation, - flags, - null, - 0, - ); - switch (rc) { - .SUCCESS => { - if (std.io.is_async and options.io_mode == .evented) { - _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined; - } - return result; - }, - .OBJECT_NAME_INVALID => unreachable, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NO_MEDIA_IN_DEVICE => return error.NoDevice, - .INVALID_PARAMETER => unreachable, - .SHARING_VIOLATION => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - .PIPE_BUSY => return error.PipeBusy, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - .FILE_IS_A_DIRECTORY => return error.IsDir, - .NOT_A_DIRECTORY => return error.NotDir, - .USER_MAPPED_FILE => return error.AccessDenied, - .INVALID_HANDLE => unreachable, - else => return unexpectedStatus(rc), + while (true) { + const rc = ntdll.NtCreateFile( + &result, + options.access_mask, + &attr, + &io, + null, + FILE_ATTRIBUTE_NORMAL, + options.share_access, + options.creation, + flags, + null, + 0, + ); + switch (rc) { + .SUCCESS => { + if (std.io.is_async and options.io_mode == .evented) { + _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined; + } + return result; + }, + .OBJECT_NAME_INVALID => unreachable, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NO_MEDIA_IN_DEVICE => return error.NoDevice, + .INVALID_PARAMETER => unreachable, + .SHARING_VIOLATION => return error.AccessDenied, + .ACCESS_DENIED => return error.AccessDenied, + .PIPE_BUSY => return error.PipeBusy, + .OBJECT_PATH_SYNTAX_BAD => unreachable, + .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, + .FILE_IS_A_DIRECTORY => return error.IsDir, + .NOT_A_DIRECTORY => return error.NotDir, + .USER_MAPPED_FILE => return error.AccessDenied, + .INVALID_HANDLE => unreachable, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. There is not really a sane way to handle + // this other than retrying the creation after the OS finishes + // the deletion. + std.time.sleep(std.time.ns_per_ms); + continue; + }, + else => return unexpectedStatus(rc), + } } } From 6664d2418d0348c6432a1c93c96b4412f80036a8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 18:02:33 -0700 Subject: [PATCH 280/294] test-cases: add missing compile log output The new testing harness is not bound by previous limitations; it can now test compile log output as well. --- ...mpileLog_of_tagged_enum_doesnt_crash_the_compiler.zig | 4 ++++ test/cases/compile_errors/compile_log.zig | 9 +++++++++ .../compile_log_a_pointer_to_an_opaque_value.zig | 5 ++++- ..._inside_function_which_must_be_comptime_evaluated.zig | 3 +++ ...log_statement_warning_deduplication_in_generic_fn.zig | 5 +++++ test/cases/compile_log.0.zig | 5 +++++ test/cases/compile_log.1.zig | 4 ++++ 7 files changed, 34 insertions(+), 1 deletion(-) diff --git a/test/cases/compile_errors/compileLog_of_tagged_enum_doesnt_crash_the_compiler.zig b/test/cases/compile_errors/compileLog_of_tagged_enum_doesnt_crash_the_compiler.zig index 9189eeb48d..55676f9230 100644 --- a/test/cases/compile_errors/compileLog_of_tagged_enum_doesnt_crash_the_compiler.zig +++ b/test/cases/compile_errors/compileLog_of_tagged_enum_doesnt_crash_the_compiler.zig @@ -15,3 +15,7 @@ pub export fn entry() void { // target=native // // :6:5: error: found compile log statement +// +// Compile Log Output: +// @as(tmp.Bar, .{ .X = 123 }) +// @as(tmp.Bar, [runtime value]) diff --git a/test/cases/compile_errors/compile_log.zig b/test/cases/compile_errors/compile_log.zig index 772853b023..e1ea460dc3 100644 --- a/test/cases/compile_errors/compile_log.zig +++ b/test/cases/compile_errors/compile_log.zig @@ -17,3 +17,12 @@ export fn baz() void { // // :5:5: error: found compile log statement // :11:5: note: also here +// +// Compile Log Output: +// @as(*const [5:0]u8, "begin") +// @as(*const [1:0]u8, "a"), @as(i32, 12), @as(*const [1:0]u8, "b"), @as([]const u8, "hi") +// @as(*const [3:0]u8, "end") +// @as(comptime_int, 4) +// @as(*const [5:0]u8, "begin") +// @as(*const [1:0]u8, "a"), @as(i32, [runtime value]), @as(*const [1:0]u8, "b"), @as([]const u8, [runtime value]) +// @as(*const [3:0]u8, "end") diff --git a/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig b/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig index 252b6e5f14..0162860525 100644 --- a/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig +++ b/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig @@ -1,5 +1,5 @@ export fn entry() void { - @compileLog(@ptrCast(*const anyopaque, &entry)); + @compileLog(@ptrCast(*align(1) const anyopaque, &entry)); } // error @@ -7,3 +7,6 @@ export fn entry() void { // target=native // // :2:5: error: found compile log statement +// +// Compile Log Output: +// @as(*const anyopaque, (function 'entry')) diff --git a/test/cases/compile_errors/compile_log_statement_inside_function_which_must_be_comptime_evaluated.zig b/test/cases/compile_errors/compile_log_statement_inside_function_which_must_be_comptime_evaluated.zig index 0bc45eae0a..8a39fdec46 100644 --- a/test/cases/compile_errors/compile_log_statement_inside_function_which_must_be_comptime_evaluated.zig +++ b/test/cases/compile_errors/compile_log_statement_inside_function_which_must_be_comptime_evaluated.zig @@ -12,3 +12,6 @@ export fn entry() void { // target=native // // :2:5: error: found compile log statement +// +// Compile Log Output: +// @as(*const [3:0]u8, "i32\x00") diff --git a/test/cases/compile_errors/compile_log_statement_warning_deduplication_in_generic_fn.zig b/test/cases/compile_errors/compile_log_statement_warning_deduplication_in_generic_fn.zig index 76e1c80cf9..4b31d9924a 100644 --- a/test/cases/compile_errors/compile_log_statement_warning_deduplication_in_generic_fn.zig +++ b/test/cases/compile_errors/compile_log_statement_warning_deduplication_in_generic_fn.zig @@ -13,3 +13,8 @@ fn inner(comptime n: usize) void { // // :7:39: error: found compile log statement // :7:39: note: also here +// +// Compile Log Output: +// @as(*const [4:0]u8, "!@#$") +// @as(*const [4:0]u8, "!@#$") +// @as(*const [4:0]u8, "!@#$") diff --git a/test/cases/compile_log.0.zig b/test/cases/compile_log.0.zig index 161a6f37ca..27dfbc706d 100644 --- a/test/cases/compile_log.0.zig +++ b/test/cases/compile_log.0.zig @@ -15,3 +15,8 @@ fn x() void {} // error // // :6:23: error: expected type 'usize', found 'bool' +// +// Compile Log Output: +// @as(bool, true), @as(comptime_int, 20), @as(u32, [runtime value]), @as(fn() void, (function 'x')) +// @as(comptime_int, 1000) +// @as(comptime_int, 1234) diff --git a/test/cases/compile_log.1.zig b/test/cases/compile_log.1.zig index 12e6641542..286722653c 100644 --- a/test/cases/compile_log.1.zig +++ b/test/cases/compile_log.1.zig @@ -14,3 +14,7 @@ fn x() void {} // // :9:5: error: found compile log statement // :4:5: note: also here +// +// Compile Log Output: +// @as(bool, true), @as(comptime_int, 20), @as(u32, [runtime value]), @as(fn() void, (function 'x')) +// @as(comptime_int, 1000) From 4f1382e58166c4324f842450fdad4d134d553085 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 19:47:42 -0700 Subject: [PATCH 281/294] add std.LinearFifo.readableSliceOfLen --- lib/std/fifo.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index 5a72b56269..eddbff5af0 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -164,6 +164,17 @@ pub fn LinearFifo( return self.readableSliceMut(offset); } + pub fn readableSliceOfLen(self: *Self, len: usize) []const T { + assert(len <= self.count); + const buf = self.readableSlice(0); + if (buf.len >= len) { + return buf[0..len]; + } else { + self.realign(); + return self.readableSlice(0)[0..len]; + } + } + /// Discard first `count` items in the fifo pub fn discard(self: *Self, count: usize) void { assert(count <= self.count); From 4aa5895d322c58cfa654e890beccc1009d49c6e5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 19:50:52 -0700 Subject: [PATCH 282/294] std.Build: fix invalid assumption about fifos Previously this code asserted that a fifo's readable length was greater than or equal to the length of its readable slice, which was an invalid assertion. This code avoids making that assumption. --- lib/std/Build/RunStep.zig | 157 +++++++++++++++++++------------------- lib/std/Build/Step.zig | 99 ++++++++++++------------ 2 files changed, 129 insertions(+), 127 deletions(-) diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig index d2fb20bdae..feeb64f6ca 100644 --- a/lib/std/Build/RunStep.zig +++ b/lib/std/Build/RunStep.zig @@ -949,85 +949,86 @@ fn evalZigTest( var sub_prog_node: ?std.Progress.Node = null; defer if (sub_prog_node) |*n| n.end(); - poll: while (try poller.poll()) { - while (true) { - const buf = stdout.readableSlice(0); - assert(stdout.readableLength() == buf.len); - if (buf.len < @sizeOf(Header)) continue :poll; - const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - const header_and_msg_len = header.bytes_len + @sizeOf(Header); - if (buf.len < header_and_msg_len) continue :poll; - const body = buf[@sizeOf(Header)..][0..header.bytes_len]; - switch (header.tag) { - .zig_version => { - if (!std.mem.eql(u8, builtin.zig_version_string, body)) { - return self.step.fail( - "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", - .{ builtin.zig_version_string, body }, - ); - } - }, - .test_metadata => { - const TmHdr = std.zig.Server.Message.TestMetadata; - const tm_hdr = @ptrCast(*align(1) const TmHdr, body); - test_count = tm_hdr.tests_len; - - const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; - const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; - const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; - const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; - - const names = std.mem.bytesAsSlice(u32, names_bytes); - const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); - const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); - const names_aligned = try arena.alloc(u32, names.len); - for (names_aligned, names) |*dest, src| dest.* = src; - - const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); - for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; - - const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); - for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; - - prog_node.setEstimatedTotalItems(names.len); - metadata = .{ - .string_bytes = try arena.dupe(u8, string_bytes), - .names = names_aligned, - .async_frame_lens = async_frame_lens_aligned, - .expected_panic_msgs = expected_panic_msgs_aligned, - .next_index = 0, - .prog_node = prog_node, - }; - - try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); - }, - .test_results => { - const md = metadata.?; - - const TrHdr = std.zig.Server.Message.TestResults; - const tr_hdr = @ptrCast(*align(1) const TrHdr, body); - fail_count += @boolToInt(tr_hdr.flags.fail); - skip_count += @boolToInt(tr_hdr.flags.skip); - leak_count += @boolToInt(tr_hdr.flags.leak); - - if (tr_hdr.flags.fail or tr_hdr.flags.leak) { - const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); - const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); - const label = if (tr_hdr.flags.fail) "failed" else "leaked"; - if (msg.len > 0) { - try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); - } else { - try self.step.addError("'{s}' {s}", .{ name, label }); - } - stderr.discard(msg.len); - } - - try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); - }, - else => {}, // ignore other messages - } - stdout.discard(header_and_msg_len); + poll: while (true) { + while (stdout.readableLength() < @sizeOf(Header)) { + if (!(try poller.poll())) break :poll; } + const header = stdout.reader().readStruct(Header) catch unreachable; + while (stdout.readableLength() < header.bytes_len) { + if (!(try poller.poll())) break :poll; + } + const body = stdout.readableSliceOfLen(header.bytes_len); + + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return self.step.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .test_metadata => { + const TmHdr = std.zig.Server.Message.TestMetadata; + const tm_hdr = @ptrCast(*align(1) const TmHdr, body); + test_count = tm_hdr.tests_len; + + const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; + const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; + + const names = std.mem.bytesAsSlice(u32, names_bytes); + const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); + const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); + const names_aligned = try arena.alloc(u32, names.len); + for (names_aligned, names) |*dest, src| dest.* = src; + + const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); + for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; + + const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); + for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; + + prog_node.setEstimatedTotalItems(names.len); + metadata = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .names = names_aligned, + .async_frame_lens = async_frame_lens_aligned, + .expected_panic_msgs = expected_panic_msgs_aligned, + .next_index = 0, + .prog_node = prog_node, + }; + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + .test_results => { + const md = metadata.?; + + const TrHdr = std.zig.Server.Message.TestResults; + const tr_hdr = @ptrCast(*align(1) const TrHdr, body); + fail_count += @boolToInt(tr_hdr.flags.fail); + skip_count += @boolToInt(tr_hdr.flags.skip); + leak_count += @boolToInt(tr_hdr.flags.leak); + + if (tr_hdr.flags.fail or tr_hdr.flags.leak) { + const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); + const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); + const label = if (tr_hdr.flags.fail) "failed" else "leaked"; + if (msg.len > 0) { + try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); + } else { + try self.step.addError("'{s}' {s}", .{ name, label }); + } + stderr.discard(msg.len); + } + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + else => {}, // ignore other messages + } + + stdout.discard(body.len); } if (stderr.readableLength() > 0) { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 123122a74b..88580a6cbc 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -321,56 +321,57 @@ pub fn evalZigProcess( const stdout = poller.fifo(.stdout); - poll: while (try poller.poll()) { - while (true) { - const buf = stdout.readableSlice(0); - assert(stdout.readableLength() == buf.len); - if (buf.len < @sizeOf(Header)) continue :poll; - const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]); - const header_and_msg_len = header.bytes_len + @sizeOf(Header); - if (buf.len < header_and_msg_len) continue :poll; - const body = buf[@sizeOf(Header)..][0..header.bytes_len]; - switch (header.tag) { - .zig_version => { - if (!std.mem.eql(u8, builtin.zig_version_string, body)) { - return s.fail( - "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", - .{ builtin.zig_version_string, body }, - ); - } - }, - .error_bundle => { - const EbHdr = std.zig.Server.Message.ErrorBundle; - const eb_hdr = @ptrCast(*align(1) const EbHdr, body); - const extra_bytes = - body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; - const string_bytes = - body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; - // TODO: use @ptrCast when the compiler supports it - const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); - const extra_array = try arena.alloc(u32, unaligned_extra.len); - // TODO: use @memcpy when it supports slices - for (extra_array, unaligned_extra) |*dst, src| dst.* = src; - s.result_error_bundle = .{ - .string_bytes = try arena.dupe(u8, string_bytes), - .extra = extra_array, - }; - }, - .progress => { - node_name.clearRetainingCapacity(); - try node_name.appendSlice(gpa, body); - sub_prog_node.setName(node_name.items); - }, - .emit_bin_path => { - const EbpHdr = std.zig.Server.Message.EmitBinPath; - const ebp_hdr = @ptrCast(*align(1) const EbpHdr, body); - s.result_cached = ebp_hdr.flags.cache_hit; - result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); - }, - else => {}, // ignore other messages - } - stdout.discard(header_and_msg_len); + poll: while (true) { + while (stdout.readableLength() < @sizeOf(Header)) { + if (!(try poller.poll())) break :poll; } + const header = stdout.reader().readStruct(Header) catch unreachable; + while (stdout.readableLength() < header.bytes_len) { + if (!(try poller.poll())) break :poll; + } + const body = stdout.readableSliceOfLen(header.bytes_len); + + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return s.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .error_bundle => { + const EbHdr = std.zig.Server.Message.ErrorBundle; + const eb_hdr = @ptrCast(*align(1) const EbHdr, body); + const extra_bytes = + body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + // TODO: use @ptrCast when the compiler supports it + const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try arena.alloc(u32, unaligned_extra.len); + // TODO: use @memcpy when it supports slices + for (extra_array, unaligned_extra) |*dst, src| dst.* = src; + s.result_error_bundle = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .extra = extra_array, + }; + }, + .progress => { + node_name.clearRetainingCapacity(); + try node_name.appendSlice(gpa, body); + sub_prog_node.setName(node_name.items); + }, + .emit_bin_path => { + const EbpHdr = std.zig.Server.Message.EmitBinPath; + const ebp_hdr = @ptrCast(*align(1) const EbpHdr, body); + s.result_cached = ebp_hdr.flags.cache_hit; + result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); + }, + else => {}, // ignore other messages + } + + stdout.discard(body.len); } const stderr = poller.fifo(.stderr); From a1058dd27bd32fed2fba14732b8a6c7009fd1116 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 20:22:31 -0700 Subject: [PATCH 283/294] fix std.fs unit test to not be racey --- lib/std/fs/test.zig | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 16458d7dc4..1fbd7dbd63 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1124,17 +1124,31 @@ test "open file with exclusive lock twice, make sure second lock waits" { test "open file with exclusive nonblocking lock twice (absolute paths)" { if (builtin.os.tag == .wasi) return error.SkipZigTest; - const allocator = testing.allocator; + var random_bytes: [12]u8 = undefined; + std.crypto.random.bytes(&random_bytes); - const cwd = try std.process.getCwdAlloc(allocator); - defer allocator.free(cwd); - const file_paths: [2][]const u8 = .{ cwd, "zig-test-absolute-paths.txt" }; - const filename = try fs.path.resolve(allocator, &file_paths); - defer allocator.free(filename); + var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; + _ = fs.base64_encoder.encode(&random_b64, &random_bytes); - const file1 = try fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); + const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt"; - const file2 = fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); + const gpa = testing.allocator; + + const cwd = try std.process.getCwdAlloc(gpa); + defer gpa.free(cwd); + + const filename = try fs.path.resolve(gpa, &[_][]const u8{ cwd, sub_path }); + defer gpa.free(filename); + + const file1 = try fs.createFileAbsolute(filename, .{ + .lock = .Exclusive, + .lock_nonblocking = true, + }); + + const file2 = fs.createFileAbsolute(filename, .{ + .lock = .Exclusive, + .lock_nonblocking = true, + }); file1.close(); try testing.expectError(error.WouldBlock, file2); From 1a70ea0576ce9bbdfb02f4cbd8eb42efb099d71c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 20:24:46 -0700 Subject: [PATCH 284/294] windows_spawn standalone test: test on native OS In master branch this test tests native Windows. In this branch, I accidentally made aarch64-windows test x86_64-windows which caused some subtle behavior that we aren't ready to add test coverage for yet. --- test/standalone/windows_spawn/build.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/standalone/windows_spawn/build.zig b/test/standalone/windows_spawn/build.zig index 8cc6e18599..6c865f0a9f 100644 --- a/test/standalone/windows_spawn/build.zig +++ b/test/standalone/windows_spawn/build.zig @@ -6,10 +6,9 @@ pub fn build(b: *std.Build) void { b.default_step = test_step; const optimize: std.builtin.OptimizeMode = .Debug; - const target: std.zig.CrossTarget = .{ - .os_tag = .windows, - .cpu_arch = .x86_64, - }; + const target: std.zig.CrossTarget = .{}; + + if (builtin.os.tag != .windows) return; const hello = b.addExecutable(.{ .name = "hello", From 21b544a90a45cbac02c226ff7082241c2237ffdf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 21:02:30 -0700 Subject: [PATCH 285/294] fix compile log test case expected output --- .../compile_errors/compile_log_a_pointer_to_an_opaque_value.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig b/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig index 0162860525..73de52fc97 100644 --- a/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig +++ b/test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig @@ -1,5 +1,5 @@ export fn entry() void { - @compileLog(@ptrCast(*align(1) const anyopaque, &entry)); + @compileLog(@as(*align(1) const anyopaque, @ptrCast(*const anyopaque, &entry))); } // error From 5c6adbeb3931c209c651239cffb60831bcf02949 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Mar 2023 21:02:52 -0700 Subject: [PATCH 286/294] test-c-abi: disable LTO on more targets --- test/tests.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests.zig b/test/tests.zig index af0fcea2d5..a9da9b41c7 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1043,7 +1043,7 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S // test-c-abi should test both with LTO on and with LTO off. Only // some combinations are passing currently: // https://github.com/ziglang/zig/issues/14908 - if (c_abi_target.isWindows() and (c_abi_target.getCpuArch() == .x86 or builtin.target.os.tag == .linux)) { + if (c_abi_target.isWindows()) { test_step.want_lto = false; } From cdda39559020b8d2c14a49d670738a3d265b496f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Mar 2023 10:32:32 -0700 Subject: [PATCH 287/294] std lib tests: avoid cwd races by using std.testing.tmpDir --- lib/std/os/linux/io_uring.zig | 137 ++++++++++++++++++---------------- lib/std/os/linux/test.zig | 33 ++++---- 2 files changed, 86 insertions(+), 84 deletions(-) diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig index a302f2cdf1..4dbf87c501 100644 --- a/lib/std/os/linux/io_uring.zig +++ b/lib/std/os/linux/io_uring.zig @@ -1728,10 +1728,12 @@ test "writev/fsync/readv" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_writev_fsync_readv"; - const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true }); + const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); defer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; const fd = file.handle; const buffer_write = [_]u8{42} ** 128; @@ -1796,10 +1798,11 @@ test "write/read" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); const path = "test_io_uring_write_read"; - const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true }); + const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); defer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; const fd = file.handle; const buffer_write = [_]u8{97} ** 20; @@ -1842,10 +1845,12 @@ test "write_fixed/read_fixed" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_write_read_fixed"; - const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true }); + const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true }); defer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; const fd = file.handle; var raw_buffers: [2][11]u8 = undefined; @@ -1899,8 +1904,10 @@ test "openat" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_openat"; - defer std.fs.cwd().deleteFile(path) catch {}; // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: { @@ -1910,12 +1917,12 @@ test "openat" { const flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.CREAT; const mode: os.mode_t = 0o666; - const sqe_openat = try ring.openat(0x33333333, linux.AT.FDCWD, path, flags, mode); + const sqe_openat = try ring.openat(0x33333333, tmp.dir.fd, path, flags, mode); try testing.expectEqual(linux.io_uring_sqe{ .opcode = .OPENAT, .flags = 0, .ioprio = 0, - .fd = linux.AT.FDCWD, + .fd = tmp.dir.fd, .off = 0, .addr = path_addr, .len = mode, @@ -1931,12 +1938,6 @@ test "openat" { const cqe_openat = try ring.copy_cqe(); try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data); if (cqe_openat.err() == .INVAL) return error.SkipZigTest; - // AT.FDCWD is not fully supported before kernel 5.6: - // See https://lore.kernel.org/io-uring/20200207155039.12819-1-axboe@kernel.dk/T/ - // We use IORING_FEAT_RW_CUR_POS to know if we are pre-5.6 since that feature was added in 5.6. - if (cqe_openat.err() == .BADF and (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0) { - return error.SkipZigTest; - } if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res}); try testing.expect(cqe_openat.res > 0); try testing.expectEqual(@as(u32, 0), cqe_openat.flags); @@ -1954,10 +1955,12 @@ test "close" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_close"; - const file = try std.fs.cwd().createFile(path, .{}); + const file = try tmp.dir.createFile(path, .{}); errdefer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; const sqe_close = try ring.close(0x44444444, file.handle); try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode); @@ -2295,10 +2298,12 @@ test "fallocate" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_fallocate"; - const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); defer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; try testing.expectEqual(@as(u64, 0), (try file.stat()).size); @@ -2339,10 +2344,11 @@ test "statx" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); const path = "test_io_uring_statx"; - const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); defer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; try testing.expectEqual(@as(u64, 0), (try file.stat()).size); @@ -2351,14 +2357,14 @@ test "statx" { var buf: linux.Statx = undefined; const sqe = try ring.statx( 0xaaaaaaaa, - linux.AT.FDCWD, + tmp.dir.fd, path, 0, linux.STATX_SIZE, &buf, ); try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -2371,8 +2377,6 @@ test "statx" { // The filesystem containing the file referred to by fd does not support this operation; // or the mode is not supported by the filesystem containing the file referred to by fd: .OPNOTSUPP => return error.SkipZigTest, - // The kernel is too old to support FDCWD for dir_fd - .BADF => return error.SkipZigTest, else => |errno| std.debug.panic("unhandled errno: {}", .{errno}), } try testing.expectEqual(linux.io_uring_cqe{ @@ -2606,28 +2610,28 @@ test "renameat" { const old_path = "test_io_uring_renameat_old"; const new_path = "test_io_uring_renameat_new"; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + // Write old file with data - const old_file = try std.fs.cwd().createFile(old_path, .{ .truncate = true, .mode = 0o666 }); - defer { - old_file.close(); - std.fs.cwd().deleteFile(new_path) catch {}; - } + const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 }); + defer old_file.close(); try old_file.writeAll("hello"); // Submit renameat var sqe = try ring.renameat( 0x12121212, - linux.AT.FDCWD, + tmp.dir.fd, old_path, - linux.AT.FDCWD, + tmp.dir.fd, new_path, 0, ); try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len)); + try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len)); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -2645,7 +2649,7 @@ test "renameat" { // Validate that the old file doesn't exist anymore { - _ = std.fs.cwd().openFile(old_path, .{}) catch |err| switch (err) { + _ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) { error.FileNotFound => {}, else => std.debug.panic("unexpected error: {}", .{err}), }; @@ -2653,7 +2657,7 @@ test "renameat" { // Validate that the new file exists with the proper content { - const new_file = try std.fs.cwd().openFile(new_path, .{}); + const new_file = try tmp.dir.openFile(new_path, .{}); defer new_file.close(); var new_file_data: [16]u8 = undefined; @@ -2674,22 +2678,24 @@ test "unlinkat" { const path = "test_io_uring_unlinkat"; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + // Write old file with data - const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); defer file.close(); - defer std.fs.cwd().deleteFile(path) catch {}; // Submit unlinkat var sqe = try ring.unlinkat( 0x12121212, - linux.AT.FDCWD, + tmp.dir.fd, path, 0, ); try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -2706,7 +2712,7 @@ test "unlinkat" { }, cqe); // Validate that the file doesn't exist anymore - _ = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { + _ = tmp.dir.openFile(path, .{}) catch |err| switch (err) { error.FileNotFound => {}, else => std.debug.panic("unexpected error: {}", .{err}), }; @@ -2722,20 +2728,21 @@ test "mkdirat" { }; defer ring.deinit(); - const path = "test_io_uring_mkdirat"; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); - defer std.fs.cwd().deleteDir(path) catch {}; + const path = "test_io_uring_mkdirat"; // Submit mkdirat var sqe = try ring.mkdirat( 0x12121212, - linux.AT.FDCWD, + tmp.dir.fd, path, 0o0755, ); try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -2752,7 +2759,7 @@ test "mkdirat" { }, cqe); // Validate that the directory exist - _ = try std.fs.cwd().openDir(path, .{}); + _ = try tmp.dir.openDir(path, .{}); } test "symlinkat" { @@ -2765,26 +2772,25 @@ test "symlinkat" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_io_uring_symlinkat"; const link_path = "test_io_uring_symlinkat_link"; - const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); - defer { - file.close(); - std.fs.cwd().deleteFile(path) catch {}; - std.fs.cwd().deleteFile(link_path) catch {}; - } + const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); + defer file.close(); // Submit symlinkat var sqe = try ring.symlinkat( 0x12121212, path, - linux.AT.FDCWD, + tmp.dir.fd, link_path, ); try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -2801,7 +2807,7 @@ test "symlinkat" { }, cqe); // Validate that the symlink exist - _ = try std.fs.cwd().openFile(link_path, .{}); + _ = try tmp.dir.openFile(link_path, .{}); } test "linkat" { @@ -2814,32 +2820,31 @@ test "linkat" { }; defer ring.deinit(); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const first_path = "test_io_uring_linkat_first"; const second_path = "test_io_uring_linkat_second"; // Write file with data - const first_file = try std.fs.cwd().createFile(first_path, .{ .truncate = true, .mode = 0o666 }); - defer { - first_file.close(); - std.fs.cwd().deleteFile(first_path) catch {}; - std.fs.cwd().deleteFile(second_path) catch {}; - } + const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 }); + defer first_file.close(); try first_file.writeAll("hello"); // Submit linkat var sqe = try ring.linkat( 0x12121212, - linux.AT.FDCWD, + tmp.dir.fd, first_path, - linux.AT.FDCWD, + tmp.dir.fd, second_path, 0, ); try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd); - try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len)); + try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd); + try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len)); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); @@ -2856,7 +2861,7 @@ test "linkat" { }, cqe); // Validate the second file - const second_file = try std.fs.cwd().openFile(second_path, .{}); + const second_file = try tmp.dir.openFile(second_path, .{}); defer second_file.close(); var second_file_data: [16]u8 = undefined; diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 63deab3edc..e1ad36b2e5 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -8,10 +8,12 @@ const expectEqual = std.testing.expectEqual; const fs = std.fs; test "fallocate" { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const path = "test_fallocate"; - const file = try fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 }); + const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 }); defer file.close(); - defer fs.cwd().deleteFile(path) catch {}; try expect((try file.stat()).size == 0); @@ -67,12 +69,12 @@ test "timer" { } test "statx" { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const tmp_file_name = "just_a_temporary_file.txt"; - var file = try fs.cwd().createFile(tmp_file_name, .{}); - defer { - file.close(); - fs.cwd().deleteFile(tmp_file_name) catch {}; - } + var file = try tmp.dir.createFile(tmp_file_name, .{}); + defer file.close(); var statx_buf: linux.Statx = undefined; switch (linux.getErrno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) { @@ -105,21 +107,16 @@ test "user and group ids" { } test "fadvise" { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const tmp_file_name = "temp_posix_fadvise.txt"; - var file = try fs.cwd().createFile(tmp_file_name, .{}); - defer { - file.close(); - fs.cwd().deleteFile(tmp_file_name) catch {}; - } + var file = try tmp.dir.createFile(tmp_file_name, .{}); + defer file.close(); var buf: [2048]u8 = undefined; try file.writeAll(&buf); - const ret = linux.fadvise( - file.handle, - 0, - 0, - linux.POSIX_FADV.SEQUENTIAL, - ); + const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL); try expectEqual(@as(usize, 0), ret); } From 1f59994a37afcfa5909440a05bcff5990b3a4651 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Mar 2023 10:47:36 -0700 Subject: [PATCH 288/294] C ABI tests: don't test aarch64-windows yet because it is not passing. See tracking issue #14908 --- test/tests.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/tests.zig b/test/tests.zig index a9da9b41c7..3166fbc14a 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1027,6 +1027,11 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S for (c_abi_targets) |c_abi_target| { if (skip_non_native and !c_abi_target.isNative()) continue; + if (c_abi_target.isWindows() and c_abi_target.getCpuArch() == .aarch64) { + // https://github.com/ziglang/zig/issues/14908 + continue; + } + const test_step = b.addTest(.{ .root_source_file = .{ .path = "test/c_abi/main.zig" }, .optimize = optimize_mode, From 7177b3994626114e57bf8df36ca84fd942bac282 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Mar 2023 12:32:17 -0700 Subject: [PATCH 289/294] fix test-case copy-paste typo from earlier commit commit 3204d00a5e7fe119b690e921138a439fb84dff5b intended to move this passing test case from stage1 folder but it was accidentally changed to have identical contents as a different test case instead. Fortunately, the test case has not regressed, so I simply replaced it with the intended test from before. --- .../undefined_as_field_type_is_rejected.zig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/cases/compile_errors/undefined_as_field_type_is_rejected.zig b/test/cases/compile_errors/undefined_as_field_type_is_rejected.zig index e78cadc878..b6eb059661 100644 --- a/test/cases/compile_errors/undefined_as_field_type_is_rejected.zig +++ b/test/cases/compile_errors/undefined_as_field_type_is_rejected.zig @@ -1,9 +1,13 @@ -export fn a() void { - b(); +const Foo = struct { + a: undefined, +}; +export fn entry1() void { + const foo: Foo = undefined; + _ = foo; } // error -// backend=stage2 +// backend=stage1 // target=native // -// :2:5: error: use of undeclared identifier 'b' +// tmp.zig:2:8: error: use of undefined value here causes undefined behavior From e1e414e62a86cc460ef215ea8050c953b68b6080 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 15 Mar 2023 10:12:48 +0100 Subject: [PATCH 290/294] std: move os/darwin.zig and related to c/darwin.zig Move to c/darwin.zig as they really are libSystem/libc imports/wrappers. As an added bonus, get rid of the nasty `usingnamespace`s which are now unneeded. Finally, add `os.ptrace` but currently only implemented on darwin. --- CMakeLists.txt | 1 - lib/std/c/darwin.zig | 477 ++++++++++++++++++++++++++ lib/std/{os => c}/darwin/cssm.zig | 0 lib/std/os.zig | 25 +- lib/std/os/darwin.zig | 540 ------------------------------ lib/std/os/ptrace.zig | 28 -- 6 files changed, 500 insertions(+), 571 deletions(-) rename lib/std/{os => c}/darwin/cssm.zig (100%) delete mode 100644 lib/std/os/darwin.zig delete mode 100644 lib/std/os/ptrace.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index b0867c220b..3f5cf7cd6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,7 +296,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig" "${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig" "${CMAKE_SOURCE_DIR}/lib/std/os.zig" - "${CMAKE_SOURCE_DIR}/lib/std/os/darwin.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig" diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 9c5ac1e93a..75267cc171 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -8,6 +8,7 @@ const iovec_const = std.os.iovec_const; pub const aarch64 = @import("darwin/aarch64.zig"); pub const x86_64 = @import("darwin/x86_64.zig"); +pub const cssm = @import("darwin/cssm.zig"); const arch_bits = switch (native_arch) { .aarch64 => @import("darwin/aarch64.zig"), @@ -2179,6 +2180,14 @@ pub fn getKernError(err: kern_return_t) KernE { return @intToEnum(KernE, @truncate(u32, @intCast(usize, err))); } +pub fn unexpectedKernError(err: KernE) std.os.UnexpectedError { + if (std.os.unexpected_error_tracing) { + std.debug.print("unexpected errno: {d}\n", .{@enumToInt(err)}); + std.debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + /// Kernel return values pub const KernE = enum(u32) { SUCCESS = 0, @@ -3085,3 +3094,471 @@ pub const PT_DENY_ATTACH = 31; pub const caddr_t = ?[*]u8; pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int) c_int; + +pub const MachError = error{ + /// Not enough permissions held to perform the requested kernel + /// call. + PermissionDenied, +} || std.os.UnexpectedError; + +pub const MachTask = extern struct { + port: mach_port_name_t, + + pub fn isValid(self: MachTask) bool { + return self.port != TASK_NULL; + } + + pub fn pidForTask(self: MachTask) MachError!std.os.pid_t { + var pid: std.os.pid_t = undefined; + switch (getKernError(pid_for_task(self.port, &pid))) { + .SUCCESS => return pid, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn allocatePort(self: MachTask, right: MACH_PORT_RIGHT) MachError!MachTask { + var out_port: mach_port_name_t = undefined; + switch (getKernError(mach_port_allocate( + self.port, + @enumToInt(right), + &out_port, + ))) { + .SUCCESS => return .{ .port = out_port }, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn deallocatePort(self: MachTask, port: MachTask) void { + _ = getKernError(mach_port_deallocate(self.port, port.port)); + } + + pub fn insertRight(self: MachTask, port: MachTask, msg: MACH_MSG_TYPE) !void { + switch (getKernError(mach_port_insert_right( + self.port, + port.port, + port.port, + @enumToInt(msg), + ))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub const PortInfo = struct { + mask: exception_mask_t, + masks: [EXC_TYPES_COUNT]exception_mask_t, + ports: [EXC_TYPES_COUNT]mach_port_t, + behaviors: [EXC_TYPES_COUNT]exception_behavior_t, + flavors: [EXC_TYPES_COUNT]thread_state_flavor_t, + count: mach_msg_type_number_t, + }; + + pub fn getExceptionPorts(self: MachTask, mask: exception_mask_t) !PortInfo { + var info = PortInfo{ + .mask = mask, + .masks = undefined, + .ports = undefined, + .behaviors = undefined, + .flavors = undefined, + .count = 0, + }; + info.count = info.ports.len / @sizeOf(mach_port_t); + + switch (getKernError(task_get_exception_ports( + self.port, + info.mask, + &info.masks, + &info.count, + &info.ports, + &info.behaviors, + &info.flavors, + ))) { + .SUCCESS => return info, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn setExceptionPorts( + self: MachTask, + mask: exception_mask_t, + new_port: MachTask, + behavior: exception_behavior_t, + new_flavor: thread_state_flavor_t, + ) !void { + switch (getKernError(task_set_exception_ports( + self.port, + mask, + new_port.port, + behavior, + new_flavor, + ))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub const RegionInfo = struct { + pub const Tag = enum { + basic, + extended, + top, + }; + + base_addr: u64, + tag: Tag, + info: union { + basic: vm_region_basic_info_64, + extended: vm_region_extended_info, + top: vm_region_top_info, + }, + }; + + pub fn getRegionInfo( + task: MachTask, + address: u64, + len: usize, + tag: RegionInfo.Tag, + ) MachError!RegionInfo { + var info: RegionInfo = .{ + .base_addr = address, + .tag = tag, + .info = undefined, + }; + switch (tag) { + .basic => info.info = .{ .basic = undefined }, + .extended => info.info = .{ .extended = undefined }, + .top => info.info = .{ .top = undefined }, + } + var base_len: mach_vm_size_t = if (len == 1) 2 else len; + var objname: mach_port_t = undefined; + var count: mach_msg_type_number_t = switch (tag) { + .basic => VM_REGION_BASIC_INFO_COUNT, + .extended => VM_REGION_EXTENDED_INFO_COUNT, + .top => VM_REGION_TOP_INFO_COUNT, + }; + switch (getKernError(mach_vm_region( + task.port, + &info.base_addr, + &base_len, + switch (tag) { + .basic => VM_REGION_BASIC_INFO_64, + .extended => VM_REGION_EXTENDED_INFO, + .top => VM_REGION_TOP_INFO, + }, + switch (tag) { + .basic => @ptrCast(vm_region_info_t, &info.info.basic), + .extended => @ptrCast(vm_region_info_t, &info.info.extended), + .top => @ptrCast(vm_region_info_t, &info.info.top), + }, + &count, + &objname, + ))) { + .SUCCESS => return info, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub const RegionSubmapInfo = struct { + pub const Tag = enum { + short, + full, + }; + + tag: Tag, + base_addr: u64, + info: union { + short: vm_region_submap_short_info_64, + full: vm_region_submap_info_64, + }, + }; + + pub fn getRegionSubmapInfo( + task: MachTask, + address: u64, + len: usize, + nesting_depth: u32, + tag: RegionSubmapInfo.Tag, + ) MachError!RegionSubmapInfo { + var info: RegionSubmapInfo = .{ + .base_addr = address, + .tag = tag, + .info = undefined, + }; + switch (tag) { + .short => info.info = .{ .short = undefined }, + .full => info.info = .{ .full = undefined }, + } + var nesting = nesting_depth; + var base_len: mach_vm_size_t = if (len == 1) 2 else len; + var count: mach_msg_type_number_t = switch (tag) { + .short => VM_REGION_SUBMAP_SHORT_INFO_COUNT_64, + .full => VM_REGION_SUBMAP_INFO_COUNT_64, + }; + switch (getKernError(mach_vm_region_recurse( + task.port, + &info.base_addr, + &base_len, + &nesting, + switch (tag) { + .short => @ptrCast(vm_region_recurse_info_t, &info.info.short), + .full => @ptrCast(vm_region_recurse_info_t, &info.info.full), + }, + &count, + ))) { + .SUCCESS => return info, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!vm_prot_t { + const info = try task.getRegionSubmapInfo(address, len, 0, .short); + return info.info.short.protection; + } + + pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: vm_prot_t) MachError!void { + return task.setProtectionImpl(address, len, true, prot); + } + + pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: vm_prot_t) MachError!void { + return task.setProtectionImpl(address, len, false, prot); + } + + fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: vm_prot_t) MachError!void { + switch (getKernError(mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + /// Will write to VM even if current protection attributes specifically prohibit + /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY + /// variant, and resetting after a successful or unsuccessful write. + pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { + const curr_prot = try task.getCurrProtection(address, buf.len); + try task.setCurrProtection( + address, + buf.len, + PROT.READ | PROT.WRITE | PROT.COPY, + ); + defer { + task.setCurrProtection(address, buf.len, curr_prot) catch {}; + } + return task.writeMem(address, buf, arch); + } + + pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { + const count = buf.len; + var total_written: usize = 0; + var curr_addr = address; + const page_size = try getPageSize(task); // TODO we probably can assume value here + var out_buf = buf[0..]; + + while (total_written < count) { + const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); + switch (getKernError(mach_vm_write( + task.port, + curr_addr, + @ptrToInt(out_buf.ptr), + @intCast(mach_msg_type_number_t, curr_size), + ))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + + switch (arch) { + .aarch64 => { + var mattr_value: vm_machine_attribute_val_t = MATTR_VAL_CACHE_FLUSH; + switch (getKernError(vm_machine_attribute( + task.port, + curr_addr, + curr_size, + MATTR_CACHE, + &mattr_value, + ))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + }, + .x86_64 => {}, + else => unreachable, + } + + out_buf = out_buf[curr_size..]; + total_written += curr_size; + curr_addr += curr_size; + } + + return total_written; + } + + pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { + const count = buf.len; + var total_read: usize = 0; + var curr_addr = address; + const page_size = try getPageSize(task); // TODO we probably can assume value here + var out_buf = buf[0..]; + + while (total_read < count) { + const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); + var curr_bytes_read: mach_msg_type_number_t = 0; + var vm_memory: vm_offset_t = undefined; + switch (getKernError(mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + + @memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read); + _ = vm_deallocate(mach_task_self(), vm_memory, curr_bytes_read); + + out_buf = out_buf[curr_bytes_read..]; + curr_addr += curr_bytes_read; + total_read += curr_bytes_read; + } + + return total_read; + } + + fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { + var left = count; + if (page_size > 0) { + const page_offset = address % page_size; + const bytes_left_in_page = page_size - page_offset; + if (count > bytes_left_in_page) { + left = bytes_left_in_page; + } + } + return left; + } + + fn getPageSize(task: MachTask) MachError!usize { + if (task.isValid()) { + var info_count = TASK_VM_INFO_COUNT; + var vm_info: task_vm_info_data_t = undefined; + switch (getKernError(task_info( + task.port, + TASK_VM_INFO, + @ptrCast(task_info_t, &vm_info), + &info_count, + ))) { + .SUCCESS => return @intCast(usize, vm_info.page_size), + else => {}, + } + } + var page_size: vm_size_t = undefined; + switch (getKernError(_host_page_size(mach_host_self(), &page_size))) { + .SUCCESS => return page_size, + else => |err| return unexpectedKernError(err), + } + } + + pub fn basicTaskInfo(task: MachTask) MachError!mach_task_basic_info { + var info: mach_task_basic_info = undefined; + var count = MACH_TASK_BASIC_INFO_COUNT; + switch (getKernError(task_info( + task.port, + MACH_TASK_BASIC_INFO, + @ptrCast(task_info_t, &info), + &count, + ))) { + .SUCCESS => return info, + else => |err| return unexpectedKernError(err), + } + } + + pub fn @"resume"(task: MachTask) MachError!void { + switch (getKernError(task_resume(task.port))) { + .SUCCESS => {}, + else => |err| return unexpectedKernError(err), + } + } + + pub fn @"suspend"(task: MachTask) MachError!void { + switch (getKernError(task_suspend(task.port))) { + .SUCCESS => {}, + else => |err| return unexpectedKernError(err), + } + } + + const ThreadList = struct { + buf: []MachThread, + + pub fn deinit(list: ThreadList) void { + const self_task = machTaskForSelf(); + _ = vm_deallocate( + self_task.port, + @ptrToInt(list.buf.ptr), + @intCast(vm_size_t, list.buf.len * @sizeOf(mach_port_t)), + ); + } + }; + + pub fn getThreads(task: MachTask) MachError!ThreadList { + var thread_list: mach_port_array_t = undefined; + var thread_count: mach_msg_type_number_t = undefined; + switch (getKernError(task_threads(task.port, &thread_list, &thread_count))) { + .SUCCESS => return ThreadList{ .buf = @ptrCast([*]MachThread, thread_list)[0..thread_count] }, + else => |err| return unexpectedKernError(err), + } + } +}; + +pub const MachThread = extern struct { + port: mach_port_t, + + pub fn isValid(thread: MachThread) bool { + return thread.port != THREAD_NULL; + } + + pub fn getBasicInfo(thread: MachThread) MachError!thread_basic_info { + var info: thread_basic_info = undefined; + var count = THREAD_BASIC_INFO_COUNT; + switch (getKernError(thread_info( + thread.port, + THREAD_BASIC_INFO, + @ptrCast(thread_info_t, &info), + &count, + ))) { + .SUCCESS => return info, + else => |err| return unexpectedKernError(err), + } + } + + pub fn getIdentifierInfo(thread: MachThread) MachError!thread_identifier_info { + var info: thread_identifier_info = undefined; + var count = THREAD_IDENTIFIER_INFO_COUNT; + switch (getKernError(thread_info( + thread.port, + THREAD_IDENTIFIER_INFO, + @ptrCast(thread_info_t, &info), + &count, + ))) { + .SUCCESS => return info, + else => |err| return unexpectedKernError(err), + } + } +}; + +pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask { + var port: mach_port_name_t = undefined; + switch (getKernError(task_for_pid(mach_task_self(), pid, &port))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + return MachTask{ .port = port }; +} + +pub fn machTaskForSelf() MachTask { + return .{ .port = mach_task_self() }; +} diff --git a/lib/std/os/darwin/cssm.zig b/lib/std/c/darwin/cssm.zig similarity index 100% rename from lib/std/os/darwin/cssm.zig rename to lib/std/c/darwin/cssm.zig diff --git a/lib/std/os.zig b/lib/std/os.zig index 722028b3f8..77995da034 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -29,7 +29,7 @@ const Allocator = std.mem.Allocator; const Preopen = std.fs.wasi.Preopen; const PreopenList = std.fs.wasi.PreopenList; -pub const darwin = @import("os/darwin.zig"); +pub const darwin = std.c; pub const dragonfly = std.c; pub const freebsd = std.c; pub const haiku = std.c; @@ -41,7 +41,6 @@ pub const plan9 = @import("os/plan9.zig"); pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); pub const windows = @import("os/windows.zig"); -pub const ptrace = @import("os/ptrace.zig"); comptime { assert(@import("std") == std); // std lib tests require --zig-lib-dir @@ -7127,3 +7126,25 @@ pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec { else => |err| return unexpectedErrno(err), }; } + +pub const PtraceError = error{ + DeviceBusy, + ProcessNotFound, + PermissionDenied, +} || UnexpectedError; + +/// TODO on other OSes +pub fn ptrace(request: i32, pid: pid_t, addr: ?[*]u8, signal: i32) PtraceError!void { + switch (builtin.os.tag) { + .macos, .ios, .tvos, .watchos => {}, + else => @compileError("TODO implement ptrace"), + } + return switch (errno(system.ptrace(request, pid, addr, signal))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .INVAL => unreachable, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }; +} diff --git a/lib/std/os/darwin.zig b/lib/std/os/darwin.zig deleted file mode 100644 index 164a0e06c2..0000000000 --- a/lib/std/os/darwin.zig +++ /dev/null @@ -1,540 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const log = std.log; -const mem = std.mem; - -pub const cssm = @import("darwin/cssm.zig"); - -pub usingnamespace std.c; -pub usingnamespace mach_task; - -const mach_task = if (builtin.target.isDarwin()) struct { - pub const MachError = error{ - /// Not enough permissions held to perform the requested kernel - /// call. - PermissionDenied, - /// Kernel returned an unhandled and unexpected error code. - /// This is a catch-all for any yet unobserved kernel response - /// to some Mach message. - Unexpected, - }; - - pub const MachTask = extern struct { - port: std.c.mach_port_name_t, - - pub fn isValid(self: MachTask) bool { - return self.port != std.c.TASK_NULL; - } - - pub fn pidForTask(self: MachTask) MachError!std.os.pid_t { - var pid: std.os.pid_t = undefined; - switch (std.c.getKernError(std.c.pid_for_task(self.port, &pid))) { - .SUCCESS => return pid, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("pid_for_task kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn allocatePort(self: MachTask, right: std.c.MACH_PORT_RIGHT) MachError!MachTask { - var out_port: std.c.mach_port_name_t = undefined; - switch (std.c.getKernError(std.c.mach_port_allocate( - self.port, - @enumToInt(right), - &out_port, - ))) { - .SUCCESS => return .{ .port = out_port }, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_task_allocate kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn deallocatePort(self: MachTask, port: MachTask) void { - _ = std.c.getKernError(std.c.mach_port_deallocate(self.port, port.port)); - } - - pub fn insertRight(self: MachTask, port: MachTask, msg: std.c.MACH_MSG_TYPE) !void { - switch (std.c.getKernError(std.c.mach_port_insert_right( - self.port, - port.port, - port.port, - @enumToInt(msg), - ))) { - .SUCCESS => return, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_port_insert_right kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub const PortInfo = struct { - mask: std.c.exception_mask_t, - masks: [std.c.EXC_TYPES_COUNT]std.c.exception_mask_t, - ports: [std.c.EXC_TYPES_COUNT]std.c.mach_port_t, - behaviors: [std.c.EXC_TYPES_COUNT]std.c.exception_behavior_t, - flavors: [std.c.EXC_TYPES_COUNT]std.c.thread_state_flavor_t, - count: std.c.mach_msg_type_number_t, - }; - - pub fn getExceptionPorts(self: MachTask, mask: std.c.exception_mask_t) !PortInfo { - var info = PortInfo{ - .mask = mask, - .masks = undefined, - .ports = undefined, - .behaviors = undefined, - .flavors = undefined, - .count = 0, - }; - info.count = info.ports.len / @sizeOf(std.c.mach_port_t); - - switch (std.c.getKernError(std.c.task_get_exception_ports( - self.port, - info.mask, - &info.masks, - &info.count, - &info.ports, - &info.behaviors, - &info.flavors, - ))) { - .SUCCESS => return info, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("task_get_exception_ports kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn setExceptionPorts( - self: MachTask, - mask: std.c.exception_mask_t, - new_port: MachTask, - behavior: std.c.exception_behavior_t, - new_flavor: std.c.thread_state_flavor_t, - ) !void { - switch (std.c.getKernError(std.c.task_set_exception_ports( - self.port, - mask, - new_port.port, - behavior, - new_flavor, - ))) { - .SUCCESS => return, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("task_set_exception_ports kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub const RegionInfo = struct { - pub const Tag = enum { - basic, - extended, - top, - }; - - base_addr: u64, - tag: Tag, - info: union { - basic: std.c.vm_region_basic_info_64, - extended: std.c.vm_region_extended_info, - top: std.c.vm_region_top_info, - }, - }; - - pub fn getRegionInfo( - task: MachTask, - address: u64, - len: usize, - tag: RegionInfo.Tag, - ) MachError!RegionInfo { - var info: RegionInfo = .{ - .base_addr = address, - .tag = tag, - .info = undefined, - }; - switch (tag) { - .basic => info.info = .{ .basic = undefined }, - .extended => info.info = .{ .extended = undefined }, - .top => info.info = .{ .top = undefined }, - } - var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; - var objname: std.c.mach_port_t = undefined; - var count: std.c.mach_msg_type_number_t = switch (tag) { - .basic => std.c.VM_REGION_BASIC_INFO_COUNT, - .extended => std.c.VM_REGION_EXTENDED_INFO_COUNT, - .top => std.c.VM_REGION_TOP_INFO_COUNT, - }; - switch (std.c.getKernError(std.c.mach_vm_region( - task.port, - &info.base_addr, - &base_len, - switch (tag) { - .basic => std.c.VM_REGION_BASIC_INFO_64, - .extended => std.c.VM_REGION_EXTENDED_INFO, - .top => std.c.VM_REGION_TOP_INFO, - }, - switch (tag) { - .basic => @ptrCast(std.c.vm_region_info_t, &info.info.basic), - .extended => @ptrCast(std.c.vm_region_info_t, &info.info.extended), - .top => @ptrCast(std.c.vm_region_info_t, &info.info.top), - }, - &count, - &objname, - ))) { - .SUCCESS => return info, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub const RegionSubmapInfo = struct { - pub const Tag = enum { - short, - full, - }; - - tag: Tag, - base_addr: u64, - info: union { - short: std.c.vm_region_submap_short_info_64, - full: std.c.vm_region_submap_info_64, - }, - }; - - pub fn getRegionSubmapInfo( - task: MachTask, - address: u64, - len: usize, - nesting_depth: u32, - tag: RegionSubmapInfo.Tag, - ) MachError!RegionSubmapInfo { - var info: RegionSubmapInfo = .{ - .base_addr = address, - .tag = tag, - .info = undefined, - }; - switch (tag) { - .short => info.info = .{ .short = undefined }, - .full => info.info = .{ .full = undefined }, - } - var nesting = nesting_depth; - var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; - var count: std.c.mach_msg_type_number_t = switch (tag) { - .short => std.c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64, - .full => std.c.VM_REGION_SUBMAP_INFO_COUNT_64, - }; - switch (std.c.getKernError(std.c.mach_vm_region_recurse( - task.port, - &info.base_addr, - &base_len, - &nesting, - switch (tag) { - .short => @ptrCast(std.c.vm_region_recurse_info_t, &info.info.short), - .full => @ptrCast(std.c.vm_region_recurse_info_t, &info.info.full), - }, - &count, - ))) { - .SUCCESS => return info, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t { - const info = try task.getRegionSubmapInfo(address, len, 0, .short); - return info.info.short.protection; - } - - pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { - return task.setProtectionImpl(address, len, true, prot); - } - - pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { - return task.setProtectionImpl(address, len, false, prot); - } - - fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void { - switch (std.c.getKernError(std.c.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) { - .SUCCESS => return, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_vm_protect kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - /// Will write to VM even if current protection attributes specifically prohibit - /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY - /// variant, and resetting after a successful or unsuccessful write. - pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { - const curr_prot = try task.getCurrProtection(address, buf.len); - try task.setCurrProtection( - address, - buf.len, - std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY, - ); - defer { - task.setCurrProtection(address, buf.len, curr_prot) catch {}; - } - return task.writeMem(address, buf, arch); - } - - pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { - const count = buf.len; - var total_written: usize = 0; - var curr_addr = address; - const page_size = try getPageSize(task); // TODO we probably can assume value here - var out_buf = buf[0..]; - - while (total_written < count) { - const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); - switch (std.c.getKernError(std.c.mach_vm_write( - task.port, - curr_addr, - @ptrToInt(out_buf.ptr), - @intCast(std.c.mach_msg_type_number_t, curr_size), - ))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - - switch (arch) { - .aarch64 => { - var mattr_value: std.c.vm_machine_attribute_val_t = std.c.MATTR_VAL_CACHE_FLUSH; - switch (std.c.getKernError(std.c.vm_machine_attribute( - task.port, - curr_addr, - curr_size, - std.c.MATTR_CACHE, - &mattr_value, - ))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("vm_machine_attribute kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - }, - .x86_64 => {}, - else => unreachable, - } - - out_buf = out_buf[curr_size..]; - total_written += curr_size; - curr_addr += curr_size; - } - - return total_written; - } - - pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { - const count = buf.len; - var total_read: usize = 0; - var curr_addr = address; - const page_size = try getPageSize(task); // TODO we probably can assume value here - var out_buf = buf[0..]; - - while (total_read < count) { - const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); - var curr_bytes_read: std.c.mach_msg_type_number_t = 0; - var vm_memory: std.c.vm_offset_t = undefined; - switch (std.c.getKernError(std.c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - - @memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read); - _ = std.c.vm_deallocate(std.c.mach_task_self(), vm_memory, curr_bytes_read); - - out_buf = out_buf[curr_bytes_read..]; - curr_addr += curr_bytes_read; - total_read += curr_bytes_read; - } - - return total_read; - } - - fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { - var left = count; - if (page_size > 0) { - const page_offset = address % page_size; - const bytes_left_in_page = page_size - page_offset; - if (count > bytes_left_in_page) { - left = bytes_left_in_page; - } - } - return left; - } - - fn getPageSize(task: MachTask) MachError!usize { - if (task.isValid()) { - var info_count = std.c.TASK_VM_INFO_COUNT; - var vm_info: std.c.task_vm_info_data_t = undefined; - switch (std.c.getKernError(std.c.task_info( - task.port, - std.c.TASK_VM_INFO, - @ptrCast(std.c.task_info_t, &vm_info), - &info_count, - ))) { - .SUCCESS => return @intCast(usize, vm_info.page_size), - else => {}, - } - } - var page_size: std.c.vm_size_t = undefined; - switch (std.c.getKernError(std.c._host_page_size(std.c.mach_host_self(), &page_size))) { - .SUCCESS => return page_size, - else => |err| { - log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn basicTaskInfo(task: MachTask) MachError!std.c.mach_task_basic_info { - var info: std.c.mach_task_basic_info = undefined; - var count = std.c.MACH_TASK_BASIC_INFO_COUNT; - switch (std.c.getKernError(std.c.task_info( - task.port, - std.c.MACH_TASK_BASIC_INFO, - @ptrCast(std.c.task_info_t, &info), - &count, - ))) { - .SUCCESS => return info, - else => |err| { - log.err("task_info kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn @"resume"(task: MachTask) MachError!void { - switch (std.c.getKernError(std.c.task_resume(task.port))) { - .SUCCESS => {}, - else => |err| { - log.err("task_resume kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn @"suspend"(task: MachTask) MachError!void { - switch (std.c.getKernError(std.c.task_suspend(task.port))) { - .SUCCESS => {}, - else => |err| { - log.err("task_suspend kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - const ThreadList = struct { - buf: []MachThread, - - pub fn deinit(list: ThreadList) void { - const self_task = machTaskForSelf(); - _ = std.c.vm_deallocate( - self_task.port, - @ptrToInt(list.buf.ptr), - @intCast(std.c.vm_size_t, list.buf.len * @sizeOf(std.c.mach_port_t)), - ); - } - }; - - pub fn getThreads(task: MachTask) MachError!ThreadList { - var thread_list: std.c.mach_port_array_t = undefined; - var thread_count: std.c.mach_msg_type_number_t = undefined; - switch (std.c.getKernError(std.c.task_threads(task.port, &thread_list, &thread_count))) { - .SUCCESS => return ThreadList{ .buf = @ptrCast([*]MachThread, thread_list)[0..thread_count] }, - else => |err| { - log.err("task_threads kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - }; - - pub const MachThread = extern struct { - port: std.c.mach_port_t, - - pub fn isValid(thread: MachThread) bool { - return thread.port != std.c.THREAD_NULL; - } - - pub fn getBasicInfo(thread: MachThread) MachError!std.c.thread_basic_info { - var info: std.c.thread_basic_info = undefined; - var count = std.c.THREAD_BASIC_INFO_COUNT; - switch (std.c.getKernError(std.c.thread_info( - thread.port, - std.c.THREAD_BASIC_INFO, - @ptrCast(std.c.thread_info_t, &info), - &count, - ))) { - .SUCCESS => return info, - else => |err| { - log.err("thread_info kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - - pub fn getIdentifierInfo(thread: MachThread) MachError!std.c.thread_identifier_info { - var info: std.c.thread_identifier_info = undefined; - var count = std.c.THREAD_IDENTIFIER_INFO_COUNT; - switch (std.c.getKernError(std.c.thread_info( - thread.port, - std.c.THREAD_IDENTIFIER_INFO, - @ptrCast(std.c.thread_info_t, &info), - &count, - ))) { - .SUCCESS => return info, - else => |err| { - log.err("thread_info kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - } - }; - - pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask { - var port: std.c.mach_port_name_t = undefined; - switch (std.c.getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| { - log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)}); - return error.Unexpected; - }, - } - return MachTask{ .port = port }; - } - - pub fn machTaskForSelf() MachTask { - return .{ .port = std.c.mach_task_self() }; - } -} else struct {}; diff --git a/lib/std/os/ptrace.zig b/lib/std/os/ptrace.zig deleted file mode 100644 index afe0b51e2e..0000000000 --- a/lib/std/os/ptrace.zig +++ /dev/null @@ -1,28 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const os = @import("../os.zig"); -const system = os.system; -const errno = system.getErrno; -const pid_t = system.pid_t; -const unexpectedErrno = os.unexpectedErrno; -const UnexpectedError = os.UnexpectedError; - -pub usingnamespace ptrace; - -const ptrace = if (builtin.target.isDarwin()) struct { - pub const PtraceError = error{ - ProcessNotFound, - PermissionDenied, - } || UnexpectedError; - - pub fn ptrace(request: i32, pid: pid_t, addr: ?[*]u8, signal: i32) PtraceError!void { - switch (errno(system.ptrace(request, pid, addr, signal))) { - .SUCCESS => return, - .SRCH => return error.ProcessNotFound, - .INVAL => unreachable, - .BUSY, .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } - } -} else struct {}; From 9964f1c160acd0bf994708333cd69bc070d6c77e Mon Sep 17 00:00:00 2001 From: InKryption Date: Thu, 16 Mar 2023 01:02:10 +0100 Subject: [PATCH 291/294] Add error for bad cast from `*T` to `*[n]T` Casting `*T` to `*[1]T` should still work, but every other length will now be a compiler error instead of a potential OOB access. --- src/Sema.zig | 1 + ..._implicit_cast_from_T_to_long_array_ptr.zig | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 test/cases/compile_errors/attempted_implicit_cast_from_T_to_long_array_ptr.zig diff --git a/src/Sema.zig b/src/Sema.zig index fc39fbb9fc..8b476d4542 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -24853,6 +24853,7 @@ fn coerceExtra( const array_ty = dest_info.pointee_type; if (array_ty.zigTypeTag() != .Array) break :single_item; const array_elem_ty = array_ty.childType(); + if (array_ty.arrayLen() != 1) break :single_item; const dest_is_mut = dest_info.mutable; switch (try sema.coerceInMemoryAllowed(block, array_elem_ty, ptr_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) { .ok => {}, diff --git a/test/cases/compile_errors/attempted_implicit_cast_from_T_to_long_array_ptr.zig b/test/cases/compile_errors/attempted_implicit_cast_from_T_to_long_array_ptr.zig new file mode 100644 index 0000000000..135081c15c --- /dev/null +++ b/test/cases/compile_errors/attempted_implicit_cast_from_T_to_long_array_ptr.zig @@ -0,0 +1,18 @@ +export fn entry0(single: *u32) void { + _ = @as(*const [0]u32, single); +} +export fn entry1(single: *u32) void { + _ = @as(*const [1]u32, single); +} +export fn entry2(single: *u32) void { + _ = @as(*const [2]u32, single); +} + +// error +// backend=stage2 +// target=native +// +// :2:28: error: expected type '*const [0]u32', found '*u32' +// :2:28: note: pointer type child 'u32' cannot cast into pointer type child '[0]u32' +// :8:28: error: expected type '*const [2]u32', found '*u32' +// :8:28: note: pointer type child 'u32' cannot cast into pointer type child '[2]u32' From da0509750a332806cfddad24b88ae8900782185d Mon Sep 17 00:00:00 2001 From: mllken Date: Tue, 14 Mar 2023 14:50:38 +0700 Subject: [PATCH 292/294] std.os: handle EPERM errno for bind --- lib/std/os.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 77995da034..25cc4e34c4 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -3474,7 +3474,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi const rc = system.bind(sock, addr, len); switch (errno(rc)) { .SUCCESS => return, - .ACCES => return error.AccessDenied, + .ACCES, .PERM => return error.AccessDenied, .ADDRINUSE => return error.AddressInUse, .BADF => unreachable, // always a race condition if this error is returned .INVAL => unreachable, // invalid parameters From b3af5d076c24744bdd100c25eabfea2a1a4688cf Mon Sep 17 00:00:00 2001 From: Evin Yulo Date: Wed, 15 Mar 2023 12:53:53 -0400 Subject: [PATCH 293/294] Fix #14901: parseFloat parsing `0x` successfully --- lib/std/fmt/parse_float.zig | 1 + lib/std/fmt/parse_float/parse.zig | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/std/fmt/parse_float.zig b/lib/std/fmt/parse_float.zig index 427ac727c9..e92564ef01 100644 --- a/lib/std/fmt/parse_float.zig +++ b/lib/std/fmt/parse_float.zig @@ -119,6 +119,7 @@ test "fmt.parseFloat hex.f16" { } test "fmt.parseFloat hex.f32" { + try testing.expectError(error.InvalidCharacter, parseFloat(f32, "0x")); try testing.expectEqual(try parseFloat(f32, "0x1p0"), 1.0); try testing.expectEqual(try parseFloat(f32, "-0x1p-1"), -0.5); try testing.expectEqual(try parseFloat(f32, "0x10p+10"), 16384.0); diff --git a/lib/std/fmt/parse_float/parse.zig b/lib/std/fmt/parse_float/parse.zig index 3b757c7c41..9f6e75b29a 100644 --- a/lib/std/fmt/parse_float/parse.zig +++ b/lib/std/fmt/parse_float/parse.zig @@ -107,6 +107,8 @@ fn parsePartialNumberBase(comptime T: type, stream: *FloatStream, negative: bool tryParseDigits(MantissaT, stream, &mantissa, info.base); var int_end = stream.offsetTrue(); var n_digits = @intCast(isize, stream.offsetTrue()); + // the base being 16 implies a 0x prefix, which shouldn't be included in the digit count + if (info.base == 16) n_digits -= 2; // handle dot with the following digits var exponent: i64 = 0; From b4d58e93ea4d0bbfe674f80d301279d302fe8fc8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Mar 2023 15:43:51 -0700 Subject: [PATCH 294/294] make docgen accept --zig-lib-dir --- build.zig | 3 ++ doc/docgen.zig | 87 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/build.zig b/build.zig index a0c4ca7fb4..303495f6ba 100644 --- a/build.zig +++ b/build.zig @@ -47,6 +47,9 @@ pub fn build(b: *std.Build) !void { const docgen_cmd = b.addRunArtifact(docgen_exe); docgen_cmd.addArgs(&.{ "--zig", b.zig_exe }); + if (b.zig_lib_dir) |p| { + docgen_cmd.addArgs(&.{ "--zig-lib-dir", p }); + } docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" }); const langref_file = docgen_cmd.addOutputFileArg("langref.html"); const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html"); diff --git a/doc/docgen.zig b/doc/docgen.zig index 67163ca427..277316dd37 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -28,10 +28,10 @@ const usage = \\ ; -fn errorf(comptime format: []const u8, args: anytype) noreturn { +fn fatal(comptime format: []const u8, args: anytype) noreturn { const stderr = io.getStdErr().writer(); - stderr.print("error: " ++ format, args) catch {}; + stderr.print("error: " ++ format ++ "\n", args) catch {}; process.exit(1); } @@ -45,6 +45,7 @@ pub fn main() !void { if (!args_it.skip()) @panic("expected self arg"); var zig_exe: []const u8 = "zig"; + var opt_zig_lib_dir: ?[]const u8 = null; var do_code_tests = true; var files = [_][]const u8{ "", "" }; @@ -59,24 +60,29 @@ pub fn main() !void { if (args_it.next()) |param| { zig_exe = param; } else { - errorf("expected parameter after --zig\n", .{}); + fatal("expected parameter after --zig", .{}); + } + } else if (mem.eql(u8, arg, "--zig-lib-dir")) { + if (args_it.next()) |param| { + opt_zig_lib_dir = param; + } else { + fatal("expected parameter after --zig-lib-dir", .{}); } } else if (mem.eql(u8, arg, "--skip-code-tests")) { do_code_tests = false; } else { - errorf("unrecognized option: '{s}'\n", .{arg}); + fatal("unrecognized option: '{s}'", .{arg}); } } else { if (i > 1) { - errorf("too many arguments\n", .{}); + fatal("too many arguments", .{}); } files[i] = arg; i += 1; } } if (i < 2) { - errorf("not enough arguments\n", .{}); - process.exit(1); + fatal("not enough arguments", .{}); } var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only }); @@ -95,7 +101,7 @@ pub fn main() !void { try fs.cwd().makePath(tmp_dir_name); defer fs.cwd().deleteTree(tmp_dir_name) catch {}; - try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, do_code_tests); + try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, opt_zig_lib_dir, do_code_tests); try buffered_writer.flush(); } @@ -1268,6 +1274,7 @@ fn genHtml( toc: *Toc, out: anytype, zig_exe: []const u8, + opt_zig_lib_dir: ?[]const u8, do_code_tests: bool, ) !void { var progress = Progress{ .dont_print_on_dumb = true }; @@ -1278,7 +1285,7 @@ fn genHtml( try env_map.put("ZIG_DEBUG_COLOR", "1"); const host = try std.zig.system.NativeTargetInfo.detect(.{}); - const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe); + const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir); for (toc.nodes) |node| { defer root_node.completeOne(); @@ -1370,6 +1377,9 @@ fn genHtml( "--color", "on", "--enable-cache", tmp_source_file_name, }); + if (opt_zig_lib_dir) |zig_lib_dir| { + try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); + } try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext}); @@ -1512,8 +1522,12 @@ fn genHtml( defer test_args.deinit(); try test_args.appendSlice(&[_][]const u8{ - zig_exe, "test", tmp_source_file_name, + zig_exe, "test", + tmp_source_file_name, }); + if (opt_zig_lib_dir) |zig_lib_dir| { + try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); + } try shell_out.print("$ zig test {s}.zig ", .{code.name}); switch (code.mode) { @@ -1564,12 +1578,13 @@ fn genHtml( defer test_args.deinit(); try test_args.appendSlice(&[_][]const u8{ - zig_exe, - "test", - "--color", - "on", + zig_exe, "test", + "--color", "on", tmp_source_file_name, }); + if (opt_zig_lib_dir) |zig_lib_dir| { + try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); + } try shell_out.print("$ zig test {s}.zig ", .{code.name}); switch (code.mode) { @@ -1624,8 +1639,12 @@ fn genHtml( defer test_args.deinit(); try test_args.appendSlice(&[_][]const u8{ - zig_exe, "test", tmp_source_file_name, + zig_exe, "test", + tmp_source_file_name, }); + if (opt_zig_lib_dir) |zig_lib_dir| { + try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); + } var mode_arg: []const u8 = ""; switch (code.mode) { .Debug => {}, @@ -1684,17 +1703,17 @@ fn genHtml( defer build_args.deinit(); try build_args.appendSlice(&[_][]const u8{ - zig_exe, - "build-obj", + zig_exe, "build-obj", + "--color", "on", + "--name", code.name, tmp_source_file_name, - "--color", - "on", - "--name", - code.name, try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{ tmp_dir_name, fs.path.sep, name_plus_obj_ext, }), }); + if (opt_zig_lib_dir) |zig_lib_dir| { + try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); + } try shell_out.print("$ zig build-obj {s}.zig ", .{code.name}); @@ -1758,13 +1777,15 @@ fn genHtml( defer test_args.deinit(); try test_args.appendSlice(&[_][]const u8{ - zig_exe, - "build-lib", + zig_exe, "build-lib", tmp_source_file_name, try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{ tmp_dir_name, fs.path.sep_str, bin_basename, }), }); + if (opt_zig_lib_dir) |zig_lib_dir| { + try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); + } try shell_out.print("$ zig build-lib {s}.zig ", .{code.name}); switch (code.mode) { @@ -1829,9 +1850,23 @@ fn exec(allocator: Allocator, env_map: *process.EnvMap, args: []const []const u8 return result; } -fn getBuiltinCode(allocator: Allocator, env_map: *process.EnvMap, zig_exe: []const u8) ![]const u8 { - const result = try exec(allocator, env_map, &[_][]const u8{ zig_exe, "build-obj", "--show-builtin" }); - return result.stdout; +fn getBuiltinCode( + allocator: Allocator, + env_map: *process.EnvMap, + zig_exe: []const u8, + opt_zig_lib_dir: ?[]const u8, +) ![]const u8 { + if (opt_zig_lib_dir) |zig_lib_dir| { + const result = try exec(allocator, env_map, &.{ + zig_exe, "build-obj", "--show-builtin", "--zig-lib-dir", zig_lib_dir, + }); + return result.stdout; + } else { + const result = try exec(allocator, env_map, &.{ + zig_exe, "build-obj", "--show-builtin", + }); + return result.stdout; + } } fn dumpArgs(args: []const []const u8) void {