From 1a20b467ea3f480306317a6145d910d8c44a9b48 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Jul 2025 21:04:00 -0700 Subject: [PATCH 01/46] std.zig: update to new I/O API --- lib/std/zig.zig | 58 +++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index ef1f953209..6a64adc547 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -2,6 +2,12 @@ //! source lives here. These APIs are provided as-is and have absolutely no API //! guarantees whatsoever. +const std = @import("std.zig"); +const tokenizer = @import("zig/tokenizer.zig"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Writer = std.Io.Writer; + pub const ErrorBundle = @import("zig/ErrorBundle.zig"); pub const Server = @import("zig/Server.zig"); pub const Client = @import("zig/Client.zig"); @@ -355,11 +361,6 @@ pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![ return buffer.toOwnedSlice(); } -const std = @import("std.zig"); -const tokenizer = @import("zig/tokenizer.zig"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - /// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed. /// /// See also `fmtIdFlags`. @@ -425,7 +426,7 @@ pub const FormatId = struct { }; /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed. - fn render(ctx: FormatId, writer: *std.io.Writer) std.io.Writer.Error!void { + fn render(ctx: FormatId, writer: *Writer) Writer.Error!void { const bytes = ctx.bytes; if (isValidId(bytes) and (ctx.flags.allow_primitive or !std.zig.isPrimitive(bytes)) and @@ -463,7 +464,7 @@ test fmtChar { } /// Print the string as escaped contents of a double quoted string. -pub fn stringEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!void { +pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void { for (bytes) |byte| switch (byte) { '\n' => try w.writeAll("\\n"), '\r' => try w.writeAll("\\r"), @@ -480,7 +481,7 @@ pub fn stringEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!vo } /// Print the string as escaped contents of a single-quoted string. -pub fn charEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!void { +pub fn charEscape(bytes: []const u8, w: *Writer) Writer.Error!void { for (bytes) |byte| switch (byte) { '\n' => try w.writeAll("\\n"), '\r' => try w.writeAll("\\r"), @@ -529,20 +530,18 @@ test isUnderscore { try std.testing.expect(!isUnderscore("\\x5f")); } -pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?usize) ![:0]u8 { - const source_code = input.readToEndAllocOptions( - gpa, - max_src_size, - size_hint, - .of(u8), - 0, - ) catch |err| switch (err) { +pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: usize) ![:0]u8 { + var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty; + defer buffer.deinit(gpa); + + try buffer.ensureUnusedCapacity(gpa, size_hint); + + input.readIntoArrayList(gpa, .limited(max_src_size), .@"2", &buffer) catch |err| switch (err) { error.ConnectionResetByPeer => unreachable, error.ConnectionTimedOut => unreachable, error.NotOpenForReading => unreachable, else => |e| return e, }; - errdefer gpa.free(source_code); // Detect unsupported file types with their Byte Order Mark const unsupported_boms = [_][]const u8{ @@ -551,30 +550,23 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ? "\xfe\xff", // UTF-16 big endian }; for (unsupported_boms) |bom| { - if (std.mem.startsWith(u8, source_code, bom)) { + if (std.mem.startsWith(u8, buffer.items, bom)) { return error.UnsupportedEncoding; } } // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8 - if (std.mem.startsWith(u8, source_code, "\xff\xfe")) { - if (source_code.len % 2 != 0) return error.InvalidEncoding; - // TODO: after wrangle-writer-buffering branch is merged, - // avoid this unnecessary allocation - const aligned_copy = try gpa.alloc(u16, source_code.len / 2); - defer gpa.free(aligned_copy); - @memcpy(std.mem.sliceAsBytes(aligned_copy), source_code); - const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(gpa, aligned_copy) catch |err| switch (err) { + if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) { + if (buffer.items.len % 2 != 0) return error.InvalidEncoding; + return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(buffer.items)) catch |err| switch (err) { error.DanglingSurrogateHalf => error.UnsupportedEncoding, error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding, error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding, else => |e| return e, }; - gpa.free(source_code); - return source_code_utf8; } - return source_code; + return buffer.toOwnedSliceSentinel(gpa, 0); } pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void { @@ -621,7 +613,7 @@ pub fn parseTargetQueryOrReportFatalError( var help_text = std.ArrayList(u8).init(allocator); defer help_text.deinit(); for (diags.arch.?.allCpuModels()) |cpu| { - help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help; + help_text.print(" {s}\n", .{cpu.name}) catch break :help; } std.log.info("available CPUs for architecture '{s}':\n{s}", .{ @tagName(diags.arch.?), help_text.items, @@ -634,7 +626,7 @@ pub fn parseTargetQueryOrReportFatalError( var help_text = std.ArrayList(u8).init(allocator); defer help_text.deinit(); for (diags.arch.?.allFeaturesList()) |feature| { - help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; + help_text.print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; } std.log.info("available CPU features for architecture '{s}':\n{s}", .{ @tagName(diags.arch.?), help_text.items, @@ -647,7 +639,7 @@ pub fn parseTargetQueryOrReportFatalError( var help_text = std.ArrayList(u8).init(allocator); defer help_text.deinit(); inline for (@typeInfo(std.Target.ObjectFormat).@"enum".fields) |field| { - help_text.writer().print(" {s}\n", .{field.name}) catch break :help; + help_text.print(" {s}\n", .{field.name}) catch break :help; } std.log.info("available object formats:\n{s}", .{help_text.items}); } @@ -658,7 +650,7 @@ pub fn parseTargetQueryOrReportFatalError( var help_text = std.ArrayList(u8).init(allocator); defer help_text.deinit(); inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".fields) |field| { - help_text.writer().print(" {s}\n", .{field.name}) catch break :help; + help_text.print(" {s}\n", .{field.name}) catch break :help; } std.log.info("available architectures:\n{s} native\n", .{help_text.items}); } From 6c4a104822679e92e094b166cc11b56b43f84a33 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Jul 2025 21:12:05 -0700 Subject: [PATCH 02/46] std.zig.Ast: update to new I/O API --- lib/std/zig/Ast.zig | 186 +++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 97 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index aad83f1c63..1b86b103c4 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -4,6 +4,16 @@ //! For Zon syntax, the root node is at nodes[0] and contains lhs as the node //! index of the main expression. +const std = @import("../std.zig"); +const assert = std.debug.assert; +const testing = std.testing; +const mem = std.mem; +const Token = std.zig.Token; +const Ast = @This(); +const Allocator = std.mem.Allocator; +const Parse = @import("Parse.zig"); +const Writer = std.Io.Writer; + /// Reference to externally-owned data. source: [:0]const u8, @@ -128,12 +138,6 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void { tree.* = undefined; } -pub const RenderError = error{ - /// Ran out of memory allocating call stack frames to complete rendering, or - /// ran out of memory allocating space in the output buffer. - OutOfMemory, -}; - pub const Mode = enum { zig, zon }; /// Result should be freed with tree.deinit() when there are @@ -199,27 +203,25 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A /// `gpa` is used for allocating the resulting formatted source code. /// Caller owns the returned slice of bytes, allocated with `gpa`. -pub fn render(tree: Ast, gpa: Allocator) RenderError![]u8 { - var buffer = std.ArrayList(u8).init(gpa); - defer buffer.deinit(); - - try tree.renderToArrayList(&buffer, .{}); - return buffer.toOwnedSlice(); +pub fn renderAlloc(tree: Ast, gpa: Allocator) error{OutOfMemory}![]u8 { + var aw: std.io.Writer.Allocating = .init(gpa); + defer aw.deinit(); + render(tree, gpa, &aw.writer, .{}) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + return aw.toOwnedSlice(); } -pub const Fixups = private_render.Fixups; +pub const Render = @import("Ast/Render.zig"); -pub fn renderToArrayList(tree: Ast, buffer: *std.ArrayList(u8), fixups: Fixups) RenderError!void { - return @import("./render.zig").renderTree(buffer, tree, fixups); +pub fn render(tree: Ast, gpa: Allocator, w: *Writer, fixups: Render.Fixups) Render.Error!void { + return Render.tree(gpa, w, tree, fixups); } /// Returns an extra offset for column and byte offset of errors that /// should point after the token in the error message. pub fn errorOffset(tree: Ast, parse_error: Error) u32 { - return if (parse_error.token_is_prev) - @as(u32, @intCast(tree.tokenSlice(parse_error.token).len)) - else - 0; + return if (parse_error.token_is_prev) @intCast(tree.tokenSlice(parse_error.token).len) else 0; } pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location { @@ -318,254 +320,254 @@ pub fn rootDecls(tree: Ast) []const Node.Index { } } -pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { +pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void { switch (parse_error.tag) { .asterisk_after_ptr_deref => { // Note that the token will point at the `.*` but ideally the source // location would point to the `*` after the `.*`. - return stream.writeAll("'.*' cannot be followed by '*'; are you missing a space?"); + return w.writeAll("'.*' cannot be followed by '*'; are you missing a space?"); }, .chained_comparison_operators => { - return stream.writeAll("comparison operators cannot be chained"); + return w.writeAll("comparison operators cannot be chained"); }, .decl_between_fields => { - return stream.writeAll("declarations are not allowed between container fields"); + return w.writeAll("declarations are not allowed between container fields"); }, .expected_block => { - return stream.print("expected block, found '{s}'", .{ + return w.print("expected block, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_block_or_assignment => { - return stream.print("expected block or assignment, found '{s}'", .{ + return w.print("expected block or assignment, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_block_or_expr => { - return stream.print("expected block or expression, found '{s}'", .{ + return w.print("expected block or expression, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_block_or_field => { - return stream.print("expected block or field, found '{s}'", .{ + return w.print("expected block or field, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_container_members => { - return stream.print("expected test, comptime, var decl, or container field, found '{s}'", .{ + return w.print("expected test, comptime, var decl, or container field, found '{s}'", .{ tree.tokenTag(parse_error.token).symbol(), }); }, .expected_expr => { - return stream.print("expected expression, found '{s}'", .{ + return w.print("expected expression, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_expr_or_assignment => { - return stream.print("expected expression or assignment, found '{s}'", .{ + return w.print("expected expression or assignment, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_expr_or_var_decl => { - return stream.print("expected expression or var decl, found '{s}'", .{ + return w.print("expected expression or var decl, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_fn => { - return stream.print("expected function, found '{s}'", .{ + return w.print("expected function, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_inlinable => { - return stream.print("expected 'while' or 'for', found '{s}'", .{ + return w.print("expected 'while' or 'for', found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_labelable => { - return stream.print("expected 'while', 'for', 'inline', or '{{', found '{s}'", .{ + return w.print("expected 'while', 'for', 'inline', or '{{', found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_param_list => { - return stream.print("expected parameter list, found '{s}'", .{ + return w.print("expected parameter list, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_prefix_expr => { - return stream.print("expected prefix expression, found '{s}'", .{ + return w.print("expected prefix expression, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_primary_type_expr => { - return stream.print("expected primary type expression, found '{s}'", .{ + return w.print("expected primary type expression, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_pub_item => { - return stream.writeAll("expected function or variable declaration after pub"); + return w.writeAll("expected function or variable declaration after pub"); }, .expected_return_type => { - return stream.print("expected return type expression, found '{s}'", .{ + return w.print("expected return type expression, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_semi_or_else => { - return stream.writeAll("expected ';' or 'else' after statement"); + return w.writeAll("expected ';' or 'else' after statement"); }, .expected_semi_or_lbrace => { - return stream.writeAll("expected ';' or block after function prototype"); + return w.writeAll("expected ';' or block after function prototype"); }, .expected_statement => { - return stream.print("expected statement, found '{s}'", .{ + return w.print("expected statement, found '{s}'", .{ tree.tokenTag(parse_error.token).symbol(), }); }, .expected_suffix_op => { - return stream.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{ + return w.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_type_expr => { - return stream.print("expected type expression, found '{s}'", .{ + return w.print("expected type expression, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_var_decl => { - return stream.print("expected variable declaration, found '{s}'", .{ + return w.print("expected variable declaration, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_var_decl_or_fn => { - return stream.print("expected variable declaration or function, found '{s}'", .{ + return w.print("expected variable declaration or function, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_loop_payload => { - return stream.print("expected loop payload, found '{s}'", .{ + return w.print("expected loop payload, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .expected_container => { - return stream.print("expected a struct, enum or union, found '{s}'", .{ + return w.print("expected a struct, enum or union, found '{s}'", .{ tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(), }); }, .extern_fn_body => { - return stream.writeAll("extern functions have no body"); + return w.writeAll("extern functions have no body"); }, .extra_addrspace_qualifier => { - return stream.writeAll("extra addrspace qualifier"); + return w.writeAll("extra addrspace qualifier"); }, .extra_align_qualifier => { - return stream.writeAll("extra align qualifier"); + return w.writeAll("extra align qualifier"); }, .extra_allowzero_qualifier => { - return stream.writeAll("extra allowzero qualifier"); + return w.writeAll("extra allowzero qualifier"); }, .extra_const_qualifier => { - return stream.writeAll("extra const qualifier"); + return w.writeAll("extra const qualifier"); }, .extra_volatile_qualifier => { - return stream.writeAll("extra volatile qualifier"); + return w.writeAll("extra volatile qualifier"); }, .ptr_mod_on_array_child_type => { - return stream.print("pointer modifier '{s}' not allowed on array child type", .{ + return w.print("pointer modifier '{s}' not allowed on array child type", .{ tree.tokenTag(parse_error.token).symbol(), }); }, .invalid_bit_range => { - return stream.writeAll("bit range not allowed on slices and arrays"); + return w.writeAll("bit range not allowed on slices and arrays"); }, .same_line_doc_comment => { - return stream.writeAll("same line documentation comment"); + return w.writeAll("same line documentation comment"); }, .unattached_doc_comment => { - return stream.writeAll("unattached documentation comment"); + return w.writeAll("unattached documentation comment"); }, .test_doc_comment => { - return stream.writeAll("documentation comments cannot be attached to tests"); + return w.writeAll("documentation comments cannot be attached to tests"); }, .comptime_doc_comment => { - return stream.writeAll("documentation comments cannot be attached to comptime blocks"); + return w.writeAll("documentation comments cannot be attached to comptime blocks"); }, .varargs_nonfinal => { - return stream.writeAll("function prototype has parameter after varargs"); + return w.writeAll("function prototype has parameter after varargs"); }, .expected_continue_expr => { - return stream.writeAll("expected ':' before while continue expression"); + return w.writeAll("expected ':' before while continue expression"); }, .expected_semi_after_decl => { - return stream.writeAll("expected ';' after declaration"); + return w.writeAll("expected ';' after declaration"); }, .expected_semi_after_stmt => { - return stream.writeAll("expected ';' after statement"); + return w.writeAll("expected ';' after statement"); }, .expected_comma_after_field => { - return stream.writeAll("expected ',' after field"); + return w.writeAll("expected ',' after field"); }, .expected_comma_after_arg => { - return stream.writeAll("expected ',' after argument"); + return w.writeAll("expected ',' after argument"); }, .expected_comma_after_param => { - return stream.writeAll("expected ',' after parameter"); + return w.writeAll("expected ',' after parameter"); }, .expected_comma_after_initializer => { - return stream.writeAll("expected ',' after initializer"); + return w.writeAll("expected ',' after initializer"); }, .expected_comma_after_switch_prong => { - return stream.writeAll("expected ',' after switch prong"); + return w.writeAll("expected ',' after switch prong"); }, .expected_comma_after_for_operand => { - return stream.writeAll("expected ',' after for operand"); + return w.writeAll("expected ',' after for operand"); }, .expected_comma_after_capture => { - return stream.writeAll("expected ',' after for capture"); + return w.writeAll("expected ',' after for capture"); }, .expected_initializer => { - return stream.writeAll("expected field initializer"); + return w.writeAll("expected field initializer"); }, .mismatched_binary_op_whitespace => { - return stream.print("binary operator '{s}' has whitespace on one side, but not the other", .{tree.tokenTag(parse_error.token).lexeme().?}); + return w.print("binary operator '{s}' has whitespace on one side, but not the other", .{tree.tokenTag(parse_error.token).lexeme().?}); }, .invalid_ampersand_ampersand => { - return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND"); + return w.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND"); }, .c_style_container => { - return stream.print("'{s} {s}' is invalid", .{ + return w.print("'{s} {s}' is invalid", .{ parse_error.extra.expected_tag.symbol(), tree.tokenSlice(parse_error.token), }); }, .zig_style_container => { - return stream.print("to declare a container do 'const {s} = {s}'", .{ + return w.print("to declare a container do 'const {s} = {s}'", .{ tree.tokenSlice(parse_error.token), parse_error.extra.expected_tag.symbol(), }); }, .previous_field => { - return stream.writeAll("field before declarations here"); + return w.writeAll("field before declarations here"); }, .next_field => { - return stream.writeAll("field after declarations here"); + return w.writeAll("field after declarations here"); }, .expected_var_const => { - return stream.writeAll("expected 'var' or 'const' before variable declaration"); + return w.writeAll("expected 'var' or 'const' before variable declaration"); }, .wrong_equal_var_decl => { - return stream.writeAll("variable initialized with '==' instead of '='"); + return w.writeAll("variable initialized with '==' instead of '='"); }, .var_const_decl => { - return stream.writeAll("use 'var' or 'const' to declare variable"); + return w.writeAll("use 'var' or 'const' to declare variable"); }, .extra_for_capture => { - return stream.writeAll("extra capture in for loop"); + return w.writeAll("extra capture in for loop"); }, .for_input_not_captured => { - return stream.writeAll("for input is not captured"); + return w.writeAll("for input is not captured"); }, .invalid_byte => { const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..]; - return stream.print("{s} contains invalid byte: '{f}'", .{ + return w.print("{s} contains invalid byte: '{f}'", .{ switch (tok_slice[0]) { '\'' => "character literal", '"', '\\' => "string literal", @@ -580,10 +582,10 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { const found_tag = tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)); const expected_symbol = parse_error.extra.expected_tag.symbol(); switch (found_tag) { - .invalid => return stream.print("expected '{s}', found invalid bytes", .{ + .invalid => return w.print("expected '{s}', found invalid bytes", .{ expected_symbol, }), - else => return stream.print("expected '{s}', found '{s}'", .{ + else => return w.print("expected '{s}', found '{s}'", .{ expected_symbol, found_tag.symbol(), }), } @@ -4136,17 +4138,7 @@ pub fn tokensToSpan(tree: *const Ast, start: Ast.TokenIndex, end: Ast.TokenIndex return Span{ .start = start_off, .end = end_off, .main = tree.tokenStart(main) }; } -const std = @import("../std.zig"); -const assert = std.debug.assert; -const testing = std.testing; -const mem = std.mem; -const Token = std.zig.Token; -const Ast = @This(); -const Allocator = std.mem.Allocator; -const Parse = @import("Parse.zig"); -const private_render = @import("./render.zig"); - test { _ = Parse; - _ = private_render; + _ = Render; } From 0389b4c7b9b83434daab05e8b94da315c61166ce Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Jul 2025 21:29:08 -0700 Subject: [PATCH 03/46] move a file without changing it --- lib/std/zig/{render.zig => Ast/Render.zig} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/std/zig/{render.zig => Ast/Render.zig} (100%) diff --git a/lib/std/zig/render.zig b/lib/std/zig/Ast/Render.zig similarity index 100% rename from lib/std/zig/render.zig rename to lib/std/zig/Ast/Render.zig From 2d5d2ba4f51fa5300c9807b477cada2c9b3023cd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Jul 2025 22:27:23 -0700 Subject: [PATCH 04/46] std.zig.Render: update it and references --- lib/std/Io/Writer.zig | 6 + lib/std/zig/Ast.zig | 4 +- lib/std/zig/Ast/Render.zig | 596 +++++++++++++++++---------------- lib/std/zig/ZonGen.zig | 130 +++---- lib/std/zig/parser_test.zig | 6 +- lib/std/zig/string_literal.zig | 26 +- lib/std/zon/parse.zig | 22 +- 7 files changed, 421 insertions(+), 369 deletions(-) diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 1d6abb2080..8543f0bce5 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2475,6 +2475,12 @@ pub const Allocating = struct { return result; } + pub fn ensureUnusedCapacity(a: *Allocating, additional_count: usize) Allocator.Error!void { + var list = a.toArrayList(); + defer a.setArrayList(list); + return list.ensureUnusedCapacity(a.allocator, additional_count); + } + pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 { var list = a.toArrayList(); defer a.setArrayList(list); diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 1b86b103c4..1f36c0fdbf 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -207,7 +207,7 @@ pub fn renderAlloc(tree: Ast, gpa: Allocator) error{OutOfMemory}![]u8 { var aw: std.io.Writer.Allocating = .init(gpa); defer aw.deinit(); render(tree, gpa, &aw.writer, .{}) catch |err| switch (err) { - error.WriteFailed => return error.OutOfMemory, + error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, }; return aw.toOwnedSlice(); } @@ -215,7 +215,7 @@ pub fn renderAlloc(tree: Ast, gpa: Allocator) error{OutOfMemory}![]u8 { pub const Render = @import("Ast/Render.zig"); pub fn render(tree: Ast, gpa: Allocator, w: *Writer, fixups: Render.Fixups) Render.Error!void { - return Render.tree(gpa, w, tree, fixups); + return Render.renderTree(gpa, w, tree, fixups); } /// Returns an extra offset for column and byte offset of errors that diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 2fcc1d7f35..d24d6ec694 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -1,4 +1,4 @@ -const std = @import("../std.zig"); +const std = @import("../../std.zig"); const assert = std.debug.assert; const mem = std.mem; const Allocator = std.mem.Allocator; @@ -6,13 +6,24 @@ const meta = std.meta; const Ast = std.zig.Ast; const Token = std.zig.Token; const primitives = std.zig.primitives; +const Writer = std.io.Writer; + +const Render = @This(); + +gpa: Allocator, +ais: *AutoIndentingStream, +tree: Ast, +fixups: Fixups, const indent_delta = 4; const asm_indent_delta = 2; -pub const Error = Ast.RenderError; - -const Ais = AutoIndentingStream(std.ArrayList(u8).Writer); +pub const Error = error{ + /// Ran out of memory allocating call stack frames to complete rendering. + OutOfMemory, + /// Transitive failure from + WriteFailed, +}; pub const Fixups = struct { /// The key is the mut token (`var`/`const`) of the variable declaration @@ -72,19 +83,12 @@ pub const Fixups = struct { } }; -const Render = struct { - gpa: Allocator, - ais: *Ais, - tree: Ast, - fixups: Fixups, -}; - -pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void { +pub fn renderTree(gpa: Allocator, w: *Writer, tree: Ast, fixups: Fixups) Error!void { assert(tree.errors.len == 0); // Cannot render an invalid tree. - var auto_indenting_stream = Ais.init(buffer, indent_delta); + var auto_indenting_stream: AutoIndentingStream = .init(gpa, w, indent_delta); defer auto_indenting_stream.deinit(); var r: Render = .{ - .gpa = buffer.allocator, + .gpa = gpa, .ais = &auto_indenting_stream, .tree = tree, .fixups = fixups, @@ -186,7 +190,7 @@ fn renderMember( if (opt_callconv_expr.unwrap()) |callconv_expr| { if (tree.nodeTag(callconv_expr) == .enum_literal) { if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) { - try ais.writer().writeAll("inline "); + try ais.underlying_writer.writeAll("inline "); } } } @@ -200,7 +204,7 @@ fn renderMember( const lbrace = tree.nodeMainToken(body_node); try renderToken(r, lbrace, .newline); try discardAllParams(r, fn_proto); - try ais.writer().writeAll("@trap();"); + try ais.writeAll("@trap();"); ais.popIndent(); try ais.insertNewline(); try renderToken(r, tree.lastToken(body_node), space); // rbrace @@ -216,10 +220,9 @@ fn renderMember( const name_ident = param.name_token.?; assert(tree.tokenTag(name_ident) == .identifier); if (r.fixups.unused_var_decls.contains(name_ident)) { - const w = ais.writer(); - try w.writeAll("_ = "); - try w.writeAll(tokenSliceForRender(r.tree, name_ident)); - try w.writeAll(";\n"); + try ais.writeAll("_ = "); + try ais.writeAll(tokenSliceForRender(r.tree, name_ident)); + try ais.writeAll(";\n"); } } var statements_buf: [2]Ast.Node.Index = undefined; @@ -312,7 +315,7 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const tree = r.tree; const ais = r.ais; if (r.fixups.replace_nodes_with_string.get(node)) |replacement| { - try ais.writer().writeAll(replacement); + try ais.writeAll(replacement); try renderOnlySpace(r, space); return; } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| { @@ -881,7 +884,7 @@ fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!v const ais = r.ais; try renderExpression(r, node, space); if (r.fixups.append_string_after_node.get(node)) |bytes| { - try ais.writer().writeAll(bytes); + try ais.writeAll(bytes); } } @@ -1086,10 +1089,10 @@ fn renderVarDecl( try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space); if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) { // Discard the variable like this: `_ = foo;` - const w = r.ais.writer(); - try w.writeAll("_ = "); - try w.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1)); - try w.writeAll(";\n"); + const ais = r.ais; + try ais.writeAll("_ = "); + try ais.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1)); + try ais.writeAll(";\n"); } } @@ -1567,7 +1570,7 @@ fn renderBuiltinCall( defer r.gpa.free(new_string); try renderToken(r, builtin_token + 1, .none); // ( - try ais.writer().print("\"{f}\"", .{std.zig.fmtString(new_string)}); + try ais.print("\"{f}\"", .{std.zig.fmtString(new_string)}); return renderToken(r, str_lit_token + 1, space); // ) } } @@ -2125,13 +2128,13 @@ fn renderArrayInit( const section_exprs = row_exprs[0..section_end]; - var sub_expr_buffer = std.ArrayList(u8).init(gpa); + var sub_expr_buffer: std.io.Writer.Allocating = .init(gpa); defer sub_expr_buffer.deinit(); const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1); defer gpa.free(sub_expr_buffer_starts); - var auto_indenting_stream = Ais.init(&sub_expr_buffer, indent_delta); + var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.writer, indent_delta); defer auto_indenting_stream.deinit(); var sub_render: Render = .{ .gpa = r.gpa, @@ -2145,13 +2148,14 @@ fn renderArrayInit( var single_line = true; var contains_newline = false; for (section_exprs, 0..) |expr, i| { - const start = sub_expr_buffer.items.len; + const start = sub_expr_buffer.getWritten().len; sub_expr_buffer_starts[i] = start; if (i + 1 < section_exprs.len) { try renderExpression(&sub_render, expr, .none); - const width = sub_expr_buffer.items.len - start; - const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start..], '\n') != null; + const written = sub_expr_buffer.getWritten(); + const width = written.len - start; + const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null; contains_newline = contains_newline or this_contains_newline; expr_widths[i] = width; expr_newlines[i] = this_contains_newline; @@ -2173,8 +2177,9 @@ fn renderArrayInit( try renderExpression(&sub_render, expr, .comma); ais.popSpace(); - const width = sub_expr_buffer.items.len - start - 2; - const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start .. sub_expr_buffer.items.len - 1], '\n') != null; + const written = sub_expr_buffer.getWritten(); + const width = written.len - start - 2; + const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null; contains_newline = contains_newline or this_contains_newline; expr_widths[i] = width; expr_newlines[i] = contains_newline; @@ -2185,20 +2190,20 @@ fn renderArrayInit( } } } - sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.items.len; + sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.getWritten().len; // Render exprs in current section. column_counter = 0; for (section_exprs, 0..) |expr, i| { const start = sub_expr_buffer_starts[i]; const end = sub_expr_buffer_starts[i + 1]; - const expr_text = sub_expr_buffer.items[start..end]; + const expr_text = sub_expr_buffer.getWritten()[start..end]; if (!expr_newlines[i]) { - try ais.writer().writeAll(expr_text); + try ais.writeAll(expr_text); } else { var by_line = std.mem.splitScalar(u8, expr_text, '\n'); var last_line_was_empty = false; - try ais.writer().writeAll(by_line.first()); + try ais.writeAll(by_line.first()); while (by_line.next()) |line| { if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) { try ais.insertNewline(); @@ -2206,7 +2211,7 @@ fn renderArrayInit( try ais.maybeInsertNewline(); } last_line_was_empty = (line.len == 0); - try ais.writer().writeAll(line); + try ais.writeAll(line); } } @@ -2220,7 +2225,7 @@ fn renderArrayInit( try renderToken(r, comma, .space); // , assert(column_widths[column_counter % row_size] >= expr_widths[i]); const padding = column_widths[column_counter % row_size] - expr_widths[i]; - try ais.writer().writeByteNTimes(' ', padding); + try ais.splatByteAll(' ', padding); column_counter += 1; continue; @@ -2799,7 +2804,7 @@ fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void const tree = r.tree; const ais = r.ais; const lexeme = tokenSliceForRender(tree, token_index); - try ais.writer().writeAll(lexeme); + try ais.writeAll(lexeme); try renderSpace(r, token_index, lexeme.len, space); } @@ -2807,7 +2812,7 @@ fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: const tree = r.tree; const ais = r.ais; const lexeme = tokenSliceForRender(tree, token_index); - try ais.writer().writeAll(lexeme); + try ais.writeAll(lexeme); ais.enableSpaceMode(override_space); defer ais.disableSpaceMode(); try renderSpace(r, token_index, lexeme.len, space); @@ -2822,7 +2827,7 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space if (space == .skip) return; if (space == .comma and next_token_tag != .comma) { - try ais.writer().writeByte(','); + try ais.writeByte(','); } if (space == .semicolon or space == .comma) ais.enableSpaceMode(space); defer ais.disableSpaceMode(); @@ -2833,7 +2838,7 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space ); switch (space) { .none => {}, - .space => if (!comment) try ais.writer().writeByte(' '), + .space => if (!comment) try ais.writeByte(' '), .newline => if (!comment) try ais.insertNewline(), .comma => if (next_token_tag == .comma) { @@ -2845,7 +2850,7 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space .comma_space => if (next_token_tag == .comma) { try renderToken(r, token_index + 1, .space); } else if (!comment) { - try ais.writer().writeByte(' '); + try ais.writeByte(' '); }, .semicolon => if (next_token_tag == .semicolon) { @@ -2862,11 +2867,11 @@ fn renderOnlySpace(r: *Render, space: Space) Error!void { const ais = r.ais; switch (space) { .none => {}, - .space => try ais.writer().writeByte(' '), + .space => try ais.writeByte(' '), .newline => try ais.insertNewline(), - .comma => try ais.writer().writeAll(",\n"), - .comma_space => try ais.writer().writeAll(", "), - .semicolon => try ais.writer().writeAll(";\n"), + .comma => try ais.writeAll(",\n"), + .comma_space => try ais.writeAll(", "), + .semicolon => try ais.writeAll(";\n"), .skip => unreachable, } } @@ -2883,7 +2888,7 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote const lexeme = tokenSliceForRender(tree, token_index); if (r.fixups.rename_identifiers.get(lexeme)) |mangled| { - try r.ais.writer().writeAll(mangled); + try r.ais.writeAll(mangled); try renderSpace(r, token_index, lexeme.len, space); return; } @@ -2992,15 +2997,15 @@ fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, const lexeme = tokenSliceForRender(tree, token_index); assert(lexeme.len >= 3 and lexeme[0] == '@'); - if (!unquote) try ais.writer().writeAll("@\""); + if (!unquote) try ais.writeAll("@\""); const contents = lexeme[2 .. lexeme.len - 1]; - try renderIdentifierContents(ais.writer(), contents); - if (!unquote) try ais.writer().writeByte('\"'); + try renderIdentifierContents(ais, contents); + if (!unquote) try ais.writeByte('\"'); try renderSpace(r, token_index, lexeme.len, space); } -fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void { +fn renderIdentifierContents(ais: *AutoIndentingStream, bytes: []const u8) !void { var pos: usize = 0; while (pos < bytes.len) { const byte = bytes[pos]; @@ -3013,23 +3018,23 @@ fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void { .success => |codepoint| { if (codepoint <= 0x7f) { const buf = [1]u8{@as(u8, @intCast(codepoint))}; - try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)}); + try ais.print("{f}", .{std.zig.fmtString(&buf)}); } else { - try writer.writeAll(escape_sequence); + try ais.writeAll(escape_sequence); } }, .failure => { - try writer.writeAll(escape_sequence); + try ais.writeAll(escape_sequence); }, } }, 0x00...('\\' - 1), ('\\' + 1)...0x7f => { const buf = [1]u8{byte}; - try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)}); + try ais.print("{f}", .{std.zig.fmtString(&buf)}); pos += 1; }, 0x80...0xff => { - try writer.writeByte(byte); + try ais.writeByte(byte); pos += 1; }, } @@ -3091,7 +3096,7 @@ fn renderComments(r: *Render, start: usize, end: usize) Error!bool { } else if (index == start) { // Otherwise if the first comment is on the same line as // the token before it, prefix it with a single space. - try ais.writer().writeByte(' '); + try ais.writeByte(' '); } } @@ -3108,11 +3113,11 @@ fn renderComments(r: *Render, start: usize, end: usize) Error!bool { ais.disabled_offset = null; } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) { // Write with the canonical single space. - try ais.writer().writeAll("// zig fmt: off\n"); + try ais.writeAll("// zig fmt: off\n"); ais.disabled_offset = index; } else { // Write the comment minus trailing whitespace. - try ais.writer().print("{s}\n", .{trimmed_comment}); + try ais.print("{s}\n", .{trimmed_comment}); } } @@ -3213,10 +3218,9 @@ fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void { while (it.next()) |param| { const name_ident = param.name_token.?; assert(tree.tokenTag(name_ident) == .identifier); - const w = ais.writer(); - try w.writeAll("_ = "); - try w.writeAll(tokenSliceForRender(r.tree, name_ident)); - try w.writeAll(";\n"); + try ais.writeAll("_ = "); + try ais.writeAll(tokenSliceForRender(r.tree, name_ident)); + try ais.writeAll(";\n"); } } @@ -3269,11 +3273,11 @@ fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenI return false; } -fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void { +fn writeFixingWhitespace(w: *Writer, slice: []const u8) Error!void { for (slice) |byte| switch (byte) { - '\t' => try writer.writeAll(" " ** indent_delta), + '\t' => try w.splatByteAll(' ', indent_delta), '\r' => {}, - else => try writer.writeByte(byte), + else => try w.writeByte(byte), }; } @@ -3398,224 +3402,244 @@ fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usi /// of the appropriate indentation level for them with pushSpace/popSpace. /// This should be done whenever a scope that ends in a .semicolon or a /// .comma is introduced. -fn AutoIndentingStream(comptime UnderlyingWriter: type) type { - return struct { - const Self = @This(); - pub const WriteError = UnderlyingWriter.Error; - pub const Writer = std.io.GenericWriter(*Self, WriteError, write); +const AutoIndentingStream = struct { + underlying_writer: *Writer, - pub const IndentType = enum { - normal, - after_equals, - binop, - field_access, - }; - const StackElem = struct { - indent_type: IndentType, - realized: bool, - }; - const SpaceElem = struct { - space: Space, - indent_count: usize, - }; + /// Offset into the source at which formatting has been disabled with + /// a `zig fmt: off` comment. + /// + /// If non-null, the AutoIndentingStream will not write any bytes + /// to the underlying writer. It will however continue to track the + /// indentation level. + disabled_offset: ?usize = null, - underlying_writer: UnderlyingWriter, + indent_count: usize = 0, + indent_delta: usize, + indent_stack: std.ArrayList(StackElem), + space_stack: std.ArrayList(SpaceElem), + space_mode: ?usize = null, + disable_indent_committing: usize = 0, + current_line_empty: bool = true, + /// the most recently applied indent + applied_indent: usize = 0, - /// Offset into the source at which formatting has been disabled with - /// a `zig fmt: off` comment. - /// - /// If non-null, the AutoIndentingStream will not write any bytes - /// to the underlying writer. It will however continue to track the - /// indentation level. - disabled_offset: ?usize = null, - - indent_count: usize = 0, - indent_delta: usize, - indent_stack: std.ArrayList(StackElem), - space_stack: std.ArrayList(SpaceElem), - space_mode: ?usize = null, - disable_indent_committing: usize = 0, - current_line_empty: bool = true, - /// the most recently applied indent - applied_indent: usize = 0, - - pub fn init(buffer: *std.ArrayList(u8), indent_delta_: usize) Self { - return .{ - .underlying_writer = buffer.writer(), - .indent_delta = indent_delta_, - .indent_stack = std.ArrayList(StackElem).init(buffer.allocator), - .space_stack = std.ArrayList(SpaceElem).init(buffer.allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.indent_stack.deinit(); - self.space_stack.deinit(); - } - - pub fn writer(self: *Self) Writer { - return .{ .context = self }; - } - - pub fn write(self: *Self, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) - return @as(usize, 0); - - try self.applyIndent(); - return self.writeNoIndent(bytes); - } - - // Change the indent delta without changing the final indentation level - pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void { - if (self.indent_delta == new_indent_delta) { - return; - } else if (self.indent_delta > new_indent_delta) { - assert(self.indent_delta % new_indent_delta == 0); - self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta); - } else { - // assert that the current indentation (in spaces) in a multiple of the new delta - assert((self.indent_count * self.indent_delta) % new_indent_delta == 0); - self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta); - } - self.indent_delta = new_indent_delta; - } - - fn writeNoIndent(self: *Self, bytes: []const u8) WriteError!usize { - if (bytes.len == 0) - return @as(usize, 0); - - if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes); - if (bytes[bytes.len - 1] == '\n') - self.resetLine(); - return bytes.len; - } - - pub fn insertNewline(self: *Self) WriteError!void { - _ = try self.writeNoIndent("\n"); - } - - fn resetLine(self: *Self) void { - self.current_line_empty = true; - - if (self.disable_indent_committing > 0) return; - - if (self.indent_stack.items.len > 0) { - // By default, we realize the most recent indentation scope. - var to_realize = self.indent_stack.items.len - 1; - - if (self.indent_stack.items.len >= 2 and - self.indent_stack.items[to_realize - 1].indent_type == .after_equals and - self.indent_stack.items[to_realize - 1].realized and - self.indent_stack.items[to_realize].indent_type == .binop) - { - // If we are in a .binop scope and our direct parent is .after_equals, don't indent. - // This ensures correct indentation in the below example: - // - // const foo = - // (x >= 'a' and x <= 'z') or //<-- we are here - // (x >= 'A' and x <= 'Z'); - // - return; - } - - if (self.indent_stack.items[to_realize].indent_type == .field_access) { - // Only realize the top-most field_access in a chain. - while (to_realize > 0 and self.indent_stack.items[to_realize - 1].indent_type == .field_access) - to_realize -= 1; - } - - if (self.indent_stack.items[to_realize].realized) return; - self.indent_stack.items[to_realize].realized = true; - self.indent_count += 1; - } - } - - /// Disables indentation level changes during the next newlines until re-enabled. - pub fn disableIndentCommitting(self: *Self) void { - self.disable_indent_committing += 1; - } - - pub fn enableIndentCommitting(self: *Self) void { - assert(self.disable_indent_committing > 0); - self.disable_indent_committing -= 1; - } - - pub fn pushSpace(self: *Self, space: Space) !void { - try self.space_stack.append(.{ .space = space, .indent_count = self.indent_count }); - } - - pub fn popSpace(self: *Self) void { - _ = self.space_stack.pop(); - } - - /// Sets current indentation level to be the same as that of the last pushSpace. - pub fn enableSpaceMode(self: *Self, space: Space) void { - if (self.space_stack.items.len == 0) return; - const curr = self.space_stack.getLast(); - if (curr.space != space) return; - self.space_mode = curr.indent_count; - } - - pub fn disableSpaceMode(self: *Self) void { - self.space_mode = null; - } - - pub fn lastSpaceModeIndent(self: *Self) usize { - if (self.space_stack.items.len == 0) return 0; - return self.space_stack.getLast().indent_count * self.indent_delta; - } - - /// Insert a newline unless the current line is blank - pub fn maybeInsertNewline(self: *Self) WriteError!void { - if (!self.current_line_empty) - try self.insertNewline(); - } - - /// Push default indentation - /// Doesn't actually write any indentation. - /// Just primes the stream to be able to write the correct indentation if it needs to. - pub fn pushIndent(self: *Self, indent_type: IndentType) !void { - try self.indent_stack.append(.{ .indent_type = indent_type, .realized = false }); - } - - /// Forces an indentation level to be realized. - pub fn forcePushIndent(self: *Self, indent_type: IndentType) !void { - try self.indent_stack.append(.{ .indent_type = indent_type, .realized = true }); - self.indent_count += 1; - } - - pub fn popIndent(self: *Self) void { - if (self.indent_stack.pop().?.realized) { - assert(self.indent_count > 0); - self.indent_count -= 1; - } - } - - pub fn indentStackEmpty(self: *Self) bool { - return self.indent_stack.items.len == 0; - } - - /// Writes ' ' bytes if the current line is empty - fn applyIndent(self: *Self) WriteError!void { - const current_indent = self.currentIndent(); - if (self.current_line_empty and current_indent > 0) { - if (self.disabled_offset == null) { - try self.underlying_writer.writeByteNTimes(' ', current_indent); - } - self.applied_indent = current_indent; - } - self.current_line_empty = false; - } - - /// Checks to see if the most recent indentation exceeds the currently pushed indents - pub fn isLineOverIndented(self: *Self) bool { - if (self.current_line_empty) return false; - return self.applied_indent > self.currentIndent(); - } - - fn currentIndent(self: *Self) usize { - const indent_count = self.space_mode orelse self.indent_count; - return indent_count * self.indent_delta; - } + pub const IndentType = enum { + normal, + after_equals, + binop, + field_access, }; -} + const StackElem = struct { + indent_type: IndentType, + realized: bool, + }; + const SpaceElem = struct { + space: Space, + indent_count: usize, + }; + + pub fn init(gpa: Allocator, w: *Writer, starting_indent_delta: usize) AutoIndentingStream { + return .{ + .underlying_writer = w, + .indent_delta = starting_indent_delta, + .indent_stack = .init(gpa), + .space_stack = .init(gpa), + }; + } + + pub fn deinit(self: *AutoIndentingStream) void { + self.indent_stack.deinit(); + self.space_stack.deinit(); + } + + pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void { + if (bytes.len == 0) return; + try ais.applyIndent(); + if (ais.disabled_offset == null) try ais.underlying_writer.writeAll(bytes); + if (bytes[bytes.len - 1] == '\n') ais.resetLine(); + } + + /// Assumes that if the printed data ends with a newline, it is directly + /// contained in the format string. + pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void { + try ais.applyIndent(); + if (ais.disabled_offset == null) try ais.underlying_writer.print(format, args); + if (format[format.len - 1] == '\n') ais.resetLine(); + } + + pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void { + try ais.applyIndent(); + if (ais.disabled_offset == null) try ais.underlying_writer.writeByte(byte); + assert(byte != '\n'); + } + + pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void { + assert(byte != '\n'); + try ais.applyIndent(); + if (ais.disabled_offset == null) try ais.underlying_writer.splatByteAll(byte, n); + } + + // Change the indent delta without changing the final indentation level + pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void { + if (ais.indent_delta == new_indent_delta) { + return; + } else if (ais.indent_delta > new_indent_delta) { + assert(ais.indent_delta % new_indent_delta == 0); + ais.indent_count = ais.indent_count * (ais.indent_delta / new_indent_delta); + } else { + // assert that the current indentation (in spaces) in a multiple of the new delta + assert((ais.indent_count * ais.indent_delta) % new_indent_delta == 0); + ais.indent_count = ais.indent_count / (new_indent_delta / ais.indent_delta); + } + ais.indent_delta = new_indent_delta; + } + + pub fn insertNewline(ais: *AutoIndentingStream) Error!void { + if (ais.disabled_offset == null) try ais.underlying_writer.writeByte('\n'); + ais.resetLine(); + } + + /// Insert a newline unless the current line is blank + pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void { + if (!ais.current_line_empty) + try ais.insertNewline(); + } + + /// Push an indent that is automatically popped after being applied + pub fn pushIndentOneShot(ais: *AutoIndentingStream) void { + ais.indent_one_shot_count += 1; + ais.pushIndent(); + } + + /// Turns all one-shot indents into regular indents + /// Returns number of indents that must now be manually popped + pub fn lockOneShotIndent(ais: *AutoIndentingStream) usize { + const locked_count = ais.indent_one_shot_count; + ais.indent_one_shot_count = 0; + return locked_count; + } + + /// Push an indent that should not take effect until the next line + pub fn pushIndentNextLine(ais: *AutoIndentingStream) void { + ais.indent_next_line += 1; + ais.pushIndent(); + } + + /// Checks to see if the most recent indentation exceeds the currently pushed indents + pub fn isLineOverIndented(ais: *AutoIndentingStream) bool { + if (ais.current_line_empty) return false; + return ais.applied_indent > ais.currentIndent(); + } + + fn resetLine(ais: *AutoIndentingStream) void { + ais.current_line_empty = true; + + if (ais.disable_indent_committing > 0) return; + + if (ais.indent_stack.items.len > 0) { + // By default, we realize the most recent indentation scope. + var to_realize = ais.indent_stack.items.len - 1; + + if (ais.indent_stack.items.len >= 2 and + ais.indent_stack.items[to_realize - 1].indent_type == .after_equals and + ais.indent_stack.items[to_realize - 1].realized and + ais.indent_stack.items[to_realize].indent_type == .binop) + { + // If we are in a .binop scope and our direct parent is .after_equals, don't indent. + // This ensures correct indentation in the below example: + // + // const foo = + // (x >= 'a' and x <= 'z') or //<-- we are here + // (x >= 'A' and x <= 'Z'); + // + return; + } + + if (ais.indent_stack.items[to_realize].indent_type == .field_access) { + // Only realize the top-most field_access in a chain. + while (to_realize > 0 and ais.indent_stack.items[to_realize - 1].indent_type == .field_access) + to_realize -= 1; + } + + if (ais.indent_stack.items[to_realize].realized) return; + ais.indent_stack.items[to_realize].realized = true; + ais.indent_count += 1; + } + } + + /// Disables indentation level changes during the next newlines until re-enabled. + pub fn disableIndentCommitting(ais: *AutoIndentingStream) void { + ais.disable_indent_committing += 1; + } + + pub fn enableIndentCommitting(ais: *AutoIndentingStream) void { + assert(ais.disable_indent_committing > 0); + ais.disable_indent_committing -= 1; + } + + pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void { + try ais.space_stack.append(.{ .space = space, .indent_count = ais.indent_count }); + } + + pub fn popSpace(ais: *AutoIndentingStream) void { + _ = ais.space_stack.pop(); + } + + /// Sets current indentation level to be the same as that of the last pushSpace. + pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void { + if (ais.space_stack.items.len == 0) return; + const curr = ais.space_stack.getLast(); + if (curr.space != space) return; + ais.space_mode = curr.indent_count; + } + + pub fn disableSpaceMode(ais: *AutoIndentingStream) void { + ais.space_mode = null; + } + + pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize { + if (ais.space_stack.items.len == 0) return 0; + return ais.space_stack.getLast().indent_count * ais.indent_delta; + } + + /// Push default indentation + /// Doesn't actually write any indentation. + /// Just primes the stream to be able to write the correct indentation if it needs to. + pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void { + try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = false }); + } + + /// Forces an indentation level to be realized. + pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void { + try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = true }); + ais.indent_count += 1; + } + + pub fn popIndent(ais: *AutoIndentingStream) void { + if (ais.indent_stack.pop().?.realized) { + assert(ais.indent_count > 0); + ais.indent_count -= 1; + } + } + + pub fn indentStackEmpty(ais: *AutoIndentingStream) bool { + return ais.indent_stack.items.len == 0; + } + + /// Writes ' ' bytes if the current line is empty + fn applyIndent(ais: *AutoIndentingStream) Error!void { + const current_indent = ais.currentIndent(); + if (ais.current_line_empty and current_indent > 0) { + if (ais.disabled_offset == null) { + try ais.underlying_writer.splatByteAll(' ', current_indent); + } + ais.applied_indent = current_indent; + } + ais.current_line_empty = false; + } + + fn currentIndent(ais: *AutoIndentingStream) usize { + const indent_count = ais.space_mode orelse ais.indent_count; + return indent_count * ais.indent_delta; + } +}; diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 44b59a328e..578ac28820 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -1,5 +1,16 @@ //! Ingests an `Ast` and produces a `Zoir`. +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; +const Allocator = mem.Allocator; +const StringIndexAdapter = std.hash_map.StringIndexAdapter; +const StringIndexContext = std.hash_map.StringIndexContext; +const ZonGen = @This(); +const Zoir = @import("Zoir.zig"); +const Ast = @import("Ast.zig"); +const Writer = std.io.Writer; + gpa: Allocator, tree: Ast, @@ -446,37 +457,44 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator } } -fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 { +fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) error{ OutOfMemory, BadString }!u32 { + const gpa = zg.gpa; const tree = zg.tree; assert(tree.tokenTag(ident_token) == .identifier); const ident_name = tree.tokenSlice(ident_token); if (!mem.startsWith(u8, ident_name, "@")) { const start = zg.string_bytes.items.len; - try zg.string_bytes.appendSlice(zg.gpa, ident_name); + try zg.string_bytes.appendSlice(gpa, ident_name); return @intCast(start); - } else { - const offset = 1; - const start: u32 = @intCast(zg.string_bytes.items.len); - const raw_string = zg.tree.tokenSlice(ident_token)[offset..]; - try zg.string_bytes.ensureUnusedCapacity(zg.gpa, raw_string.len); - switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) { - .success => {}, - .failure => |err| { - try zg.lowerStrLitError(err, ident_token, raw_string, offset); - return error.BadString; - }, - } - - const slice = zg.string_bytes.items[start..]; - if (mem.indexOfScalar(u8, slice, 0) != null) { - try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{}); - return error.BadString; - } else if (slice.len == 0) { - try zg.addErrorTok(ident_token, "identifier cannot be empty", .{}); - return error.BadString; - } - return start; } + const offset = 1; + const start: u32 = @intCast(zg.string_bytes.items.len); + const raw_string = zg.tree.tokenSlice(ident_token)[offset..]; + try zg.string_bytes.ensureUnusedCapacity(gpa, raw_string.len); + const result = r: { + var aw: std.io.Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes); + defer zg.string_bytes = aw.toArrayList(); + break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + }; + switch (result) { + .success => {}, + .failure => |err| { + try zg.lowerStrLitError(err, ident_token, raw_string, offset); + return error.BadString; + }, + } + + const slice = zg.string_bytes.items[start..]; + if (mem.indexOfScalar(u8, slice, 0) != null) { + try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{}); + return error.BadString; + } else if (slice.len == 0) { + try zg.addErrorTok(ident_token, "identifier cannot be empty", .{}); + return error.BadString; + } + return start; } /// Estimates the size of a string node without parsing it. @@ -507,8 +525,8 @@ pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize { pub fn parseStrLit( tree: Ast, node: Ast.Node.Index, - writer: anytype, -) error{OutOfMemory}!std.zig.string_literal.Result { + writer: *Writer, +) Writer.Error!std.zig.string_literal.Result { switch (tree.nodeTag(node)) { .string_literal => { const token = tree.nodeMainToken(node); @@ -543,15 +561,22 @@ const StringLiteralResult = union(enum) { slice: struct { start: u32, len: u32 }, }; -fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) !StringLiteralResult { +fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) error{ OutOfMemory, BadString }!StringLiteralResult { if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } }; const gpa = zg.gpa; const string_bytes = &zg.string_bytes; const str_index: u32 = @intCast(zg.string_bytes.items.len); const size_hint = strLitSizeHint(zg.tree, str_node); - try string_bytes.ensureUnusedCapacity(zg.gpa, size_hint); - switch (try parseStrLit(zg.tree, str_node, zg.string_bytes.writer(zg.gpa))) { + try string_bytes.ensureUnusedCapacity(gpa, size_hint); + const result = r: { + var aw: std.io.Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes); + defer zg.string_bytes = aw.toArrayList(); + break :r parseStrLit(zg.tree, str_node, &aw.writer) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + }; + switch (result) { .success => {}, .failure => |err| { const token = zg.tree.nodeMainToken(str_node); @@ -793,10 +818,7 @@ fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.T fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note { const message_idx: u32 = @intCast(zg.string_bytes.items.len); - const writer = zg.string_bytes.writer(zg.gpa); - try writer.print(format, args); - try writer.writeByte(0); - + try zg.string_bytes.print(zg.gpa, format ++ "\x00", args); return .{ .msg = @enumFromInt(message_idx), .token = .none, @@ -806,10 +828,7 @@ fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, a fn errNoteTok(zg: *ZonGen, tok: Ast.TokenIndex, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note { const message_idx: u32 = @intCast(zg.string_bytes.items.len); - const writer = zg.string_bytes.writer(zg.gpa); - try writer.print(format, args); - try writer.writeByte(0); - + try zg.string_bytes.print(zg.gpa, format ++ "\x00", args); return .{ .msg = @enumFromInt(message_idx), .token = .fromToken(tok), @@ -850,9 +869,7 @@ fn addErrorInner( try zg.error_notes.appendSlice(gpa, notes); const message_idx: u32 = @intCast(zg.string_bytes.items.len); - const writer = zg.string_bytes.writer(zg.gpa); - try writer.print(format, args); - try writer.writeByte(0); + try zg.string_bytes.print(gpa, format ++ "\x00", args); try zg.compile_errors.append(gpa, .{ .msg = @enumFromInt(message_idx), @@ -868,8 +885,9 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void { const tree = zg.tree; assert(tree.errors.len > 0); - var msg: std.ArrayListUnmanaged(u8) = .empty; - defer msg.deinit(gpa); + var msg: std.io.Writer.Allocating = .init(gpa); + defer msg.deinit(); + const msg_bw = &msg.writer; var notes: std.ArrayListUnmanaged(Zoir.CompileError.Note) = .empty; defer notes.deinit(gpa); @@ -877,18 +895,20 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void { var cur_err = tree.errors[0]; for (tree.errors[1..]) |err| { if (err.is_note) { - try tree.renderError(err, msg.writer(gpa)); - try notes.append(gpa, try zg.errNoteTok(err.token, "{s}", .{msg.items})); + tree.renderError(err, msg_bw) catch return error.OutOfMemory; + try notes.append(gpa, try zg.errNoteTok(err.token, "{s}", .{msg.getWritten()})); } else { // Flush error - try tree.renderError(cur_err, msg.writer(gpa)); + tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory; const extra_offset = tree.errorOffset(cur_err); - try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items); + try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items); notes.clearRetainingCapacity(); cur_err = err; - // TODO: `Parse` currently does not have good error recovery mechanisms, so the remaining errors could be bogus. - // As such, we'll ignore all remaining errors for now. We should improve `Parse` so that we can report all the errors. + // TODO: `Parse` currently does not have good error recovery + // mechanisms, so the remaining errors could be bogus. As such, + // we'll ignore all remaining errors for now. We should improve + // `Parse` so that we can report all the errors. return; } msg.clearRetainingCapacity(); @@ -896,16 +916,6 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void { // Flush error const extra_offset = tree.errorOffset(cur_err); - try tree.renderError(cur_err, msg.writer(gpa)); - try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items); + tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory; + try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items); } - -const std = @import("std"); -const assert = std.debug.assert; -const mem = std.mem; -const Allocator = mem.Allocator; -const StringIndexAdapter = std.hash_map.StringIndexAdapter; -const StringIndexContext = std.hash_map.StringIndexContext; -const ZonGen = @This(); -const Zoir = @import("Zoir.zig"); -const Ast = @import("Ast.zig"); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index ed9f8b79b9..9991f334a1 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6367,7 +6367,9 @@ test "ampersand" { var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { - const stderr = std.fs.File.stderr().deprecatedWriter(); + var buffer: [64]u8 = undefined; + const stderr = std.debug.lockStderrWriter(&buffer); + defer std.debug.unlockStderrWriter(); var tree = try std.zig.Ast.parse(allocator, source, .zig); defer tree.deinit(allocator); @@ -6390,7 +6392,7 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: * return error.ParseError; } - const formatted = try tree.render(allocator); + const formatted = try tree.renderAlloc(allocator); anything_changed.* = !mem.eql(u8, formatted, source); return formatted; } diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 99b060eceb..9775459a36 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -1,6 +1,7 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const utf8Encode = std.unicode.utf8Encode; +const Writer = std.io.Writer; pub const ParseError = error{ OutOfMemory, @@ -315,9 +316,10 @@ test parseCharLiteral { ); } -/// Parses `bytes` as a Zig string literal and writes the result to the `std.io.GenericWriter` type. +/// Parses `bytes` as a Zig string literal and writes the result to the `Writer` type. +/// /// Asserts `bytes` has '"' at beginning and end. -pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result { +pub fn parseWrite(writer: *Writer, bytes: []const u8) Writer.Error!Result { assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"'); var index: usize = 1; @@ -333,18 +335,18 @@ pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result if (bytes[escape_char_index] == 'u') { var buf: [4]u8 = undefined; const len = utf8Encode(codepoint, &buf) catch { - return Result{ .failure = .{ .invalid_unicode_codepoint = escape_char_index + 1 } }; + return .{ .failure = .{ .invalid_unicode_codepoint = escape_char_index + 1 } }; }; try writer.writeAll(buf[0..len]); } else { try writer.writeByte(@as(u8, @intCast(codepoint))); } }, - .failure => |err| return Result{ .failure = err }, + .failure => |err| return .{ .failure = err }, } }, - '\n' => return Result{ .failure = .{ .invalid_character = index } }, - '"' => return Result.success, + '\n' => return .{ .failure = .{ .invalid_character = index } }, + '"' => return .success, else => { try writer.writeByte(b); index += 1; @@ -356,11 +358,13 @@ pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result /// Higher level API. Does not return extra info about parse errors. /// Caller owns returned memory. pub fn parseAlloc(allocator: std.mem.Allocator, bytes: []const u8) ParseError![]u8 { - var buf = std.ArrayList(u8).init(allocator); - defer buf.deinit(); - - switch (try parseWrite(buf.writer(), bytes)) { - .success => return buf.toOwnedSlice(), + var aw: std.io.Writer.Allocating = .init(allocator); + defer aw.deinit(); + const result = parseWrite(&aw.writer, bytes) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + switch (result) { + .success => return aw.toOwnedSlice(), .failure => return error.InvalidLiteral, } } diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 561fbd2a4c..d5df5693ec 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -411,18 +411,22 @@ const Parser = struct { diag: ?*Diagnostics, options: Options, - fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory }!T { + const ParseExprError = error{ ParseZon, OutOfMemory }; + + fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprError!T { return self.parseExprInner(T, node) catch |err| switch (err) { error.WrongType => return self.failExpectedType(T, node), else => |e| return e, }; } + const ParseExprInnerError = error{ ParseZon, OutOfMemory, WrongType }; + fn parseExprInner( self: *@This(), T: type, node: Zoir.Node.Index, - ) error{ ParseZon, OutOfMemory, WrongType }!T { + ) ParseExprInnerError!T { if (T == Zoir.Node.Index) { return node; } @@ -611,15 +615,17 @@ const Parser = struct { } } - fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) !T { + fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T { const ast_node = node.getAstNode(self.zoir); const pointer = @typeInfo(T).pointer; var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); if (pointer.sentinel() != null) size_hint += 1; - var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint); - defer buf.deinit(self.gpa); - switch (try ZonGen.parseStrLit(self.ast, ast_node, buf.writer(self.gpa))) { + var aw: std.Io.Writer.Allocating = .init(self.gpa); + try aw.ensureUnusedCapacity(size_hint); + defer aw.deinit(); + const result = ZonGen.parseStrLit(self.ast, ast_node, &aw.writer) catch return error.OutOfMemory; + switch (result) { .success => {}, .failure => |err| { const token = self.ast.nodeMainToken(ast_node); @@ -638,9 +644,9 @@ const Parser = struct { } if (pointer.sentinel() != null) { - return buf.toOwnedSliceSentinel(self.gpa, 0); + return aw.toOwnedSliceSentinel(0); } else { - return buf.toOwnedSlice(self.gpa); + return aw.toOwnedSlice(); } } From b3ee5a6c303998e82cdf95bd522bd9769c3d1911 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Jul 2025 23:00:27 -0700 Subject: [PATCH 05/46] update cmake file listing --- CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db580b05fa..d9824e5c12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -446,7 +446,6 @@ set(ZIG_STAGE2_SOURCES lib/std/dwarf/OP.zig lib/std/dwarf/TAG.zig lib/std/elf.zig - lib/std/fifo.zig lib/std/fmt.zig lib/std/fmt/parse_float.zig lib/std/fs.zig @@ -462,7 +461,6 @@ set(ZIG_STAGE2_SOURCES lib/std/heap.zig lib/std/heap/arena_allocator.zig lib/std/json.zig - lib/std/json/stringify.zig lib/std/leb128.zig lib/std/log.zig lib/std/macho.zig @@ -505,6 +503,7 @@ set(ZIG_STAGE2_SOURCES lib/std/unicode.zig lib/std/zig.zig lib/std/zig/Ast.zig + lib/std/zig/Ast/Render.zig lib/std/zig/AstGen.zig lib/std/zig/AstRlAnnotate.zig lib/std/zig/LibCInstallation.zig @@ -513,7 +512,6 @@ set(ZIG_STAGE2_SOURCES lib/std/zig/WindowsSdk.zig lib/std/zig/Zir.zig lib/std/zig/c_builtins.zig - lib/std/zig/render.zig lib/std/zig/string_literal.zig lib/std/zig/system.zig lib/std/zig/system/NativePaths.zig From c4776d66af3cec54a6f87337da714d388aa866da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Jul 2025 23:38:18 -0700 Subject: [PATCH 06/46] update compiler --- lib/docs/wasm/Walk.zig | 12 ++++++---- lib/std/Io/Writer.zig | 6 +++++ lib/std/zig.zig | 11 ++------- lib/std/zig/AstGen.zig | 50 ++++++++++++++++++++++------------------ src/Compilation.zig | 2 +- src/Package/Manifest.zig | 12 ++++++---- src/fmt.zig | 36 ++++++++++++++++++----------- src/main.zig | 24 +++++++++++-------- tools/gen_spirv_spec.zig | 2 +- 9 files changed, 91 insertions(+), 64 deletions(-) diff --git a/lib/docs/wasm/Walk.zig b/lib/docs/wasm/Walk.zig index 90df301f22..2c41e9c940 100644 --- a/lib/docs/wasm/Walk.zig +++ b/lib/docs/wasm/Walk.zig @@ -433,14 +433,18 @@ fn parse(file_name: []const u8, source: []u8) Oom!Ast { defer ast.deinit(gpa); const token_offsets = ast.tokens.items(.start); - var rendered_err: std.ArrayListUnmanaged(u8) = .{}; - defer rendered_err.deinit(gpa); + var rendered_err: std.Io.Writer.Allocating = .init(gpa); + defer rendered_err.deinit(); for (ast.errors) |err| { const err_offset = token_offsets[err.token] + ast.errorOffset(err); const err_loc = std.zig.findLineColumn(ast.source, err_offset); rendered_err.clearRetainingCapacity(); - try ast.renderError(err, rendered_err.writer(gpa)); - log.err("{s}:{d}:{d}: {s}", .{ file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.items }); + ast.renderError(err, &rendered_err.writer) catch |e| switch (e) { + error.WriteFailed => return error.OutOfMemory, + }; + log.err("{s}:{d}:{d}: {s}", .{ + file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.getWritten(), + }); } return Ast.parse(gpa, "", .zig); } diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 8543f0bce5..dd72607b3f 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -2481,6 +2481,12 @@ pub const Allocating = struct { return list.ensureUnusedCapacity(a.allocator, additional_count); } + pub fn ensureTotalCapacity(a: *Allocating, new_capacity: usize) Allocator.Error!void { + var list = a.toArrayList(); + defer a.setArrayList(list); + return list.ensureTotalCapacity(a.allocator, new_capacity); + } + pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 { var list = a.toArrayList(); defer a.setArrayList(list); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 6a64adc547..fa45af129c 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -530,18 +530,11 @@ test isUnderscore { try std.testing.expect(!isUnderscore("\\x5f")); } -pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: usize) ![:0]u8 { +pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader) ![:0]u8 { var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty; defer buffer.deinit(gpa); - try buffer.ensureUnusedCapacity(gpa, size_hint); - - input.readIntoArrayList(gpa, .limited(max_src_size), .@"2", &buffer) catch |err| switch (err) { - error.ConnectionResetByPeer => unreachable, - error.ConnectionTimedOut => unreachable, - error.NotOpenForReading => unreachable, - else => |e| return e, - }; + try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size)); // Detect unsupported file types with their Byte Order Mark const unsupported_boms = [_][]const u8{ diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 175403b295..52fc41a2c9 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -11278,10 +11278,14 @@ fn parseStrLit( offset: u32, ) InnerError!void { const raw_string = bytes[offset..]; - var buf_managed = buf.toManaged(astgen.gpa); - const result = std.zig.string_literal.parseWrite(buf_managed.writer(), raw_string); - buf.* = buf_managed.moveToUnmanaged(); - switch (try result) { + const result = r: { + var aw: std.io.Writer.Allocating = .fromArrayList(astgen.gpa, buf); + defer buf.* = aw.toArrayList(); + break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + }; + switch (result) { .success => return, .failure => |err| return astgen.failWithStrLitError(err, token, bytes, offset), } @@ -11324,17 +11328,18 @@ fn appendErrorNodeNotes( notes: []const u32, ) Allocator.Error!void { @branchHint(.cold); + const gpa = astgen.gpa; const string_bytes = &astgen.string_bytes; const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len); - try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args); + try string_bytes.print(gpa, 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); + try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len); astgen.extra.appendAssumeCapacity(@intCast(notes.len)); astgen.extra.appendSliceAssumeCapacity(notes); break :blk @intCast(notes_start); } else 0; - try astgen.compile_errors.append(astgen.gpa, .{ + try astgen.compile_errors.append(gpa, .{ .msg = msg, .node = node.toOptional(), .token = .none, @@ -11418,7 +11423,7 @@ fn appendErrorTokNotesOff( const gpa = astgen.gpa; const string_bytes = &astgen.string_bytes; const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len); - try string_bytes.writer(gpa).print(format ++ "\x00", args); + try string_bytes.print(gpa, 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); @@ -11454,7 +11459,7 @@ fn errNoteTokOff( @branchHint(.cold); const string_bytes = &astgen.string_bytes; const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len); - try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args); + try string_bytes.print(astgen.gpa, format ++ "\x00", args); return astgen.addExtra(Zir.Inst.CompileErrors.Item{ .msg = msg, .node = .none, @@ -11473,7 +11478,7 @@ fn errNoteNode( @branchHint(.cold); const string_bytes = &astgen.string_bytes; const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len); - try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args); + try string_bytes.print(astgen.gpa, format ++ "\x00", args); return astgen.addExtra(Zir.Inst.CompileErrors.Item{ .msg = msg, .node = node.toOptional(), @@ -13715,13 +13720,14 @@ fn emitDbgStmtForceCurrentIndex(gz: *GenZir, lc: LineColumn) !void { } }); } -fn lowerAstErrors(astgen: *AstGen) !void { +fn lowerAstErrors(astgen: *AstGen) error{OutOfMemory}!void { const gpa = astgen.gpa; const tree = astgen.tree; assert(tree.errors.len > 0); - var msg: std.ArrayListUnmanaged(u8) = .empty; - defer msg.deinit(gpa); + var msg: std.io.Writer.Allocating = .init(gpa); + defer msg.deinit(); + const msg_w = &msg.writer; var notes: std.ArrayListUnmanaged(u32) = .empty; defer notes.deinit(gpa); @@ -13749,26 +13755,26 @@ fn lowerAstErrors(astgen: *AstGen) !void { break :blk idx - tok_start; }; - const err: Ast.Error = .{ + const ast_err: Ast.Error = .{ .tag = Ast.Error.Tag.invalid_byte, .token = tok, .extra = .{ .offset = bad_off }, }; msg.clearRetainingCapacity(); - try tree.renderError(err, msg.writer(gpa)); - return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.items}, notes.items); + tree.renderError(ast_err, msg_w) catch return error.OutOfMemory; + return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.getWritten()}, notes.items); } var cur_err = tree.errors[0]; for (tree.errors[1..]) |err| { if (err.is_note) { - try tree.renderError(err, msg.writer(gpa)); - try notes.append(gpa, try astgen.errNoteTok(err.token, "{s}", .{msg.items})); + tree.renderError(err, msg_w) catch return error.OutOfMemory; + try notes.append(gpa, try astgen.errNoteTok(err.token, "{s}", .{msg.getWritten()})); } else { // Flush error const extra_offset = tree.errorOffset(cur_err); - try tree.renderError(cur_err, msg.writer(gpa)); - try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items); + tree.renderError(cur_err, msg_w) catch return error.OutOfMemory; + try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items); notes.clearRetainingCapacity(); cur_err = err; @@ -13781,8 +13787,8 @@ fn lowerAstErrors(astgen: *AstGen) !void { // Flush error const extra_offset = tree.errorOffset(cur_err); - try tree.renderError(cur_err, msg.writer(gpa)); - try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items); + tree.renderError(cur_err, msg_w) catch return error.OutOfMemory; + try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items); } const DeclarationName = union(enum) { diff --git a/src/Compilation.zig b/src/Compilation.zig index 8406134352..461528c939 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -5328,7 +5328,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{}); defer out_zig_file.close(); - const formatted = try tree.render(comp.gpa); + const formatted = try tree.renderAlloc(comp.gpa); defer comp.gpa.free(formatted); try out_zig_file.writeAll(formatted); diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index 1d71b60fa3..f5a4a8c314 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -471,10 +471,14 @@ const Parse = struct { offset: u32, ) InnerError!void { const raw_string = bytes[offset..]; - var buf_managed = buf.toManaged(p.gpa); - const result = std.zig.string_literal.parseWrite(buf_managed.writer(), raw_string); - buf.* = buf_managed.moveToUnmanaged(); - switch (try result) { + const result = r: { + var aw: std.io.Writer.Allocating = .fromArrayList(p.gpa, buf); + defer buf.* = aw.toArrayList(); + break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + }; + switch (result) { .success => {}, .failure => |err| try p.appendStrLitError(err, token, bytes, offset), } diff --git a/src/fmt.zig b/src/fmt.zig index bbb02e0c34..88c0e3a06f 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -34,7 +34,7 @@ const Fmt = struct { color: Color, gpa: Allocator, arena: Allocator, - out_buffer: std.ArrayList(u8), + out_buffer: std.Io.Writer.Allocating, const SeenMap = std.AutoHashMap(fs.File.INode, void); }; @@ -102,7 +102,9 @@ pub fn run( } const stdin: fs.File = .stdin(); - const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| { + var stdio_buffer: [1024]u8 = undefined; + var file_reader: fs.File.Reader = stdin.reader(&stdio_buffer); + const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| { fatal("unable to read stdin: {}", .{err}); }; defer gpa.free(source_code); @@ -146,7 +148,7 @@ pub fn run( try std.zig.printAstErrorsToStderr(gpa, tree, "", color); process.exit(2); } - const formatted = try tree.render(gpa); + const formatted = try tree.renderAlloc(gpa); defer gpa.free(formatted); if (check_flag) { @@ -169,7 +171,7 @@ pub fn run( .check_ast = check_ast_flag, .force_zon = force_zon, .color = color, - .out_buffer = std.ArrayList(u8).init(gpa), + .out_buffer = .init(gpa), }; defer fmt.seen.deinit(); defer fmt.out_buffer.deinit(); @@ -230,6 +232,9 @@ const FmtError = error{ NetNameDeleted, InvalidArgument, ProcessNotFound, + ConnectionTimedOut, + NotOpenForReading, + StreamTooLong, } || fs.File.OpenError; fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { @@ -295,12 +300,15 @@ fn fmtPathFile( if (stat.kind == .directory) return error.IsDir; + var read_buffer: [1024]u8 = undefined; + var file_reader: fs.File.Reader = source_file.reader(&read_buffer); + file_reader.size = stat.size; + const gpa = fmt.gpa; - const source_code = try std.zig.readSourceFileToEndAlloc( - gpa, - source_file, - std.math.cast(usize, stat.size) orelse return error.FileTooBig, - ); + const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + else => |e| return e, + }; defer gpa.free(source_code); source_file.close(); @@ -363,11 +371,13 @@ fn fmtPathFile( } // As a heuristic, we make enough capacity for the same as the input source. - fmt.out_buffer.shrinkRetainingCapacity(0); + fmt.out_buffer.clearRetainingCapacity(); try fmt.out_buffer.ensureTotalCapacity(source_code.len); - try tree.renderToArrayList(&fmt.out_buffer, .{}); - if (mem.eql(u8, fmt.out_buffer.items, source_code)) + tree.render(gpa, &fmt.out_buffer.writer, .{}) catch |err| switch (err) { + error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, + }; + if (mem.eql(u8, fmt.out_buffer.getWritten(), source_code)) return; if (check_mode) { @@ -378,7 +388,7 @@ fn fmtPathFile( var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode }); defer af.deinit(); - try af.file.writeAll(fmt.out_buffer.items); + try af.file.writeAll(fmt.out_buffer.getWritten()); try af.finish(); const stdout = std.fs.File.stdout().deprecatedWriter(); try stdout.print("{s}\n", .{file_path}); diff --git a/src/main.zig b/src/main.zig index 37b137ea94..019ae8bc7e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4550,7 +4550,7 @@ fn cmdTranslateC( error.SemanticAnalyzeFail => break :f .{ .error_bundle = errors }, }; defer tree.deinit(comp.gpa); - break :f .{ .success = try tree.render(arena) }; + break :f .{ .success = try tree.renderAlloc(arena) }; }, }; @@ -6058,7 +6058,8 @@ fn cmdAstCheck( }; } else fs.File.stdin(); defer if (zig_source_path != null) f.close(); - break :s std.zig.readSourceFileToEndAlloc(arena, f, null) catch |err| { + var file_reader: fs.File.Reader = f.reader(&stdio_buffer); + break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); }; }; @@ -6416,14 +6417,16 @@ fn cmdChangelist( var f = fs.cwd().openFile(old_source_path, .{}) catch |err| fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); defer f.close(); - break :source std.zig.readSourceFileToEndAlloc(arena, f, std.zig.max_src_size) catch |err| + var file_reader: fs.File.Reader = f.reader(&stdio_buffer); + break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); }; const new_source = source: { var f = fs.cwd().openFile(new_source_path, .{}) catch |err| fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); defer f.close(); - break :source std.zig.readSourceFileToEndAlloc(arena, f, std.zig.max_src_size) catch |err| + var file_reader: fs.File.Reader = f.reader(&stdio_buffer); + break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); }; @@ -6946,7 +6949,7 @@ fn cmdFetch( ast.deinit(gpa); } - var fixups: Ast.Fixups = .{}; + var fixups: Ast.Render.Fixups = .{}; defer fixups.deinit(gpa); var saved_path_or_url = path_or_url; @@ -7047,12 +7050,13 @@ fn cmdFetch( try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text); } - var rendered = std.ArrayList(u8).init(gpa); - defer rendered.deinit(); - try ast.renderToArrayList(&rendered, fixups); + var aw: std.Io.Writer.Allocating = .init(gpa); + defer aw.deinit(); + try ast.render(gpa, &aw.writer, fixups); + const rendered = aw.getWritten(); - build_root.directory.handle.writeFile(.{ .sub_path = Package.Manifest.basename, .data = rendered.items }) catch |err| { - fatal("unable to write {s} file: {s}", .{ Package.Manifest.basename, @errorName(err) }); + build_root.directory.handle.writeFile(.{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| { + fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err }); }; return cleanExit(); diff --git a/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index 8840a476c4..c8a3cf5ced 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -120,7 +120,7 @@ pub fn main() !void { error_bundle.renderToStdErr(color.renderOptions()); } - const formatted_output = try tree.render(allocator); + const formatted_output = try tree.renderAlloc(allocator); _ = try std.fs.File.stdout().write(formatted_output); } From 9222d201d76d5b134d9014480520f649140563c3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 11:40:21 -0700 Subject: [PATCH 07/46] add a happy little main function to src/fmt.zig Provided for debugging/testing purposes; unused by the compiler. --- src/fmt.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/fmt.zig b/src/fmt.zig index 88c0e3a06f..7ac3018814 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -394,3 +394,12 @@ fn fmtPathFile( try stdout.print("{s}\n", .{file_path}); } } + +/// Provided for debugging/testing purposes; unused by the compiler. +pub fn main() !void { + const gpa = std.heap.smp_allocator; + var arena_instance = std.heap.ArenaAllocator.init(gpa); + const arena = arena_instance.allocator(); + const args = try process.argsAlloc(arena); + return run(gpa, arena, args[1..]); +} From e7a639967e91c8408ae281a97966e3dd67dd5565 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 13:34:43 -0700 Subject: [PATCH 08/46] std.Io.Reader: fix appendRemaining it calls readVec which is a higher level function than was expected in the previous implementation --- lib/std/Io/Reader.zig | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index e569c36773..38e0c6dc30 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -246,34 +246,18 @@ pub fn appendRemaining( limit: Limit, ) LimitedAllocError!void { assert(r.buffer.len != 0); // Needed to detect limit exceeded without losing data. - const buffer = r.buffer; - const buffer_contents = buffer[r.seek..r.end]; - const copy_len = limit.minInt(buffer_contents.len); - try list.ensureUnusedCapacity(gpa, copy_len); - @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]); - list.items.len += copy_len; - r.seek += copy_len; - if (copy_len == buffer_contents.len) { - r.seek = 0; - r.end = 0; - } - var remaining = limit.subtract(copy_len).?; - while (true) { - try list.ensureUnusedCapacity(gpa, 1); + var remaining = limit; + while (remaining.nonzero()) { + try list.ensureUnusedCapacity(gpa, r.bufferedLen() + 1); const dest = remaining.slice(list.unusedCapacitySlice()); - const additional_buffer: []u8 = if (@intFromEnum(remaining) == dest.len) buffer else &.{}; - const n = readVec(r, &.{ dest, additional_buffer }) catch |err| switch (err) { + const n = readVecLimit(r, &.{dest}, .unlimited) catch |err| switch (err) { error.EndOfStream => break, error.ReadFailed => return error.ReadFailed, }; - if (n > dest.len) { - r.end = n - dest.len; - list.items.len += dest.len; - return error.StreamTooLong; - } list.items.len += n; remaining = remaining.subtract(n).?; } + if (r.bufferedLen() != 0) return error.StreamTooLong; } /// Writes bytes from the internally tracked stream position to `data`. From 73cfba4d0d0ed00de8f5fbb83257d703ba284d61 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 13:08:33 -0700 Subject: [PATCH 09/46] std.Io.Writer: fix writeStruct --- lib/std/Io/Writer.zig | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index dd72607b3f..0cebde1e3f 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -483,7 +483,7 @@ pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void { // Deal with any left over splats if (data.len != 0 and truncate < data[index].len * splat) { - std.debug.assert(index == data.len - 1); + assert(index == data.len - 1); var remaining_splat = splat; while (true) { remaining_splat -= truncate / data[index].len; @@ -840,11 +840,11 @@ pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.builtin.Endian .auto => @compileError("ill-defined memory layout"), .@"extern" => { if (native_endian == endian) { - return w.writeStruct(value); + return w.writeAll(@ptrCast((&value)[0..1])); } else { var copy = value; std.mem.byteSwapAllFields(@TypeOf(value), ©); - return w.writeStruct(copy); + return w.writeAll(@ptrCast((©)[0..1])); } }, .@"packed" => { @@ -2606,8 +2606,32 @@ test "allocating sendFile" { var file_reader = file_writer.moveToReader(); try file_reader.seekTo(0); - var allocating: std.io.Writer.Allocating = .init(std.testing.allocator); + var allocating: std.io.Writer.Allocating = .init(testing.allocator); defer allocating.deinit(); _ = try file_reader.interface.streamRemaining(&allocating.writer); } + +test writeStruct { + var buffer: [16]u8 = undefined; + const S = extern struct { a: u64, b: u32, c: u32 }; + const s: S = .{ .a = 1, .b = 2, .c = 3 }; + { + var w: Writer = .fixed(&buffer); + try w.writeStruct(s, .little); + try testing.expectEqualSlices(u8, &.{ + 1, 0, 0, 0, 0, 0, 0, 0, // + 2, 0, 0, 0, // + 3, 0, 0, 0, // + }, &buffer); + } + { + var w: Writer = .fixed(&buffer); + try w.writeStruct(s, .big); + try testing.expectEqualSlices(u8, &.{ + 0, 0, 0, 0, 0, 0, 0, 1, // + 0, 0, 0, 2, // + 0, 0, 0, 3, // + }, &buffer); + } +} From 1f93f6195805b39f698df124ad1e1495201624f2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 13:39:42 -0700 Subject: [PATCH 10/46] std.zig.readSourceFileToEndAlloc: add file size heuristic --- lib/std/zig.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index fa45af129c..d113129b7e 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -534,6 +534,11 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty; defer buffer.deinit(gpa); + if (file_reader.getSize()) |size| { + const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong; + try buffer.ensureTotalCapacityPrecise(gpa, casted_size); + } else |_| {} + try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size)); // Detect unsupported file types with their Byte Order Mark From aac301a655d63c239d39e3cd5a73154e091be703 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 14:10:18 -0700 Subject: [PATCH 11/46] update aro --- lib/compiler/aro_translate_c.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 015a558d4e..31ec1ceab9 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -1824,7 +1824,7 @@ pub fn main() !void { }; defer tree.deinit(gpa); - const formatted = try tree.render(arena); + const formatted = try tree.renderAlloc(arena); try std.fs.File.stdout().writeAll(formatted); return std.process.cleanExit(); } From ad726587cc66b1ae07a463e1d591362130090170 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 14:58:22 -0700 Subject: [PATCH 12/46] zig fmt: update to new I/O API --- src/fmt.zig | 66 ++++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 52 deletions(-) diff --git a/src/fmt.zig b/src/fmt.zig index 7ac3018814..23e668d245 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -35,15 +35,12 @@ const Fmt = struct { gpa: Allocator, arena: Allocator, out_buffer: std.Io.Writer.Allocating, + stdout_writer: *fs.File.Writer, const SeenMap = std.AutoHashMap(fs.File.INode, void); }; -pub fn run( - gpa: Allocator, - arena: Allocator, - args: []const []const u8, -) !void { +pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { var color: Color = .auto; var stdin_flag = false; var check_flag = false; @@ -60,8 +57,7 @@ pub fn run( const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - const stdout = std.fs.File.stdout().deprecatedWriter(); - try stdout.writeAll(usage_fmt); + try fs.File.stdout().writeAll(usage_fmt); return process.cleanExit(); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { @@ -156,13 +152,16 @@ pub fn run( process.exit(code); } - return std.fs.File.stdout().writeAll(formatted); + return fs.File.stdout().writeAll(formatted); } if (input_files.items.len == 0) { fatal("expected at least one source file argument", .{}); } + var stdout_buffer: [4096]u8 = undefined; + var stdout_writer = fs.File.stdout().writer(&stdout_buffer); + var fmt: Fmt = .{ .gpa = gpa, .arena = arena, @@ -172,6 +171,7 @@ pub fn run( .force_zon = force_zon, .color = color, .out_buffer = .init(gpa), + .stdout_writer = &stdout_writer, }; defer fmt.seen.deinit(); defer fmt.out_buffer.deinit(); @@ -198,46 +198,10 @@ pub fn run( if (fmt.any_error) { process.exit(1); } + try fmt.stdout_writer.interface.flush(); } -const FmtError = error{ - SystemResources, - OperationAborted, - IoPending, - BrokenPipe, - Unexpected, - WouldBlock, - Canceled, - FileClosed, - DestinationAddressRequired, - DiskQuota, - FileTooBig, - MessageTooBig, - InputOutput, - NoSpaceLeft, - AccessDenied, - OutOfMemory, - RenameAcrossMountPoints, - ReadOnlyFileSystem, - LinkQuotaExceeded, - FileBusy, - EndOfStream, - Unseekable, - NotOpenForWriting, - UnsupportedEncoding, - InvalidEncoding, - ConnectionResetByPeer, - SocketNotConnected, - LockViolation, - NetNameDeleted, - InvalidArgument, - ProcessNotFound, - ConnectionTimedOut, - NotOpenForReading, - StreamTooLong, -} || fs.File.OpenError; - -fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { +fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) !void { fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { @@ -254,7 +218,7 @@ fn fmtPathDir( check_mode: bool, parent_dir: fs.Dir, parent_sub_path: []const u8, -) FmtError!void { +) !void { var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); defer dir.close(); @@ -290,7 +254,7 @@ fn fmtPathFile( check_mode: bool, dir: fs.Dir, sub_path: []const u8, -) FmtError!void { +) !void { const source_file = try dir.openFile(sub_path, .{}); var file_closed = false; errdefer if (!file_closed) source_file.close(); @@ -381,8 +345,7 @@ fn fmtPathFile( return; if (check_mode) { - const stdout = std.fs.File.stdout().deprecatedWriter(); - try stdout.print("{s}\n", .{file_path}); + try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); fmt.any_error = true; } else { var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode }); @@ -390,8 +353,7 @@ fn fmtPathFile( try af.file.writeAll(fmt.out_buffer.getWritten()); try af.finish(); - const stdout = std.fs.File.stdout().deprecatedWriter(); - try stdout.print("{s}\n", .{file_path}); + try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); } } From 70f514f1ba961f05813fbe869a942a33113edf4c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 17:04:55 -0700 Subject: [PATCH 13/46] std.Io.Reader: fix appendRemaining harder ensure that it issues a stream call that includes the buffer to detect the end when needed, but otherwise does not offer Reader buffer to append directly to the list. --- lib/std/Io/Reader.zig | 153 +++++++++++++++++++++++++----------------- 1 file changed, 91 insertions(+), 62 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 38e0c6dc30..8a177ea183 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -246,18 +246,41 @@ pub fn appendRemaining( limit: Limit, ) LimitedAllocError!void { assert(r.buffer.len != 0); // Needed to detect limit exceeded without losing data. - var remaining = limit; - while (remaining.nonzero()) { - try list.ensureUnusedCapacity(gpa, r.bufferedLen() + 1); - const dest = remaining.slice(list.unusedCapacitySlice()); - const n = readVecLimit(r, &.{dest}, .unlimited) catch |err| switch (err) { - error.EndOfStream => break, - error.ReadFailed => return error.ReadFailed, - }; - list.items.len += n; - remaining = remaining.subtract(n).?; + const buffer_contents = r.buffer[r.seek..r.end]; + const copy_len = limit.minInt(buffer_contents.len); + try list.appendSlice(gpa, r.buffer[0..copy_len]); + r.seek += copy_len; + if (buffer_contents.len - copy_len != 0) return error.StreamTooLong; + r.seek = 0; + r.end = 0; + var remaining = @intFromEnum(limit) - copy_len; + while (true) { + try list.ensureUnusedCapacity(gpa, 1); + const cap = list.unusedCapacitySlice(); + const dest = cap[0..@min(cap.len, remaining)]; + if (remaining - dest.len == 0) { + // Additionally provides `buffer` to detect end. + const new_remaining = readVecInner(r, &.{}, dest, remaining) catch |err| switch (err) { + error.EndOfStream => { + if (r.bufferedLen() != 0) return error.StreamTooLong; + return; + }, + error.ReadFailed => return error.ReadFailed, + }; + list.items.len += remaining - new_remaining; + remaining = new_remaining; + } else { + // Leave `buffer` empty, appending directly to `list`. + var dest_w: Writer = .fixed(dest); + const n = r.vtable.stream(r, &dest_w, .limited(dest.len)) catch |err| switch (err) { + error.WriteFailed => unreachable, // Prevented by the limit. + error.EndOfStream => return, + error.ReadFailed => return error.ReadFailed, + }; + list.items.len += n; + remaining -= n; + } } - if (r.bufferedLen() != 0) return error.StreamTooLong; } /// Writes bytes from the internally tracked stream position to `data`. @@ -297,62 +320,68 @@ pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize { // buffer capacity requirements met. r.seek = 0; r.end = 0; - const first = buf[copy_len..]; - const middle = data[i + 1 ..]; - var wrapper: Writer.VectorWrapper = .{ - .it = .{ - .first = first, - .middle = middle, - .last = r.buffer, - }, - .writer = .{ - .buffer = if (first.len >= r.buffer.len) first else r.buffer, - .vtable = Writer.VectorWrapper.vtable, - }, - }; - var n = r.vtable.stream(r, &wrapper.writer, .limited(remaining)) catch |err| switch (err) { - error.WriteFailed => { - assert(!wrapper.used); - if (wrapper.writer.buffer.ptr == first.ptr) { - remaining -= wrapper.writer.end; - } else { - assert(wrapper.writer.end <= r.buffer.len); - r.end = wrapper.writer.end; - } - break; - }, - else => |e| return e, - }; - if (!wrapper.used) { - if (wrapper.writer.buffer.ptr == first.ptr) { - remaining -= n; - } else { - assert(n <= r.buffer.len); - r.end = n; - } - break; - } - if (n < first.len) { - remaining -= n; - break; - } - remaining -= first.len; - n -= first.len; - for (middle) |mid| { - if (n < mid.len) { - remaining -= n; - break; - } - remaining -= mid.len; - n -= mid.len; - } - assert(n <= r.buffer.len); - r.end = n; + remaining = try readVecInner(r, data[i + 1 ..], buf[copy_len..], remaining); break; } return @intFromEnum(limit) - remaining; } +fn readVecInner(r: *Reader, middle: []const []u8, first: []u8, remaining: usize) Error!usize { + var wrapper: Writer.VectorWrapper = .{ + .it = .{ + .first = first, + .middle = middle, + .last = r.buffer, + }, + .writer = .{ + .buffer = if (first.len >= r.buffer.len) first else r.buffer, + .vtable = Writer.VectorWrapper.vtable, + }, + }; + // If the limit may pass beyond user buffer into Reader buffer, use + // unlimited, allowing the Reader buffer to fill. + const limit: Limit = l: { + var n: usize = first.len; + for (middle) |m| n += m.len; + break :l if (remaining >= n) .unlimited else .limited(remaining); + }; + var n = r.vtable.stream(r, &wrapper.writer, limit) catch |err| switch (err) { + error.WriteFailed => { + assert(!wrapper.used); + if (wrapper.writer.buffer.ptr == first.ptr) { + return remaining - wrapper.writer.end; + } else { + assert(wrapper.writer.end <= r.buffer.len); + r.end = wrapper.writer.end; + return remaining; + } + }, + else => |e| return e, + }; + if (!wrapper.used) { + if (wrapper.writer.buffer.ptr == first.ptr) { + return remaining - n; + } else { + assert(n <= r.buffer.len); + r.end = n; + return remaining; + } + } + if (n < first.len) return remaining - n; + var result = remaining - first.len; + n -= first.len; + for (middle) |mid| { + if (n < mid.len) { + return result - n; + } + result -= mid.len; + n -= mid.len; + } + assert(n <= r.buffer.len); + r.end = n; + return result; +} + pub fn buffered(r: *Reader) []u8 { return r.buffer[r.seek..r.end]; } From 741569d5a70026ada754d0f2809a40d00c9a41ca Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 17:58:06 -0700 Subject: [PATCH 14/46] std.Ast.Render: fix conflicts with master branch --- lib/std/zig/Ast/Render.zig | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index d24d6ec694..ac982a7897 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -2399,24 +2399,24 @@ fn renderAsmLegacy( try renderToken(r, first_clobber - 2, .none); try renderToken(r, first_clobber - 1, .space); - try ais.writer().writeAll(".{ "); + try ais.writeAll(".{ "); var tok_i = first_clobber; while (true) : (tok_i += 1) { - try ais.writer().writeByte('.'); + try ais.writeByte('.'); _ = try writeStringLiteralAsIdentifier(r, tok_i); - try ais.writer().writeAll(" = true"); + try ais.writeAll(" = true"); tok_i += 1; switch (tree.tokenTag(tok_i)) { .r_paren => { - try ais.writer().writeAll(" }"); + try ais.writeAll(" }"); ais.popIndent(); return renderToken(r, tok_i, space); }, .comma => { if (tree.tokenTag(tok_i + 1) == .r_paren) { - try ais.writer().writeAll(" }"); + try ais.writeAll(" }"); ais.popIndent(); return renderToken(r, tok_i + 1, space); } else { @@ -2511,16 +2511,16 @@ fn renderAsmLegacy( }; try renderToken(r, colon3, .space); // : - try ais.writer().writeAll(".{ "); + try ais.writeAll(".{ "); const first_clobber = asm_node.first_clobber.?; var tok_i = first_clobber; while (true) { switch (tree.tokenTag(tok_i + 1)) { .r_paren => { ais.setIndentDelta(indent_delta); - try ais.writer().writeByte('.'); + try ais.writeByte('.'); const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i); - try ais.writer().writeAll(" = true }"); + try ais.writeAll(" = true }"); try renderSpace(r, tok_i, lexeme_len, .newline); ais.popIndent(); return renderToken(r, tok_i + 1, space); @@ -2529,17 +2529,17 @@ fn renderAsmLegacy( switch (tree.tokenTag(tok_i + 2)) { .r_paren => { ais.setIndentDelta(indent_delta); - try ais.writer().writeByte('.'); + try ais.writeByte('.'); const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i); - try ais.writer().writeAll(" = true }"); + try ais.writeAll(" = true }"); try renderSpace(r, tok_i, lexeme_len, .newline); ais.popIndent(); return renderToken(r, tok_i + 2, space); }, else => { - try ais.writer().writeByte('.'); + try ais.writeByte('.'); _ = try writeStringLiteralAsIdentifier(r, tok_i); - try ais.writer().writeAll(" = true"); + try ais.writeAll(" = true"); try renderToken(r, tok_i + 1, .space); tok_i += 2; }, @@ -3242,11 +3242,11 @@ fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usiz const lexeme = tokenSliceForRender(tree, token_index); const unquoted = lexeme[1..][0 .. lexeme.len - 2]; if (std.zig.isValidId(unquoted)) { - try ais.writer().writeAll(unquoted); + try ais.writeAll(unquoted); return unquoted.len; } else { - try ais.writer().writeByte('@'); - try ais.writer().writeAll(lexeme); + try ais.writeByte('@'); + try ais.writeAll(lexeme); return lexeme.len + 1; } } From 9da19e51ea1da7cf6746dbdf8340248a77ce362f Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 21:54:05 +0100 Subject: [PATCH 15/46] Add tests for exp2(), with bugfix for 64-bit boundary case --- lib/compiler_rt/exp2.zig | 101 ++++++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/lib/compiler_rt/exp2.zig b/lib/compiler_rt/exp2.zig index 66e58ee463..e27bae8092 100644 --- a/lib/compiler_rt/exp2.zig +++ b/lib/compiler_rt/exp2.zig @@ -10,6 +10,7 @@ const arch = builtin.cpu.arch; const math = std.math; const mem = std.mem; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const common = @import("common.zig"); pub const panic = common.panic; @@ -58,7 +59,7 @@ pub fn exp2f(x: f32) callconv(.c) f32 { if (common.want_float_exceptions) mem.doNotOptimizeAway(-0x1.0p-149 / x); } // x <= -150 - if (u >= 0x3160000) { + if (u >= 0xC3160000) { return 0; } } @@ -457,34 +458,78 @@ const exp2dt = [_]f64{ 0x1.690f4b19e9471p+0, -0x1.9780p-45, }; -test "exp2_32" { - const epsilon = 0.000001; - - try expect(exp2f(0.0) == 1.0); - try expect(math.approxEqAbs(f32, exp2f(0.2), 1.148698, epsilon)); - try expect(math.approxEqAbs(f32, exp2f(0.8923), 1.856133, epsilon)); - try expect(math.approxEqAbs(f32, exp2f(1.5), 2.828427, epsilon)); - try expect(math.approxEqAbs(f32, exp2f(37.45), 187747237888, epsilon)); - try expect(math.approxEqAbs(f32, exp2f(-1), 0.5, epsilon)); -} - -test "exp2_64" { - const epsilon = 0.000001; - - try expect(exp2(0.0) == 1.0); - try expect(math.approxEqAbs(f64, exp2(0.2), 1.148698, epsilon)); - try expect(math.approxEqAbs(f64, exp2(0.8923), 1.856133, epsilon)); - try expect(math.approxEqAbs(f64, exp2(1.5), 2.828427, epsilon)); - try expect(math.approxEqAbs(f64, exp2(-1), 0.5, epsilon)); - try expect(math.approxEqAbs(f64, exp2(-0x1.a05cc754481d1p-2), 0x1.824056efc687cp-1, epsilon)); -} - -test "exp2_32.special" { - try expect(math.isPositiveInf(exp2f(math.inf(f32)))); +test "exp2f() special" { + try expectEqual(exp2f(0.0), 1.0); + try expectEqual(exp2f(-0.0), 1.0); + try expectEqual(exp2f(1.0), 2.0); + try expectEqual(exp2f(-1.0), 0.5); + try expectEqual(exp2f(math.inf(f32)), math.inf(f32)); + try expectEqual(exp2f(-math.inf(f32)), 0.0); try expect(math.isNan(exp2f(math.nan(f32)))); + try expect(math.isNan(exp2f(math.snan(f32)))); } -test "exp2_64.special" { - try expect(math.isPositiveInf(exp2(math.inf(f64)))); - try expect(math.isNan(exp2(math.nan(f64)))); +test "exp2f() sanity" { + try expectEqual(exp2f(-0x1.0223a0p+3), 0x1.e8d134p-9); + try expectEqual(exp2f(0x1.161868p+2), 0x1.453672p+4); + try expectEqual(exp2f(-0x1.0c34b4p+3), 0x1.890ca0p-9); + try expectEqual(exp2f(-0x1.a206f0p+2), 0x1.622d4ep-7); + try expectEqual(exp2f(0x1.288bbcp+3), 0x1.340ecep+9); + try expectEqual(exp2f(0x1.52efd0p-1), 0x1.950eeep+0); + try expectEqual(exp2f(-0x1.a05cc8p-2), 0x1.824056p-1); + try expectEqual(exp2f(0x1.1f9efap-1), 0x1.79dfa2p+0); + try expectEqual(exp2f(0x1.8c5db0p-1), 0x1.b5ceacp+0); + try expectEqual(exp2f(-0x1.5b86eap-1), 0x1.3fd8bap-1); +} + +test "exp2f() boundary" { + try expectEqual(exp2f(0x1.fffffep+6), 0x1.ffff4ep+127); // The last value before the result gets infinite + try expectEqual(exp2f(0x1p+7), math.inf(f32)); // The first value that gives infinite result + try expectEqual(exp2f(-0x1.2bccccp+7), 0x1p-149); // The last value before the result flushes to zero + try expectEqual(exp2f(-0x1.2cp+7), 0); // The first value at which the result flushes to zero + try expectEqual(exp2f(-0x1.f8p+6), 0x1p-126); // The last value before the result flushes to subnormal + try expectEqual(exp2f(-0x1.f80002p+6), 0x1.ffff50p-127); // The first value for which the result flushes to subnormal + try expectEqual(exp2f(0x1.fffffep+127), math.inf(f32)); // Max input value + try expectEqual(exp2f(0x1p-149), 1); // Min positive input value + try expectEqual(exp2f(-0x1p-149), 1); // Min negative input value + try expectEqual(exp2f(0x1p-126), 1); // First positive subnormal input + try expectEqual(exp2f(-0x1p-126), 1); // First negative subnormal input +} + +test "exp2() special" { + try expectEqual(exp2(0.0), 1.0); + try expectEqual(exp2(-0.0), 1.0); + try expectEqual(exp2(1.0), 2.0); + try expectEqual(exp2(-1.0), 0.5); + try expectEqual(exp2(math.inf(f64)), math.inf(f64)); + try expectEqual(exp2(-math.inf(f64)), 0.0); + try expect(math.isNan(exp2(math.nan(f64)))); + try expect(math.isNan(exp2(math.snan(f64)))); +} + +test "exp2() sanity" { + try expectEqual(exp2(-0x1.02239f3c6a8f1p+3), 0x1.e8d13c396f452p-9); + try expectEqual(exp2(0x1.161868e18bc67p+2), 0x1.4536746bb6f12p+4); + try expectEqual(exp2(-0x1.0c34b3e01e6e7p+3), 0x1.890ca0c00b9a2p-9); + try expectEqual(exp2(-0x1.a206f0a19dcc4p+2), 0x1.622d4b0ebc6c1p-7); + try expectEqual(exp2(0x1.288bbb0d6a1e6p+3), 0x1.340ec7f3e607ep+9); + try expectEqual(exp2(0x1.52efd0cd80497p-1), 0x1.950eef4bc5451p+0); + try expectEqual(exp2(-0x1.a05cc754481d1p-2), 0x1.824056efc687cp-1); + try expectEqual(exp2(0x1.1f9ef934745cbp-1), 0x1.79dfa14ab121ep+0); + try expectEqual(exp2(0x1.8c5db097f7442p-1), 0x1.b5cead2247372p+0); + try expectEqual(exp2(-0x1.5b86ea8118a0ep-1), 0x1.3fd8ba33216b9p-1); +} + +test "exp2() boundary" { + try expectEqual(exp2(0x1.fffffffffffffp+9), 0x1.ffffffffffd3ap+1023); // The last value before the result gets infinite + try expectEqual(exp2(0x1p+10), math.inf(f64)); // The first value that gives infinite result + try expectEqual(exp2(-0x1.0cbffffffffffp+10), 0x1p-1074); // The last value before the result flushes to zero + try expectEqual(exp2(-0x1.0ccp+10), 0); // The first value at which the result flushes to zero + try expectEqual(exp2(-0x1.ffp+9), 0x1p-1022); // The last value before the result flushes to subnormal + try expectEqual(exp2(-0x1.ff00000000001p+9), 0x1.ffffffffffd3ap-1023); // The first value for which the result flushes to subnormal + try expectEqual(exp2(0x1.fffffffffffffp+1023), math.inf(f64)); // Max input value + try expectEqual(exp2(0x1p-1074), 1); // Min positive input value + try expectEqual(exp2(-0x1p-1074), 1); // Min negative input value + try expectEqual(exp2(0x1p-1022), 1); // First positive subnormal input + try expectEqual(exp2(-0x1p-1022), 1); // First negative subnormal input } From da8974e57f3581d7302efa3cacbd0d55f6c87bd4 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 22:05:26 +0100 Subject: [PATCH 16/46] Add tests for exp(), noting last-bit discrepancy for exp(1.0) with math.e --- lib/compiler_rt/exp.zig | 119 +++++++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 25 deletions(-) diff --git a/lib/compiler_rt/exp.zig b/lib/compiler_rt/exp.zig index 178d46617e..55b784770b 100644 --- a/lib/compiler_rt/exp.zig +++ b/lib/compiler_rt/exp.zig @@ -10,6 +10,7 @@ const arch = builtin.cpu.arch; const math = std.math; const mem = std.mem; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const common = @import("common.zig"); pub const panic = common.panic; @@ -211,32 +212,100 @@ pub fn expl(x: c_longdouble) callconv(.c) c_longdouble { } } -test "exp32" { - const epsilon = 0.000001; - - try expect(expf(0.0) == 1.0); - try expect(math.approxEqAbs(f32, expf(0.0), 1.0, epsilon)); - try expect(math.approxEqAbs(f32, expf(0.2), 1.221403, epsilon)); - try expect(math.approxEqAbs(f32, expf(0.8923), 2.440737, epsilon)); - try expect(math.approxEqAbs(f32, expf(1.5), 4.481689, epsilon)); -} - -test "exp64" { - const epsilon = 0.000001; - - try expect(exp(0.0) == 1.0); - try expect(math.approxEqAbs(f64, exp(0.0), 1.0, epsilon)); - try expect(math.approxEqAbs(f64, exp(0.2), 1.221403, epsilon)); - try expect(math.approxEqAbs(f64, exp(0.8923), 2.440737, epsilon)); - try expect(math.approxEqAbs(f64, exp(1.5), 4.481689, epsilon)); -} - -test "exp32.special" { - try expect(math.isPositiveInf(expf(math.inf(f32)))); +test "expf() special" { + try expectEqual(expf(0.0), 1.0); + try expectEqual(expf(-0.0), 1.0); + try expectEqual(expf(1.0), math.e); + try expectEqual(expf(math.ln2), 2.0); + try expectEqual(expf(math.inf(f32)), math.inf(f32)); + try expectEqual(expf(-math.inf(f32)), 0.0); try expect(math.isNan(expf(math.nan(f32)))); + try expect(math.isNan(expf(math.snan(f32)))); } -test "exp64.special" { - try expect(math.isPositiveInf(exp(math.inf(f64)))); - try expect(math.isNan(exp(math.nan(f64)))); +test "expf() sanity" { + try expectEqual(expf(-0x1.0223a0p+3), 0x1.490320p-12); + try expectEqual(expf(0x1.161868p+2), 0x1.34712ap+6); + try expectEqual(expf(-0x1.0c34b4p+3), 0x1.e06b1ap-13); + try expectEqual(expf(-0x1.a206f0p+2), 0x1.7dd484p-10); + try expectEqual(expf(0x1.288bbcp+3), 0x1.4abc80p+13); + try expectEqual(expf(0x1.52efd0p-1), 0x1.f04a9cp+0); + try expectEqual(expf(-0x1.a05cc8p-2), 0x1.54f1e0p-1); + try expectEqual(expf(0x1.1f9efap-1), 0x1.c0f628p+0); + try expectEqual(expf(0x1.8c5db0p-1), 0x1.1599b2p+1); + try expectEqual(expf(-0x1.5b86eap-1), 0x1.03b572p-1); + try expectEqual(expf(-0x1.57f25cp+2), 0x1.2fbea2p-8); + try expectEqual(expf(0x1.c7d310p+3), 0x1.76eefp+20); + try expectEqual(expf(0x1.19be70p+4), 0x1.52d3dep+25); + try expectEqual(expf(-0x1.ab6d70p+3), 0x1.a88adep-20); + try expectEqual(expf(-0x1.5ac18ep+2), 0x1.22b328p-8); + try expectEqual(expf(-0x1.925982p-1), 0x1.d2acc0p-2); + try expectEqual(expf(0x1.7221cep+3), 0x1.9c2ceap+16); + try expectEqual(expf(0x1.11a0d4p+4), 0x1.980ee6p+24); + try expectEqual(expf(-0x1.ae41a2p+1), 0x1.1c28d0p-5); + try expectEqual(expf(-0x1.329154p+4), 0x1.47ef94p-28); +} + +test "expf() boundary" { + try expectEqual(expf(0x1.62e42ep+6), 0x1.ffff08p+127); // The last value before the result gets infinite + try expectEqual(expf(0x1.62e430p+6), math.inf(f32)); // The first value that gives inf + try expectEqual(expf(0x1.fffffep+127), math.inf(f32)); // Max input value + try expectEqual(expf(0x1p-149), 1.0); // Min positive input value + try expectEqual(expf(-0x1p-149), 1.0); // Min negative input value + try expectEqual(expf(0x1p-126), 1.0); // First positive subnormal input + try expectEqual(expf(-0x1p-126), 1.0); // First negative subnormal input + try expectEqual(expf(-0x1.9fe368p+6), 0x1p-149); // The last value before the result flushes to zero + try expectEqual(expf(-0x1.9fe36ap+6), 0.0); // The first value at which the result flushes to zero + try expectEqual(expf(-0x1.5d589ep+6), 0x1.00004cp-126); // The last value before the result flushes to subnormal + try expectEqual(expf(-0x1.5d58a0p+6), 0x1.ffff98p-127); // The first value for which the result flushes to subnormal + +} + +test "exp() special" { + try expectEqual(exp(0.0), 1.0); + try expectEqual(exp(-0.0), 1.0); + // TODO: Accuracy error - off in the last bit in 64-bit, disagreeing with GCC + // try expectEqual(exp(1.0), math.e); + try expectEqual(exp(math.ln2), 2.0); + try expectEqual(exp(math.inf(f64)), math.inf(f64)); + try expectEqual(exp(-math.inf(f64)), 0.0); + try expect(math.isNan(exp(math.nan(f64)))); + try expect(math.isNan(exp(math.snan(f64)))); +} + +test "exp() sanity" { + try expectEqual(exp(-0x1.02239f3c6a8f1p+3), 0x1.490327ea61235p-12); + try expectEqual(exp(0x1.161868e18bc67p+2), 0x1.34712ed238c04p+6); + try expectEqual(exp(-0x1.0c34b3e01e6e7p+3), 0x1.e06b1b6c18e64p-13); + try expectEqual(exp(-0x1.a206f0a19dcc4p+2), 0x1.7dd47f810e68cp-10); + try expectEqual(exp(0x1.288bbb0d6a1e6p+3), 0x1.4abc77496e07ep+13); + try expectEqual(exp(0x1.52efd0cd80497p-1), 0x1.f04a9c1080500p+0); + try expectEqual(exp(-0x1.a05cc754481d1p-2), 0x1.54f1e0fd3ea0dp-1); + try expectEqual(exp(0x1.1f9ef934745cbp-1), 0x1.c0f6266a6a547p+0); + try expectEqual(exp(0x1.8c5db097f7442p-1), 0x1.1599b1d4a25fbp+1); + try expectEqual(exp(-0x1.5b86ea8118a0ep-1), 0x1.03b5728a00229p-1); + try expectEqual(exp(-0x1.57f25b2b5006dp+2), 0x1.2fbea6a01cab9p-8); + try expectEqual(exp(0x1.c7d30fb825911p+3), 0x1.76eeed45a0634p+20); + try expectEqual(exp(0x1.19be709de7505p+4), 0x1.52d3eb7be6844p+25); + try expectEqual(exp(-0x1.ab6d6fba96889p+3), 0x1.a88ae12f985d6p-20); + try expectEqual(exp(-0x1.5ac18e27084ddp+2), 0x1.22b327da9cca6p-8); + try expectEqual(exp(-0x1.925981b093c41p-1), 0x1.d2acc046b55f7p-2); + try expectEqual(exp(0x1.7221cd18455f5p+3), 0x1.9c2cde8699cfbp+16); + try expectEqual(exp(0x1.11a0d4a51b239p+4), 0x1.980ef612ff182p+24); + try expectEqual(exp(-0x1.ae41a1079de4dp+1), 0x1.1c28d16bb3222p-5); + try expectEqual(exp(-0x1.329153103b871p+4), 0x1.47efa6ddd0d22p-28); +} + +test "exp() boundary" { + try expectEqual(exp(0x1.62e42fefa39efp+9), 0x1.fffffffffff2ap+1023); // The last value before the result gets infinite + try expectEqual(exp(0x1.62e42fefa39f0p+9), math.inf(f64)); // The first value that gives inf + try expectEqual(exp(0x1.fffffffffffffp+1023), math.inf(f64)); // Max input value + try expectEqual(exp(0x1p-1074), 1.0); // Min positive input value + try expectEqual(exp(-0x1p-1074), 1.0); // Min negative input value + try expectEqual(exp(0x1p-1022), 1.0); // First positive subnormal input + try expectEqual(exp(-0x1p-1022), 1.0); // First negative subnormal input + try expectEqual(exp(-0x1.74910d52d3051p+9), 0x1p-1074); // The last value before the result flushes to zero + try expectEqual(exp(-0x1.74910d52d3052p+9), 0.0); // The first value at which the result flushes to zero + try expectEqual(exp(-0x1.6232bdd7abcd2p+9), 0x1.000000000007cp-1022); // The last value before the result flushes to subnormal + try expectEqual(exp(-0x1.6232bdd7abcd3p+9), 0x1.ffffffffffcf8p-1023); // The first value for which the result flushes to subnormal } From 936cf57a38aa8d7cbaf593e416b146294bf1b427 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 22:17:47 +0100 Subject: [PATCH 17/46] Add tests for log(), with bugfix for 64-bit boundary case --- lib/compiler_rt/log.zig | 97 ++++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/lib/compiler_rt/log.zig b/lib/compiler_rt/log.zig index 6b5c8fc683..e7c98547e7 100644 --- a/lib/compiler_rt/log.zig +++ b/lib/compiler_rt/log.zig @@ -7,7 +7,8 @@ const std = @import("std"); const builtin = @import("builtin"); const math = std.math; -const testing = std.testing; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const arch = builtin.cpu.arch; const common = @import("common.zig"); @@ -110,8 +111,8 @@ pub fn log(x_: f64) callconv(.c) f64 { // subnormal, scale x k -= 54; - x *= 0x1.0p54; - hx = @intCast(@as(u64, @bitCast(ix)) >> 32); + x *= 0x1p54; + hx = @intCast(@as(u64, @bitCast(x)) >> 32); } else if (hx >= 0x7FF00000) { return x; } else if (hx == 0x3FF00000 and ix << 32 == 0) { @@ -159,38 +160,72 @@ pub fn logl(x: c_longdouble) callconv(.c) c_longdouble { } } -test "ln32" { - const epsilon = 0.000001; - - try testing.expect(math.approxEqAbs(f32, logf(0.2), -1.609438, epsilon)); - try testing.expect(math.approxEqAbs(f32, logf(0.8923), -0.113953, epsilon)); - try testing.expect(math.approxEqAbs(f32, logf(1.5), 0.405465, epsilon)); - try testing.expect(math.approxEqAbs(f32, logf(37.45), 3.623007, epsilon)); - try testing.expect(math.approxEqAbs(f32, logf(89.123), 4.490017, epsilon)); - try testing.expect(math.approxEqAbs(f32, logf(123123.234375), 11.720941, epsilon)); +test "logf() special" { + try expectEqual(logf(0.0), -math.inf(f32)); + try expectEqual(logf(-0.0), -math.inf(f32)); + try expectEqual(logf(1.0), 0.0); + try expectEqual(logf(math.e), 1.0); + try expectEqual(logf(math.inf(f32)), math.inf(f32)); + try expect(math.isNan(logf(-1.0))); + try expect(math.isNan(logf(-math.inf(f32)))); + try expect(math.isNan(logf(math.nan(f32)))); + try expect(math.isNan(logf(math.snan(f32)))); } -test "ln64" { - const epsilon = 0.000001; - - try testing.expect(math.approxEqAbs(f64, log(0.2), -1.609438, epsilon)); - try testing.expect(math.approxEqAbs(f64, log(0.8923), -0.113953, epsilon)); - try testing.expect(math.approxEqAbs(f64, log(1.5), 0.405465, epsilon)); - try testing.expect(math.approxEqAbs(f64, log(37.45), 3.623007, epsilon)); - try testing.expect(math.approxEqAbs(f64, log(89.123), 4.490017, epsilon)); - try testing.expect(math.approxEqAbs(f64, log(123123.234375), 11.720941, epsilon)); +test "logf() sanity" { + try expect(math.isNan(logf(-0x1.0223a0p+3))); + try expectEqual(logf(0x1.161868p+2), 0x1.7815b0p+0); + try expect(math.isNan(logf(-0x1.0c34b4p+3))); + try expect(math.isNan(logf(-0x1.a206f0p+2))); + try expectEqual(logf(0x1.288bbcp+3), 0x1.1cfcd6p+1); + try expectEqual(logf(0x1.52efd0p-1), -0x1.a6694cp-2); + try expect(math.isNan(logf(-0x1.a05cc8p-2))); + try expectEqual(logf(0x1.1f9efap-1), -0x1.2742bap-1); + try expectEqual(logf(0x1.8c5db0p-1), -0x1.062160p-2); + try expect(math.isNan(logf(-0x1.5b86eap-1))); } -test "ln32.special" { - try testing.expect(math.isPositiveInf(logf(math.inf(f32)))); - try testing.expect(math.isNegativeInf(logf(0.0))); - try testing.expect(math.isNan(logf(-1.0))); - try testing.expect(math.isNan(logf(math.nan(f32)))); +test "logf() boundary" { + try expectEqual(logf(0x1.fffffep+127), 0x1.62e430p+6); // Max input value + try expectEqual(logf(0x1p-149), -0x1.9d1da0p+6); // Min positive input value + try expect(math.isNan(logf(-0x1p-149))); // Min negative input value + try expectEqual(logf(0x1.000002p+0), 0x1.fffffep-24); // Last value before result reaches +0 + try expectEqual(logf(0x1.fffffep-1), -0x1p-24); // Last value before result reaches -0 + try expectEqual(logf(0x1p-126), -0x1.5d58a0p+6); // First subnormal + try expect(math.isNan(logf(-0x1p-126))); // First negative subnormal } -test "ln64.special" { - try testing.expect(math.isPositiveInf(log(math.inf(f64)))); - try testing.expect(math.isNegativeInf(log(0.0))); - try testing.expect(math.isNan(log(-1.0))); - try testing.expect(math.isNan(log(math.nan(f64)))); +test "log() special" { + try expectEqual(log(0.0), -math.inf(f64)); + try expectEqual(log(-0.0), -math.inf(f64)); + try expectEqual(log(1.0), 0.0); + try expectEqual(log(math.e), 1.0); + try expectEqual(log(math.inf(f64)), math.inf(f64)); + try expect(math.isNan(log(-1.0))); + try expect(math.isNan(log(-math.inf(f64)))); + try expect(math.isNan(log(math.nan(f64)))); + try expect(math.isNan(log(math.snan(f64)))); +} + +test "log() sanity" { + try expect(math.isNan(log(-0x1.02239f3c6a8f1p+3))); + try expectEqual(log(0x1.161868e18bc67p+2), 0x1.7815b08f99c65p+0); + try expect(math.isNan(log(-0x1.0c34b3e01e6e7p+3))); + try expect(math.isNan(log(-0x1.a206f0a19dcc4p+2))); + try expectEqual(log(0x1.288bbb0d6a1e6p+3), 0x1.1cfcd53d72604p+1); + try expectEqual(log(0x1.52efd0cd80497p-1), -0x1.a6694a4a85621p-2); + try expect(math.isNan(log(-0x1.a05cc754481d1p-2))); + try expectEqual(log(0x1.1f9ef934745cbp-1), -0x1.2742bc03d02ddp-1); + try expectEqual(log(0x1.8c5db097f7442p-1), -0x1.06215de4a3f92p-2); + try expect(math.isNan(log(-0x1.5b86ea8118a0ep-1))); +} + +test "log() boundary" { + try expectEqual(log(0x1.fffffffffffffp+1023), 0x1.62e42fefa39efp+9); // Max input value + try expectEqual(log(0x1p-1074), -0x1.74385446d71c3p+9); // Min positive input value + try expect(math.isNan(log(-0x1p-1074))); // Min negative input value + try expectEqual(log(0x1.0000000000001p+0), 0x1.fffffffffffffp-53); // Last value before result reaches +0 + try expectEqual(log(0x1.fffffffffffffp-1), -0x1p-53); // Last value before result reaches -0 + try expectEqual(log(0x1p-1022), -0x1.6232bdd7abcd2p+9); // First subnormal + try expect(math.isNan(log(-0x1p-1022))); // First negative subnormal } From 650e358220dd82ae7037bb555c6d2af471d6c6cb Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 22:24:24 +0100 Subject: [PATCH 18/46] Add tests for log2() --- lib/compiler_rt/log2.zig | 94 ++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/lib/compiler_rt/log2.zig b/lib/compiler_rt/log2.zig index 7d52e437fd..300eff4993 100644 --- a/lib/compiler_rt/log2.zig +++ b/lib/compiler_rt/log2.zig @@ -8,6 +8,7 @@ const std = @import("std"); const builtin = @import("builtin"); const math = std.math; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const maxInt = std.math.maxInt; const arch = builtin.cpu.arch; const common = @import("common.zig"); @@ -179,36 +180,73 @@ pub fn log2l(x: c_longdouble) callconv(.c) c_longdouble { } } -test "log2_32" { - const epsilon = 0.000001; - - try expect(math.approxEqAbs(f32, log2f(0.2), -2.321928, epsilon)); - try expect(math.approxEqAbs(f32, log2f(0.8923), -0.164399, epsilon)); - try expect(math.approxEqAbs(f32, log2f(1.5), 0.584962, epsilon)); - try expect(math.approxEqAbs(f32, log2f(37.45), 5.226894, epsilon)); - try expect(math.approxEqAbs(f32, log2f(123123.234375), 16.909744, epsilon)); -} - -test "log2_64" { - const epsilon = 0.000001; - - try expect(math.approxEqAbs(f64, log2(0.2), -2.321928, epsilon)); - try expect(math.approxEqAbs(f64, log2(0.8923), -0.164399, epsilon)); - try expect(math.approxEqAbs(f64, log2(1.5), 0.584962, epsilon)); - try expect(math.approxEqAbs(f64, log2(37.45), 5.226894, epsilon)); - try expect(math.approxEqAbs(f64, log2(123123.234375), 16.909744, epsilon)); -} - -test "log2_32.special" { - try expect(math.isPositiveInf(log2f(math.inf(f32)))); - try expect(math.isNegativeInf(log2f(0.0))); +test "log2f() special" { + try expectEqual(log2f(0.0), -math.inf(f32)); + try expectEqual(log2f(-0.0), -math.inf(f32)); + try expectEqual(log2f(1.0), 0.0); + try expectEqual(log2f(2.0), 1.0); + try expectEqual(log2f(math.inf(f32)), math.inf(f32)); try expect(math.isNan(log2f(-1.0))); + try expect(math.isNan(log2f(-math.inf(f32)))); try expect(math.isNan(log2f(math.nan(f32)))); + try expect(math.isNan(log2f(math.snan(f32)))); } -test "log2_64.special" { - try expect(math.isPositiveInf(log2(math.inf(f64)))); - try expect(math.isNegativeInf(log2(0.0))); - try expect(math.isNan(log2(-1.0))); - try expect(math.isNan(log2(math.nan(f64)))); +test "log2f() sanity" { + try expect(math.isNan(log2f(-0x1.0223a0p+3))); + try expectEqual(log2f(0x1.161868p+2), 0x1.0f49acp+1); + try expect(math.isNan(log2f(-0x1.0c34b4p+3))); + try expect(math.isNan(log2f(-0x1.a206f0p+2))); + try expectEqual(log2f(0x1.288bbcp+3), 0x1.9b2676p+1); + try expectEqual(log2f(0x1.52efd0p-1), -0x1.30b494p-1); // Disagrees with GCC in last bit + try expect(math.isNan(log2f(-0x1.a05cc8p-2))); + try expectEqual(log2f(0x1.1f9efap-1), -0x1.a9f89ap-1); + try expectEqual(log2f(0x1.8c5db0p-1), -0x1.7a2c96p-2); + try expect(math.isNan(log2f(-0x1.5b86eap-1))); +} + +test "log2f() boundary" { + try expectEqual(log2f(0x1.fffffep+127), 0x1p+7); // Max input value + try expectEqual(log2f(0x1p-149), -0x1.2ap+7); // Min positive input value + try expect(math.isNan(log2f(-0x1p-149))); // Min negative input value + try expectEqual(log2f(0x1.000002p+0), 0x1.715474p-23); // Last value before result reaches +0 + try expectEqual(log2f(0x1.fffffep-1), -0x1.715478p-24); // Last value before result reaches -0 + try expectEqual(log2f(0x1p-126), -0x1.f8p+6); // First subnormal + try expect(math.isNan(log2f(-0x1p-126))); // First negative subnormal + +} + +test "log2() special" { + try expectEqual(log2(0.0), -math.inf(f64)); + try expectEqual(log2(-0.0), -math.inf(f64)); + try expectEqual(log2(1.0), 0.0); + try expectEqual(log2(2.0), 1.0); + try expectEqual(log2(math.inf(f64)), math.inf(f64)); + try expect(math.isNan(log2(-1.0))); + try expect(math.isNan(log2(-math.inf(f64)))); + try expect(math.isNan(log2(math.nan(f64)))); + try expect(math.isNan(log2(math.snan(f64)))); +} + +test "log2() sanity" { + try expect(math.isNan(log2(-0x1.02239f3c6a8f1p+3))); + try expectEqual(log2(0x1.161868e18bc67p+2), 0x1.0f49ac3838580p+1); + try expect(math.isNan(log2(-0x1.0c34b3e01e6e7p+3))); + try expect(math.isNan(log2(-0x1.a206f0a19dcc4p+2))); + try expectEqual(log2(0x1.288bbb0d6a1e6p+3), 0x1.9b26760c2a57ep+1); + try expectEqual(log2(0x1.52efd0cd80497p-1), -0x1.30b490ef684c7p-1); + try expect(math.isNan(log2(-0x1.a05cc754481d1p-2))); + try expectEqual(log2(0x1.1f9ef934745cbp-1), -0x1.a9f89b5f5acb8p-1); + try expectEqual(log2(0x1.8c5db097f7442p-1), -0x1.7a2c947173f06p-2); + try expect(math.isNan(log2(-0x1.5b86ea8118a0ep-1))); +} + +test "log2() boundary" { + try expectEqual(log2(0x1.fffffffffffffp+1023), 0x1p+10); // Max input value + try expectEqual(log2(0x1p-1074), -0x1.0c8p+10); // Min positive input value + try expect(math.isNan(log2(-0x1p-1074))); // Min negative input value + try expectEqual(log2(0x1.0000000000001p+0), 0x1.71547652b82fdp-52); // Last value before result reaches +0 + try expectEqual(log2(0x1.fffffffffffffp-1), -0x1.71547652b82fep-53); // Last value before result reaches -0 + try expectEqual(log2(0x1p-1022), -0x1.ffp+9); // First subnormal + try expect(math.isNan(log2(-0x1p-1022))); // First negative subnormal } From 36d5392f03f4a8aa8de82ff0541e657d0abc24f8 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 22:28:53 +0100 Subject: [PATCH 19/46] Add tests for log10() --- lib/compiler_rt/log10.zig | 95 +++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/lib/compiler_rt/log10.zig b/lib/compiler_rt/log10.zig index f80c514ff6..130c437ef9 100644 --- a/lib/compiler_rt/log10.zig +++ b/lib/compiler_rt/log10.zig @@ -7,7 +7,8 @@ const std = @import("std"); const builtin = @import("builtin"); const math = std.math; -const testing = std.testing; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const maxInt = std.math.maxInt; const arch = builtin.cpu.arch; const common = @import("common.zig"); @@ -187,38 +188,74 @@ pub fn log10l(x: c_longdouble) callconv(.c) c_longdouble { } } -test "log10_32" { - const epsilon = 0.000001; - - try testing.expect(math.approxEqAbs(f32, log10f(0.2), -0.698970, epsilon)); - try testing.expect(math.approxEqAbs(f32, log10f(0.8923), -0.049489, epsilon)); - try testing.expect(math.approxEqAbs(f32, log10f(1.5), 0.176091, epsilon)); - try testing.expect(math.approxEqAbs(f32, log10f(37.45), 1.573452, epsilon)); - try testing.expect(math.approxEqAbs(f32, log10f(89.123), 1.94999, epsilon)); - try testing.expect(math.approxEqAbs(f32, log10f(123123.234375), 5.09034, epsilon)); +test "log10f() special" { + try expectEqual(log10f(0.0), -math.inf(f32)); + try expectEqual(log10f(-0.0), -math.inf(f32)); + try expectEqual(log10f(1.0), 0.0); + try expectEqual(log10f(10.0), 1.0); + try expectEqual(log10f(0.1), -1.0); + try expectEqual(log10f(math.inf(f32)), math.inf(f32)); + try expect(math.isNan(log10f(-1.0))); + try expect(math.isNan(log10f(-math.inf(f32)))); + try expect(math.isNan(log10f(math.nan(f32)))); + try expect(math.isNan(log10f(math.snan(f32)))); } -test "log10_64" { - const epsilon = 0.000001; - - try testing.expect(math.approxEqAbs(f64, log10(0.2), -0.698970, epsilon)); - try testing.expect(math.approxEqAbs(f64, log10(0.8923), -0.049489, epsilon)); - try testing.expect(math.approxEqAbs(f64, log10(1.5), 0.176091, epsilon)); - try testing.expect(math.approxEqAbs(f64, log10(37.45), 1.573452, epsilon)); - try testing.expect(math.approxEqAbs(f64, log10(89.123), 1.94999, epsilon)); - try testing.expect(math.approxEqAbs(f64, log10(123123.234375), 5.09034, epsilon)); +test "log10f() sanity" { + try expect(math.isNan(log10f(-0x1.0223a0p+3))); + try expectEqual(log10f(0x1.161868p+2), 0x1.46a9bcp-1); + try expect(math.isNan(log10f(-0x1.0c34b4p+3))); + try expect(math.isNan(log10f(-0x1.a206f0p+2))); + try expectEqual(log10f(0x1.288bbcp+3), 0x1.ef1300p-1); + try expectEqual(log10f(0x1.52efd0p-1), -0x1.6ee6dcp-3); // Disagrees with GCC in last bit + try expect(math.isNan(log10f(-0x1.a05cc8p-2))); + try expectEqual(log10f(0x1.1f9efap-1), -0x1.0075ccp-2); + try expectEqual(log10f(0x1.8c5db0p-1), -0x1.c75df8p-4); + try expect(math.isNan(log10f(-0x1.5b86eap-1))); } -test "log10_32.special" { - try testing.expect(math.isPositiveInf(log10f(math.inf(f32)))); - try testing.expect(math.isNegativeInf(log10f(0.0))); - try testing.expect(math.isNan(log10f(-1.0))); - try testing.expect(math.isNan(log10f(math.nan(f32)))); +test "log10f() boundary" { + try expectEqual(log10f(0x1.fffffep+127), 0x1.344136p+5); // Max input value + try expectEqual(log10f(0x1p-149), -0x1.66d3e8p+5); // Min positive input value + try expect(math.isNan(log10f(-0x1p-149))); // Min negative input value + try expectEqual(log10f(0x1.000002p+0), 0x1.bcb7b0p-25); // Last value before result reaches +0 + try expectEqual(log10f(0x1.fffffep-1), -0x1.bcb7b2p-26); // Last value before result reaches -0 + try expectEqual(log10f(0x1p-126), -0x1.2f7030p+5); // First subnormal + try expect(math.isNan(log10f(-0x1p-126))); // First negative subnormal } -test "log10_64.special" { - try testing.expect(math.isPositiveInf(log10(math.inf(f64)))); - try testing.expect(math.isNegativeInf(log10(0.0))); - try testing.expect(math.isNan(log10(-1.0))); - try testing.expect(math.isNan(log10(math.nan(f64)))); +test "log10() special" { + try expectEqual(log10(0.0), -math.inf(f64)); + try expectEqual(log10(-0.0), -math.inf(f64)); + try expectEqual(log10(1.0), 0.0); + try expectEqual(log10(10.0), 1.0); + try expectEqual(log10(0.1), -1.0); + try expectEqual(log10(math.inf(f64)), math.inf(f64)); + try expect(math.isNan(log10(-1.0))); + try expect(math.isNan(log10(-math.inf(f64)))); + try expect(math.isNan(log10(math.nan(f64)))); + try expect(math.isNan(log10(math.snan(f64)))); +} + +test "log10() sanity" { + try expect(math.isNan(log10(-0x1.02239f3c6a8f1p+3))); + try expectEqual(log10(0x1.161868e18bc67p+2), 0x1.46a9bd1d2eb87p-1); + try expect(math.isNan(log10(-0x1.0c34b3e01e6e7p+3))); + try expect(math.isNan(log10(-0x1.a206f0a19dcc4p+2))); + try expectEqual(log10(0x1.288bbb0d6a1e6p+3), 0x1.ef12fff994862p-1); + try expectEqual(log10(0x1.52efd0cd80497p-1), -0x1.6ee6db5a155cbp-3); + try expect(math.isNan(log10(-0x1.a05cc754481d1p-2))); + try expectEqual(log10(0x1.1f9ef934745cbp-1), -0x1.0075cda79d321p-2); + try expectEqual(log10(0x1.8c5db097f7442p-1), -0x1.c75df6442465ap-4); + try expect(math.isNan(log10(-0x1.5b86ea8118a0ep-1))); +} + +test "log10() boundary" { + try expectEqual(log10(0x1.fffffffffffffp+1023), 0x1.34413509f79ffp+8); // Max input value + try expectEqual(log10(0x1p-1074), -0x1.434e6420f4374p+8); // Min positive input value + try expect(math.isNan(log10(-0x1p-1074))); // Min negative input value + try expectEqual(log10(0x1.0000000000001p+0), 0x1.bcb7b1526e50dp-54); // Last value before result reaches +0 + try expectEqual(log10(0x1.fffffffffffffp-1), -0x1.bcb7b1526e50fp-55); // Last value before result reaches -0 + try expectEqual(log10(0x1p-1022), -0x1.33a7146f72a42p+8); // First subnormal + try expect(math.isNan(log10(-0x1p-1022))); // First negative subnormal } From f34b26231ee5c6d4810b333617940bbd682e1a36 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 22:42:53 +0100 Subject: [PATCH 20/46] Add tests for math.log1p() --- lib/std/math/log1p.zig | 106 +++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/lib/std/math/log1p.zig b/lib/std/math/log1p.zig index 2d7507bc88..4dd7bda2e2 100644 --- a/lib/std/math/log1p.zig +++ b/lib/std/math/log1p.zig @@ -8,6 +8,7 @@ const std = @import("../std.zig"); const math = std.math; const mem = std.mem; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; /// Returns the natural logarithm of 1 + x with greater accuracy when x is near zero. /// @@ -182,49 +183,72 @@ fn log1p_64(x: f64) f64 { return s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi; } -test log1p { - try expect(log1p(@as(f32, 0.0)) == log1p_32(0.0)); - try expect(log1p(@as(f64, 0.0)) == log1p_64(0.0)); -} - -test log1p_32 { - const epsilon = 0.000001; - - try expect(math.approxEqAbs(f32, log1p_32(0.0), 0.0, epsilon)); - try expect(math.approxEqAbs(f32, log1p_32(0.2), 0.182322, epsilon)); - try expect(math.approxEqAbs(f32, log1p_32(0.8923), 0.637793, epsilon)); - try expect(math.approxEqAbs(f32, log1p_32(1.5), 0.916291, epsilon)); - try expect(math.approxEqAbs(f32, log1p_32(37.45), 3.649359, epsilon)); - try expect(math.approxEqAbs(f32, log1p_32(89.123), 4.501175, epsilon)); - try expect(math.approxEqAbs(f32, log1p_32(123123.234375), 11.720949, epsilon)); -} - -test log1p_64 { - const epsilon = 0.000001; - - try expect(math.approxEqAbs(f64, log1p_64(0.0), 0.0, epsilon)); - try expect(math.approxEqAbs(f64, log1p_64(0.2), 0.182322, epsilon)); - try expect(math.approxEqAbs(f64, log1p_64(0.8923), 0.637793, epsilon)); - try expect(math.approxEqAbs(f64, log1p_64(1.5), 0.916291, epsilon)); - try expect(math.approxEqAbs(f64, log1p_64(37.45), 3.649359, epsilon)); - try expect(math.approxEqAbs(f64, log1p_64(89.123), 4.501175, epsilon)); - try expect(math.approxEqAbs(f64, log1p_64(123123.234375), 11.720949, epsilon)); -} - -test "log1p_32.special" { - try expect(math.isPositiveInf(log1p_32(math.inf(f32)))); - try expect(math.isPositiveZero(log1p_32(0.0))); - try expect(math.isNegativeZero(log1p_32(-0.0))); - try expect(math.isNegativeInf(log1p_32(-1.0))); +test "log1p_32() special" { + try expectEqual(log1p_32(0.0), 0.0); + try expectEqual(log1p_32(-0.0), 0.0); + try expectEqual(log1p_32(-1.0), -math.inf(f32)); + try expectEqual(log1p_32(1.0), math.ln2); + try expectEqual(log1p_32(math.inf(f32)), math.inf(f32)); try expect(math.isNan(log1p_32(-2.0))); + try expect(math.isNan(log1p_32(-math.inf(f32)))); try expect(math.isNan(log1p_32(math.nan(f32)))); + try expect(math.isNan(log1p_32(math.snan(f32)))); } -test "log1p_64.special" { - try expect(math.isPositiveInf(log1p_64(math.inf(f64)))); - try expect(math.isPositiveZero(log1p_64(0.0))); - try expect(math.isNegativeZero(log1p_64(-0.0))); - try expect(math.isNegativeInf(log1p_64(-1.0))); - try expect(math.isNan(log1p_64(-2.0))); - try expect(math.isNan(log1p_64(math.nan(f64)))); +test "log1p_32() sanity" { + try expect(math.isNan(log1p_32(-0x1.0223a0p+3))); + try expectEqual(log1p_32(0x1.161868p+2), 0x1.ad1bdcp+0); + try expect(math.isNan(log1p_32(-0x1.0c34b4p+3))); + try expect(math.isNan(log1p_32(-0x1.a206f0p+2))); + try expectEqual(log1p_32(0x1.288bbcp+3), 0x1.2a1ab8p+1); + try expectEqual(log1p_32(0x1.52efd0p-1), 0x1.041a4ep-1); + try expectEqual(log1p_32(-0x1.a05cc8p-2), -0x1.0b3596p-1); + try expectEqual(log1p_32(0x1.1f9efap-1), 0x1.c88344p-2); + try expectEqual(log1p_32(0x1.8c5db0p-1), 0x1.258a8ep-1); + try expectEqual(log1p_32(-0x1.5b86eap-1), -0x1.22b542p+0); +} + +test "log1p_32() boundary" { + try expectEqual(log1p_32(0x1.fffffep+127), 0x1.62e430p+6); // Max input value + try expectEqual(log1p_32(0x1p-149), 0x1p-149); // Min positive input value + try expectEqual(log1p_32(-0x1p-149), -0x1p-149); // Min negative input value + try expectEqual(log1p_32(0x1p-126), 0x1p-126); // First subnormal + try expectEqual(log1p_32(-0x1p-126), -0x1p-126); // First negative subnormal + try expectEqual(log1p_32(-0x1.fffffep-1), -0x1.0a2b24p+4); // Last value before result is -inf + try expect(math.isNan(log1p_32(-0x1.000002p+0))); // First value where result is nan +} + +test "log1p_64() special" { + try expectEqual(log1p_64(0.0), 0.0); + try expectEqual(log1p_64(-0.0), 0.0); + try expectEqual(log1p_64(-1.0), -math.inf(f64)); + try expectEqual(log1p_64(1.0), math.ln2); + try expectEqual(log1p_64(math.inf(f64)), math.inf(f64)); + try expect(math.isNan(log1p_64(-2.0))); + try expect(math.isNan(log1p_64(-math.inf(f64)))); + try expect(math.isNan(log1p_64(math.nan(f64)))); + try expect(math.isNan(log1p_64(math.snan(f64)))); +} + +test "log1p_64() sanity" { + try expect(math.isNan(log1p_64(-0x1.02239f3c6a8f1p+3))); + try expectEqual(log1p_64(0x1.161868e18bc67p+2), 0x1.ad1bdd1e9e686p+0); // Disagrees with GCC in last bit + try expect(math.isNan(log1p_64(-0x1.0c34b3e01e6e7p+3))); + try expect(math.isNan(log1p_64(-0x1.a206f0a19dcc4p+2))); + try expectEqual(log1p_64(0x1.288bbb0d6a1e6p+3), 0x1.2a1ab8365b56fp+1); + try expectEqual(log1p_64(0x1.52efd0cd80497p-1), 0x1.041a4ec2a680ap-1); + try expectEqual(log1p_64(-0x1.a05cc754481d1p-2), -0x1.0b3595423aec1p-1); + try expectEqual(log1p_64(0x1.1f9ef934745cbp-1), 0x1.c8834348a846ep-2); + try expectEqual(log1p_64(0x1.8c5db097f7442p-1), 0x1.258a8e8a35bbfp-1); + try expectEqual(log1p_64(-0x1.5b86ea8118a0ep-1), -0x1.22b5426327502p+0); +} + +test "log1p_64() boundary" { + try expectEqual(log1p_64(0x1.fffffffffffffp+1023), 0x1.62e42fefa39efp+9); // Max input value + try expectEqual(log1p_64(0x1p-1074), 0x1p-1074); // Min positive input value + try expectEqual(log1p_64(-0x1p-1074), -0x1p-1074); // Min negative input value + try expectEqual(log1p_64(0x1p-1022), 0x1p-1022); // First subnormal + try expectEqual(log1p_64(-0x1p-1022), -0x1p-1022); // First negative subnormal + try expectEqual(log1p_64(-0x1.fffffffffffffp-1), -0x1.25e4f7b2737fap+5); // Last value before result is -inf + try expect(math.isNan(log1p_64(-0x1.0000000000001p+0))); // First value where result is nan } From 7abb170f59ed959c4004ec37258c8905903f36b1 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 22:50:56 +0100 Subject: [PATCH 21/46] Add tests for math.expm1(), fixing bug in 32-bit function --- lib/std/math/expm1.zig | 109 ++++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/lib/std/math/expm1.zig b/lib/std/math/expm1.zig index c64a123631..048bd57cba 100644 --- a/lib/std/math/expm1.zig +++ b/lib/std/math/expm1.zig @@ -10,6 +10,7 @@ const std = @import("../std.zig"); const math = std.math; const mem = std.mem; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; /// Returns e raised to the power of x, minus 1 (e^x - 1). This is more accurate than exp(e, x) - 1 /// when x is near 0. @@ -39,9 +40,9 @@ fn expm1_32(x_: f32) f32 { const Q2: f32 = 1.5807170421e-3; var x = x_; - const ux = @as(u32, @bitCast(x)); + const ux: u32 = @bitCast(x); const hx = ux & 0x7FFFFFFF; - const sign = hx >> 31; + const sign = ux >> 31; // TODO: Shouldn't need this check explicitly. if (math.isNegativeInf(x)) { @@ -147,7 +148,7 @@ fn expm1_32(x_: f32) f32 { return y - 1.0; } - const uf = @as(f32, @bitCast(@as(u32, @intCast(0x7F -% k)) << 23)); + const uf: f32 = @bitCast(@as(u32, @intCast(0x7F -% k)) << 23); if (k < 23) { return (x - e + (1 - uf)) * twopk; } else { @@ -286,39 +287,77 @@ fn expm1_64(x_: f64) f64 { } } -test expm1 { - try expect(expm1(@as(f32, 0.0)) == expm1_32(0.0)); - try expect(expm1(@as(f64, 0.0)) == expm1_64(0.0)); -} - -test expm1_32 { - const epsilon = 0.000001; - - try expect(math.isPositiveZero(expm1_32(0.0))); - try expect(math.approxEqAbs(f32, expm1_32(0.0), 0.0, epsilon)); - try expect(math.approxEqAbs(f32, expm1_32(0.2), 0.221403, epsilon)); - try expect(math.approxEqAbs(f32, expm1_32(0.8923), 1.440737, epsilon)); - try expect(math.approxEqAbs(f32, expm1_32(1.5), 3.481689, epsilon)); -} - -test expm1_64 { - const epsilon = 0.000001; - - try expect(math.isPositiveZero(expm1_64(0.0))); - try expect(math.approxEqAbs(f64, expm1_64(0.0), 0.0, epsilon)); - try expect(math.approxEqAbs(f64, expm1_64(0.2), 0.221403, epsilon)); - try expect(math.approxEqAbs(f64, expm1_64(0.8923), 1.440737, epsilon)); - try expect(math.approxEqAbs(f64, expm1_64(1.5), 3.481689, epsilon)); -} - -test "expm1_32.special" { - try expect(math.isPositiveInf(expm1_32(math.inf(f32)))); - try expect(expm1_32(-math.inf(f32)) == -1.0); +test "expm1_32() special" { + try expectEqual(expm1_32(0.0), 0.0); + try expectEqual(expm1_32(-0.0), 0.0); + try expectEqual(expm1_32(math.ln2), 1.0); + try expectEqual(expm1_32(math.inf(f32)), math.inf(f32)); + try expectEqual(expm1_32(-math.inf(f32)), -1.0); try expect(math.isNan(expm1_32(math.nan(f32)))); + try expect(math.isNan(expm1_32(math.snan(f32)))); } -test "expm1_64.special" { - try expect(math.isPositiveInf(expm1_64(math.inf(f64)))); - try expect(expm1_64(-math.inf(f64)) == -1.0); - try expect(math.isNan(expm1_64(math.nan(f64)))); +test "expm1_32() sanity" { + try expectEqual(expm1_32(-0x1.0223a0p+3), -0x1.ffd6e0p-1); + try expectEqual(expm1_32(0x1.161868p+2), 0x1.30712ap+6); + try expectEqual(expm1_32(-0x1.0c34b4p+3), -0x1.ffe1fap-1); + try expectEqual(expm1_32(-0x1.a206f0p+2), -0x1.ff4116p-1); + try expectEqual(expm1_32(0x1.288bbcp+3), 0x1.4ab480p+13); // Disagrees with GCC in last bit + try expectEqual(expm1_32(0x1.52efd0p-1), 0x1.e09536p-1); + try expectEqual(expm1_32(-0x1.a05cc8p-2), -0x1.561c3ep-2); + try expectEqual(expm1_32(0x1.1f9efap-1), 0x1.81ec4ep-1); + try expectEqual(expm1_32(0x1.8c5db0p-1), 0x1.2b3364p+0); + try expectEqual(expm1_32(-0x1.5b86eap-1), -0x1.f8951ap-2); +} + +test "expm1_32() boundary" { + // TODO: The last value before inf is actually 0x1.62e300p+6 -> 0x1.ff681ep+127 + // try expectEqual(expm1_32(0x1.62e42ep+6), 0x1.ffff08p+127); // Last value before result is inf + try expectEqual(expm1_32(0x1.62e430p+6), math.inf(f32)); // First value that gives inf + try expectEqual(expm1_32(0x1.fffffep+127), math.inf(f32)); // Max input value + try expectEqual(expm1_32(0x1p-149), 0x1p-149); // Min positive input value + try expectEqual(expm1_32(-0x1p-149), -0x1p-149); // Min negative input value + try expectEqual(expm1_32(0x1p-126), 0x1p-126); // First positive subnormal input + try expectEqual(expm1_32(-0x1p-126), -0x1p-126); // First negative subnormal input + try expectEqual(expm1_32(0x1.fffffep-125), 0x1.fffffep-125); // Last positive value before subnormal + try expectEqual(expm1_32(-0x1.fffffep-125), -0x1.fffffep-125); // Last negative value before subnormal + try expectEqual(expm1_32(-0x1.154244p+4), -0x1.fffffep-1); // Last value before result is -1 + try expectEqual(expm1_32(-0x1.154246p+4), -1); // First value where result is -1 +} + +test "expm1_64() special" { + try expectEqual(expm1_64(0.0), 0.0); + try expectEqual(expm1_64(-0.0), 0.0); + try expectEqual(expm1_64(math.ln2), 1.0); + try expectEqual(expm1_64(math.inf(f64)), math.inf(f64)); + try expectEqual(expm1_64(-math.inf(f64)), -1.0); + try expect(math.isNan(expm1_64(math.nan(f64)))); + try expect(math.isNan(expm1_64(math.snan(f64)))); +} + +test "expm1_64() sanity" { + try expectEqual(expm1_64(-0x1.02239f3c6a8f1p+3), -0x1.ffd6df9b02b3ep-1); + try expectEqual(expm1_64(0x1.161868e18bc67p+2), 0x1.30712ed238c04p+6); + try expectEqual(expm1_64(-0x1.0c34b3e01e6e7p+3), -0x1.ffe1f94e493e7p-1); + try expectEqual(expm1_64(-0x1.a206f0a19dcc4p+2), -0x1.ff4115c03f78dp-1); + try expectEqual(expm1_64(0x1.288bbb0d6a1e6p+3), 0x1.4ab477496e07ep+13); + try expectEqual(expm1_64(0x1.52efd0cd80497p-1), 0x1.e095382100a01p-1); + try expectEqual(expm1_64(-0x1.a05cc754481d1p-2), -0x1.561c3e0582be6p-2); + try expectEqual(expm1_64(0x1.1f9ef934745cbp-1), 0x1.81ec4cd4d4a8fp-1); + try expectEqual(expm1_64(0x1.8c5db097f7442p-1), 0x1.2b3363a944bf7p+0); + try expectEqual(expm1_64(-0x1.5b86ea8118a0ep-1), -0x1.f8951aebffbafp-2); +} + +test "expm1_64() boundary" { + try expectEqual(expm1_64(0x1.62e42fefa39efp+9), 0x1.fffffffffff2ap+1023); // Last value before result is inf + try expectEqual(expm1_64(0x1.62e42fefa39f0p+9), math.inf(f64)); // First value that gives inf + try expectEqual(expm1_64(0x1.fffffffffffffp+1023), math.inf(f64)); // Max input value + try expectEqual(expm1_64(0x1p-1074), 0x1p-1074); // Min positive input value + try expectEqual(expm1_64(-0x1p-1074), -0x1p-1074); // Min negative input value + try expectEqual(expm1_64(0x1p-1022), 0x1p-1022); // First positive subnormal input + try expectEqual(expm1_64(-0x1p-1022), -0x1p-1022); // First negative subnormal input + try expectEqual(expm1_64(0x1.fffffffffffffp-1021), 0x1.fffffffffffffp-1021); // Last positive value before subnormal + try expectEqual(expm1_64(-0x1.fffffffffffffp-1021), -0x1.fffffffffffffp-1021); // Last negative value before subnormal + try expectEqual(expm1_64(-0x1.2b708872320e1p+5), -0x1.fffffffffffffp-1); // Last value before result is -1 + try expectEqual(expm1_64(-0x1.2b708872320e2p+5), -1); // First value where result is -1 } From 03dfd2ecc37cc99b15d4ce3ff19147230ddc8fd4 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Sat, 25 May 2024 23:05:20 +0100 Subject: [PATCH 22/46] Make sure to test the sign of the zero results --- lib/compiler_rt/exp.zig | 4 ++-- lib/compiler_rt/exp2.zig | 4 ++-- lib/compiler_rt/log.zig | 4 ++-- lib/compiler_rt/log10.zig | 4 ++-- lib/compiler_rt/log2.zig | 4 ++-- lib/std/math/expm1.zig | 8 ++++---- lib/std/math/log1p.zig | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/compiler_rt/exp.zig b/lib/compiler_rt/exp.zig index 55b784770b..fa4356a336 100644 --- a/lib/compiler_rt/exp.zig +++ b/lib/compiler_rt/exp.zig @@ -218,7 +218,7 @@ test "expf() special" { try expectEqual(expf(1.0), math.e); try expectEqual(expf(math.ln2), 2.0); try expectEqual(expf(math.inf(f32)), math.inf(f32)); - try expectEqual(expf(-math.inf(f32)), 0.0); + try expect(math.isPositiveZero(expf(-math.inf(f32)))); try expect(math.isNan(expf(math.nan(f32)))); try expect(math.isNan(expf(math.snan(f32)))); } @@ -268,7 +268,7 @@ test "exp() special" { // try expectEqual(exp(1.0), math.e); try expectEqual(exp(math.ln2), 2.0); try expectEqual(exp(math.inf(f64)), math.inf(f64)); - try expectEqual(exp(-math.inf(f64)), 0.0); + try expect(math.isPositiveZero(exp(-math.inf(f64)))); try expect(math.isNan(exp(math.nan(f64)))); try expect(math.isNan(exp(math.snan(f64)))); } diff --git a/lib/compiler_rt/exp2.zig b/lib/compiler_rt/exp2.zig index e27bae8092..ce79dff497 100644 --- a/lib/compiler_rt/exp2.zig +++ b/lib/compiler_rt/exp2.zig @@ -464,7 +464,7 @@ test "exp2f() special" { try expectEqual(exp2f(1.0), 2.0); try expectEqual(exp2f(-1.0), 0.5); try expectEqual(exp2f(math.inf(f32)), math.inf(f32)); - try expectEqual(exp2f(-math.inf(f32)), 0.0); + try expect(math.isPositiveZero(exp2f(-math.inf(f32)))); try expect(math.isNan(exp2f(math.nan(f32)))); try expect(math.isNan(exp2f(math.snan(f32)))); } @@ -502,7 +502,7 @@ test "exp2() special" { try expectEqual(exp2(1.0), 2.0); try expectEqual(exp2(-1.0), 0.5); try expectEqual(exp2(math.inf(f64)), math.inf(f64)); - try expectEqual(exp2(-math.inf(f64)), 0.0); + try expect(math.isPositiveZero(exp2(-math.inf(f64)))); try expect(math.isNan(exp2(math.nan(f64)))); try expect(math.isNan(exp2(math.snan(f64)))); } diff --git a/lib/compiler_rt/log.zig b/lib/compiler_rt/log.zig index e7c98547e7..f8bbf430ac 100644 --- a/lib/compiler_rt/log.zig +++ b/lib/compiler_rt/log.zig @@ -163,7 +163,7 @@ pub fn logl(x: c_longdouble) callconv(.c) c_longdouble { test "logf() special" { try expectEqual(logf(0.0), -math.inf(f32)); try expectEqual(logf(-0.0), -math.inf(f32)); - try expectEqual(logf(1.0), 0.0); + try expect(math.isPositiveZero(logf(1.0))); try expectEqual(logf(math.e), 1.0); try expectEqual(logf(math.inf(f32)), math.inf(f32)); try expect(math.isNan(logf(-1.0))); @@ -198,7 +198,7 @@ test "logf() boundary" { test "log() special" { try expectEqual(log(0.0), -math.inf(f64)); try expectEqual(log(-0.0), -math.inf(f64)); - try expectEqual(log(1.0), 0.0); + try expect(math.isPositiveZero(log(1.0))); try expectEqual(log(math.e), 1.0); try expectEqual(log(math.inf(f64)), math.inf(f64)); try expect(math.isNan(log(-1.0))); diff --git a/lib/compiler_rt/log10.zig b/lib/compiler_rt/log10.zig index 130c437ef9..1c2ce4bbca 100644 --- a/lib/compiler_rt/log10.zig +++ b/lib/compiler_rt/log10.zig @@ -191,7 +191,7 @@ pub fn log10l(x: c_longdouble) callconv(.c) c_longdouble { test "log10f() special" { try expectEqual(log10f(0.0), -math.inf(f32)); try expectEqual(log10f(-0.0), -math.inf(f32)); - try expectEqual(log10f(1.0), 0.0); + try expect(math.isPositiveZero(log10f(1.0))); try expectEqual(log10f(10.0), 1.0); try expectEqual(log10f(0.1), -1.0); try expectEqual(log10f(math.inf(f32)), math.inf(f32)); @@ -227,7 +227,7 @@ test "log10f() boundary" { test "log10() special" { try expectEqual(log10(0.0), -math.inf(f64)); try expectEqual(log10(-0.0), -math.inf(f64)); - try expectEqual(log10(1.0), 0.0); + try expect(math.isPositiveZero(log10(1.0))); try expectEqual(log10(10.0), 1.0); try expectEqual(log10(0.1), -1.0); try expectEqual(log10(math.inf(f64)), math.inf(f64)); diff --git a/lib/compiler_rt/log2.zig b/lib/compiler_rt/log2.zig index 300eff4993..4cedcfe0c1 100644 --- a/lib/compiler_rt/log2.zig +++ b/lib/compiler_rt/log2.zig @@ -183,7 +183,7 @@ pub fn log2l(x: c_longdouble) callconv(.c) c_longdouble { test "log2f() special" { try expectEqual(log2f(0.0), -math.inf(f32)); try expectEqual(log2f(-0.0), -math.inf(f32)); - try expectEqual(log2f(1.0), 0.0); + try expect(math.isPositiveZero(log2f(1.0))); try expectEqual(log2f(2.0), 1.0); try expectEqual(log2f(math.inf(f32)), math.inf(f32)); try expect(math.isNan(log2f(-1.0))); @@ -219,7 +219,7 @@ test "log2f() boundary" { test "log2() special" { try expectEqual(log2(0.0), -math.inf(f64)); try expectEqual(log2(-0.0), -math.inf(f64)); - try expectEqual(log2(1.0), 0.0); + try expect(math.isPositiveZero(log2(1.0))); try expectEqual(log2(2.0), 1.0); try expectEqual(log2(math.inf(f64)), math.inf(f64)); try expect(math.isNan(log2(-1.0))); diff --git a/lib/std/math/expm1.zig b/lib/std/math/expm1.zig index 048bd57cba..7c57459271 100644 --- a/lib/std/math/expm1.zig +++ b/lib/std/math/expm1.zig @@ -288,8 +288,8 @@ fn expm1_64(x_: f64) f64 { } test "expm1_32() special" { - try expectEqual(expm1_32(0.0), 0.0); - try expectEqual(expm1_32(-0.0), 0.0); + try expect(math.isPositiveZero(expm1_32(0.0))); + try expect(math.isNegativeZero(expm1_32(-0.0))); try expectEqual(expm1_32(math.ln2), 1.0); try expectEqual(expm1_32(math.inf(f32)), math.inf(f32)); try expectEqual(expm1_32(-math.inf(f32)), -1.0); @@ -326,8 +326,8 @@ test "expm1_32() boundary" { } test "expm1_64() special" { - try expectEqual(expm1_64(0.0), 0.0); - try expectEqual(expm1_64(-0.0), 0.0); + try expect(math.isPositiveZero(expm1_64(0.0))); + try expect(math.isNegativeZero(expm1_64(-0.0))); try expectEqual(expm1_64(math.ln2), 1.0); try expectEqual(expm1_64(math.inf(f64)), math.inf(f64)); try expectEqual(expm1_64(-math.inf(f64)), -1.0); diff --git a/lib/std/math/log1p.zig b/lib/std/math/log1p.zig index 4dd7bda2e2..bd4139311c 100644 --- a/lib/std/math/log1p.zig +++ b/lib/std/math/log1p.zig @@ -184,8 +184,8 @@ fn log1p_64(x: f64) f64 { } test "log1p_32() special" { - try expectEqual(log1p_32(0.0), 0.0); - try expectEqual(log1p_32(-0.0), 0.0); + try expect(math.isPositiveZero(log1p_32(0.0))); + try expect(math.isNegativeZero(log1p_32(-0.0))); try expectEqual(log1p_32(-1.0), -math.inf(f32)); try expectEqual(log1p_32(1.0), math.ln2); try expectEqual(log1p_32(math.inf(f32)), math.inf(f32)); @@ -219,8 +219,8 @@ test "log1p_32() boundary" { } test "log1p_64() special" { - try expectEqual(log1p_64(0.0), 0.0); - try expectEqual(log1p_64(-0.0), 0.0); + try expect(math.isPositiveZero(log1p_64(0.0))); + try expect(math.isNegativeZero(log1p_64(-0.0))); try expectEqual(log1p_64(-1.0), -math.inf(f64)); try expectEqual(log1p_64(1.0), math.ln2); try expectEqual(log1p_64(math.inf(f64)), math.inf(f64)); From 5ef07302d7c22ae53594e34524bd02da4cc36f1b Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Fri, 19 Apr 2024 21:23:22 -0700 Subject: [PATCH 23/46] std.Build.Step.ConfigHeader: add the lazy file styled input as a dependency --- lib/std/Build/Step/ConfigHeader.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index cfff4b1c50..e27887693d 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -101,6 +101,9 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader { .generated_dir = .{ .step = &config_header.step }, }; + if (options.style.getPath()) |s| { + s.addStepDependencies(&config_header.step); + } return config_header; } From 27212a3e6bfe3cf988a12ba83ae7fbce186d960c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 14:26:23 -0700 Subject: [PATCH 24/46] LLD: don't default allow_shlib_undefined when cross compiling prevents e.g. lld-link: warning: undefined symbol: SystemFunction036 from being only a warning --- src/link/Lld.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 1aeeb5d214..296041822e 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -205,7 +205,6 @@ pub fn createEmpty( const target = &comp.root_mod.resolved_target.result; const output_mode = comp.config.output_mode; const optimize_mode = comp.root_mod.optimize_mode; - const is_native_os = comp.root_mod.resolved_target.is_native_os; const obj_file_ext: []const u8 = switch (target.ofmt) { .coff => "obj", @@ -234,7 +233,7 @@ pub fn createEmpty( .gc_sections = gc_sections, .print_gc_sections = options.print_gc_sections, .stack_size = stack_size, - .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os, + .allow_shlib_undefined = options.allow_shlib_undefined orelse false, .file = null, .build_id = options.build_id, }, From 9af076615e4078c98cb456b82a0dc081f70bc20c Mon Sep 17 00:00:00 2001 From: kcbanner Date: Thu, 17 Jul 2025 01:00:34 -0400 Subject: [PATCH 25/46] std.Progress: reset end when failing to flush stderr --- lib/std/Progress.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index c9a866b0c8..2634553d25 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -633,6 +633,7 @@ pub fn lockStderrWriter(buffer: []u8) *Writer { pub fn unlockStderrWriter() void { stderr_writer.flush() catch {}; + stderr_writer.end = 0; stderr_writer.buffer = &.{}; stderr_mutex.unlock(); } From 6e86910e194ccce076ee877a02b64e0762a270ab Mon Sep 17 00:00:00 2001 From: John Benediktsson Date: Thu, 17 Jul 2025 04:29:22 -0700 Subject: [PATCH 26/46] std.Io: Fix GenericReader.adaptToNewApi; add DeprecatedReader.adaptToNewApi (#24484) --- lib/std/Io.zig | 2 ++ lib/std/Io/DeprecatedReader.zig | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index d1efb3cbc2..ff6966d7f7 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -320,6 +320,8 @@ pub fn GenericReader( .new_interface = .{ .buffer = &.{}, .vtable = &.{ .stream = Adapter.stream }, + .seek = 0, + .end = 0, }, }; } diff --git a/lib/std/Io/DeprecatedReader.zig b/lib/std/Io/DeprecatedReader.zig index 3f2429c3ae..f6cb9f61d5 100644 --- a/lib/std/Io/DeprecatedReader.zig +++ b/lib/std/Io/DeprecatedReader.zig @@ -372,6 +372,34 @@ pub fn discard(self: Self) anyerror!u64 { } } +/// Helper for bridging to the new `Reader` API while upgrading. +pub fn adaptToNewApi(self: *const Self) Adapter { + return .{ + .derp_reader = self.*, + .new_interface = .{ + .buffer = &.{}, + .vtable = &.{ .stream = Adapter.stream }, + .seek = 0, + .end = 0, + }, + }; +} + +pub const Adapter = struct { + derp_reader: Self, + new_interface: std.io.Reader, + err: ?Error = null, + + fn stream(r: *std.io.Reader, w: *std.io.Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize { + const a: *@This() = @alignCast(@fieldParentPtr("new_interface", r)); + const buf = limit.slice(try w.writableSliceGreedy(1)); + return a.derp_reader.read(buf) catch |err| { + a.err = err; + return error.ReadFailed; + }; + } +}; + const std = @import("../std.zig"); const Self = @This(); const math = std.math; From 33041fdbe5fb0e10a0f18e1953343bf1fc2c4fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Jul 2025 14:12:05 +0200 Subject: [PATCH 27/46] ci: increase max rss for riscv64-linux back to 64G --- ci/riscv64-linux-debug.sh | 2 +- ci/riscv64-linux-release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/riscv64-linux-debug.sh b/ci/riscv64-linux-debug.sh index 564a7ca611..a8e39da0c2 100755 --- a/ci/riscv64-linux-debug.sh +++ b/ci/riscv64-linux-debug.sh @@ -50,7 +50,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-debug/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir docs \ - --maxrss 34359738368 \ + --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/riscv64-linux-release.sh b/ci/riscv64-linux-release.sh index a90335e8f2..47ef664a5b 100755 --- a/ci/riscv64-linux-release.sh +++ b/ci/riscv64-linux-release.sh @@ -50,7 +50,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-release/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir docs \ - --maxrss 34359738368 \ + --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ From b7d7446fbbfc35f291ffb641470233a7a9e18b9b Mon Sep 17 00:00:00 2001 From: kcbanner Date: Wed, 16 Jul 2025 23:42:35 -0400 Subject: [PATCH 28/46] compiler_rt: export all the chkstk variations on MinGW --- lib/compiler_rt/stack_probe.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/compiler_rt/stack_probe.zig b/lib/compiler_rt/stack_probe.zig index afc124196f..94212b7a23 100644 --- a/lib/compiler_rt/stack_probe.zig +++ b/lib/compiler_rt/stack_probe.zig @@ -13,11 +13,11 @@ comptime { // Default stack-probe functions emitted by LLVM if (builtin.target.isMinGW()) { @export(&_chkstk, .{ .name = "_alloca", .linkage = common.linkage, .visibility = common.visibility }); + @export(&__chkstk, .{ .name = "__chkstk", .linkage = common.linkage, .visibility = common.visibility }); + @export(&___chkstk, .{ .name = "__alloca", .linkage = common.linkage, .visibility = common.visibility }); + @export(&___chkstk, .{ .name = "___chkstk", .linkage = common.linkage, .visibility = common.visibility }); + @export(&__chkstk_ms, .{ .name = "__chkstk_ms", .linkage = common.linkage, .visibility = common.visibility }); @export(&___chkstk_ms, .{ .name = "___chkstk_ms", .linkage = common.linkage, .visibility = common.visibility }); - - if (arch == .thumb or arch == .aarch64) { - @export(&__chkstk, .{ .name = "__chkstk", .linkage = common.linkage, .visibility = common.visibility }); - } } else if (!builtin.link_libc) { // This symbols are otherwise exported by MSVCRT.lib @export(&_chkstk, .{ .name = "_chkstk", .linkage = common.linkage, .visibility = common.visibility }); From 86699acbb951ceed3ecdd3bd93a00ceb02028361 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 23:02:39 -0700 Subject: [PATCH 29/46] std.Io.Reader: update OneByteReader usage to std.testing.Reader --- lib/std/Io/Reader.zig | 30 +++++------------------------- lib/std/testing.zig | 10 ++++++---- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 8a177ea183..58ed0899a4 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -1702,33 +1702,13 @@ fn failingDiscard(r: *Reader, limit: Limit) Error!usize { } test "readAlloc when the backing reader provides one byte at a time" { - const OneByteReader = struct { - str: []const u8, - i: usize, - reader: Reader, - - fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize { - assert(@intFromEnum(limit) >= 1); - const self: *@This() = @fieldParentPtr("reader", r); - if (self.str.len - self.i == 0) return error.EndOfStream; - try w.writeByte(self.str[self.i]); - self.i += 1; - return 1; - } - }; const str = "This is a test"; var tiny_buffer: [1]u8 = undefined; - var one_byte_stream: OneByteReader = .{ - .str = str, - .i = 0, - .reader = .{ - .buffer = &tiny_buffer, - .vtable = &.{ .stream = OneByteReader.stream }, - .seek = 0, - .end = 0, - }, - }; - const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited); + var one_byte_stream: testing.Reader = .init(&tiny_buffer, &.{ + .{ .buffer = str }, + }); + one_byte_stream.artificial_limit = .limited(1); + const res = try one_byte_stream.interface.allocRemaining(std.testing.allocator, .unlimited); defer std.testing.allocator.free(res); try std.testing.expectEqualStrings(str, res); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 8e7ffd3f23..f9027a4f47 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1207,12 +1207,14 @@ pub inline fn fuzz( return @import("root").fuzz(context, testOne, options); } -/// A `std.io.Reader` that writes a predetermined list of buffers during `stream`. +/// A `std.Io.Reader` that writes a predetermined list of buffers during `stream`. pub const Reader = struct { calls: []const Call, - interface: std.io.Reader, + interface: std.Io.Reader, next_call_index: usize, next_offset: usize, + /// Further reduces how many bytes are written in each `stream` call. + artificial_limit: std.Io.Limit = .unlimited, pub const Call = struct { buffer: []const u8, @@ -1232,11 +1234,11 @@ pub const Reader = struct { }; } - fn stream(io_r: *std.io.Reader, w: *std.io.Writer, limit: std.io.Limit) std.io.Reader.StreamError!usize { + fn stream(io_r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize { const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); if (r.calls.len - r.next_call_index == 0) return error.EndOfStream; const call = r.calls[r.next_call_index]; - const buffer = limit.sliceConst(call.buffer[r.next_offset..]); + const buffer = r.artificial_limit.sliceConst(limit.sliceConst(call.buffer[r.next_offset..])); const n = try w.write(buffer); r.next_offset += n; if (call.buffer.len - r.next_offset == 0) { From 5784500572e3d12b0342cc5b09210c1d1f49edc5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 23:43:46 -0700 Subject: [PATCH 30/46] std.Io.Reader: fix readSliceShort with smaller buffer than Reader closes #24443 --- lib/std/Io/Reader.zig | 66 ++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 58ed0899a4..f2a1ec7287 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -593,48 +593,29 @@ pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void { /// See also: /// * `readSliceAll` pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize { - const in_buffer = r.buffer[r.seek..r.end]; - const copy_len = @min(buffer.len, in_buffer.len); - @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]); - if (buffer.len - copy_len == 0) { - r.seek += copy_len; - return buffer.len; - } - var i: usize = copy_len; - r.end = 0; - r.seek = 0; + var i: usize = 0; while (true) { + const buffer_contents = r.buffer[r.seek..r.end]; + const dest = buffer[i..]; + const copy_len = @min(dest.len, buffer_contents.len); + @memcpy(dest[0..copy_len], buffer_contents[0..copy_len]); + if (dest.len - copy_len == 0) { + @branchHint(.likely); + r.seek += copy_len; + return buffer.len; + } + i += copy_len; + r.end = 0; + r.seek = 0; const remaining = buffer[i..]; - var wrapper: Writer.VectorWrapper = .{ - .it = .{ - .first = remaining, - .last = r.buffer, - }, - .writer = .{ - .buffer = if (remaining.len >= r.buffer.len) remaining else r.buffer, - .vtable = Writer.VectorWrapper.vtable, - }, - }; - const n = r.vtable.stream(r, &wrapper.writer, .unlimited) catch |err| switch (err) { - error.WriteFailed => { - if (!wrapper.used) { - assert(r.seek == 0); - r.seek = remaining.len; - r.end = wrapper.writer.end; - @memcpy(remaining, r.buffer[0..remaining.len]); - } - return buffer.len; - }, + const new_remaining_len = readVecInner(r, &.{}, remaining, remaining.len) catch |err| switch (err) { error.EndOfStream => return i, error.ReadFailed => return error.ReadFailed, }; - if (n < remaining.len) { - i += n; - continue; - } - r.end = n - remaining.len; - return buffer.len; + if (new_remaining_len == 0) return buffer.len; + i += remaining.len - new_remaining_len; } + return buffer.len; } /// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing @@ -1640,6 +1621,19 @@ test readSliceShort { try testing.expectEqual(0, try r.readSliceShort(&buf)); } +test "readSliceShort with smaller buffer than Reader" { + var reader_buf: [15]u8 = undefined; + const str = "This is a test"; + var one_byte_stream: testing.Reader = .init(&reader_buf, &.{ + .{ .buffer = str }, + }); + one_byte_stream.artificial_limit = .limited(1); + + var buf: [14]u8 = undefined; + try testing.expectEqual(14, try one_byte_stream.interface.readSliceShort(&buf)); + try testing.expectEqualStrings(str, &buf); +} + test readVec { var r: Reader = .fixed(std.ascii.letters); var flat_buffer: [52]u8 = undefined; From 155ab56cc63d775cca77d7bedbcac6ee65558818 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 17 Jul 2025 09:33:25 -0700 Subject: [PATCH 31/46] std.zig.readSourceFileToEndAlloc: avoid resizing +1 on the ensure total capacity to account for the fact that we add a null byte before returning. thanks matklad --- lib/std/zig.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index d113129b7e..51eac1b0a6 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -536,7 +536,8 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader if (file_reader.getSize()) |size| { const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong; - try buffer.ensureTotalCapacityPrecise(gpa, casted_size); + // +1 to avoid resizing for the null byte added in toOwnedSliceSentinel below. + try buffer.ensureTotalCapacityPrecise(gpa, casted_size + 1); } else |_| {} try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size)); From e62e42f0d901fd1b053b22ecd14a42bdac7dbac4 Mon Sep 17 00:00:00 2001 From: John Benediktsson Date: Thu, 17 Jul 2025 09:42:53 -0700 Subject: [PATCH 32/46] std.io.Writer: remove requirement of a 2-byte buffer for extern unions (#24489) closes #24486 --- lib/std/Io/Writer.zig | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 0cebde1e3f..4b0e142fb0 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -618,10 +618,6 @@ pub fn writeAllPreserve(w: *Writer, preserve_length: usize, bytes: []const u8) E /// A user type may be a `struct`, `vector`, `union` or `enum` type. /// /// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`. -/// -/// Asserts `buffer` capacity of at least 2 if a union is printed. This -/// requirement could be lifted by adjusting the code, but if you trigger that -/// assertion it is a clue that you should probably be using a buffer. pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void { const ArgsType = @TypeOf(args); const args_type_info = @typeInfo(ArgsType); @@ -1257,14 +1253,13 @@ pub fn printValue( .@"extern", .@"packed" => { if (info.fields.len == 0) return w.writeAll(".{}"); try w.writeAll(".{ "); - inline for (info.fields) |field| { + inline for (info.fields, 1..) |field, i| { try w.writeByte('.'); try w.writeAll(field.name); try w.writeAll(" = "); try w.printValue(ANY, options, @field(value, field.name), max_depth - 1); - (try w.writableArray(2)).* = ", ".*; + try w.writeAll(if (i < info.fields.len) ", " else " }"); } - w.buffer[w.end - 2 ..][0..2].* = " }".*; }, } }, From df921939418ca489126edff12fc4940ef7aa6bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Jul 2025 19:34:46 +0200 Subject: [PATCH 33/46] ci: skip building docs on riscv64-linux --- ci/riscv64-linux-debug.sh | 2 +- ci/riscv64-linux-release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/riscv64-linux-debug.sh b/ci/riscv64-linux-debug.sh index a8e39da0c2..b022a9e67e 100755 --- a/ci/riscv64-linux-debug.sh +++ b/ci/riscv64-linux-debug.sh @@ -49,7 +49,7 @@ unset CXX ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. -stage3-debug/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir docs \ +stage3-debug/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir \ --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ diff --git a/ci/riscv64-linux-release.sh b/ci/riscv64-linux-release.sh index 47ef664a5b..72040505e2 100755 --- a/ci/riscv64-linux-release.sh +++ b/ci/riscv64-linux-release.sh @@ -49,7 +49,7 @@ unset CXX ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. -stage3-release/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir docs \ +stage3-release/bin/zig build test-cases test-modules test-unit test-standalone test-c-abi test-link test-stack-traces test-asm-link test-llvm-ir \ --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ From 7da4e30da7529307e471a8361d88ff9a3e8ab155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Jul 2025 19:37:42 +0200 Subject: [PATCH 34/46] ci: skip translate-c tests on riscv64-linux --- ci/riscv64-linux-debug.sh | 2 ++ ci/riscv64-linux-release.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ci/riscv64-linux-debug.sh b/ci/riscv64-linux-debug.sh index b022a9e67e..eb977974d0 100755 --- a/ci/riscv64-linux-debug.sh +++ b/ci/riscv64-linux-debug.sh @@ -53,6 +53,8 @@ stage3-debug/bin/zig build test-cases test-modules test-unit test-standalone tes --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-translate-c \ + -Dskip-run-translated-c \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" diff --git a/ci/riscv64-linux-release.sh b/ci/riscv64-linux-release.sh index 72040505e2..52b2bf3165 100755 --- a/ci/riscv64-linux-release.sh +++ b/ci/riscv64-linux-release.sh @@ -53,6 +53,8 @@ stage3-release/bin/zig build test-cases test-modules test-unit test-standalone t --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-translate-c \ + -Dskip-run-translated-c \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" From 32c9e5df894752a8fbc3b8c8a6e5fb0cfd5e8f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 17 Jul 2025 19:40:59 +0200 Subject: [PATCH 35/46] ci: skip single-threaded module tests on riscv64-linux --- ci/riscv64-linux-debug.sh | 1 + ci/riscv64-linux-release.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/ci/riscv64-linux-debug.sh b/ci/riscv64-linux-debug.sh index eb977974d0..4076ec2268 100755 --- a/ci/riscv64-linux-debug.sh +++ b/ci/riscv64-linux-debug.sh @@ -53,6 +53,7 @@ stage3-debug/bin/zig build test-cases test-modules test-unit test-standalone tes --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-single-threaded \ -Dskip-translate-c \ -Dskip-run-translated-c \ -Dtarget=native-native-musl \ diff --git a/ci/riscv64-linux-release.sh b/ci/riscv64-linux-release.sh index 52b2bf3165..78c398cab4 100755 --- a/ci/riscv64-linux-release.sh +++ b/ci/riscv64-linux-release.sh @@ -53,6 +53,7 @@ stage3-release/bin/zig build test-cases test-modules test-unit test-standalone t --maxrss 68719476736 \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-single-threaded \ -Dskip-translate-c \ -Dskip-run-translated-c \ -Dtarget=native-native-musl \ From 3ae0ba096d6ba9181a984d0745e1e079c67d62ff Mon Sep 17 00:00:00 2001 From: Felix Koppe Date: Fri, 18 Jul 2025 00:07:50 +0200 Subject: [PATCH 36/46] test: Restore and fix deleted tests that relied on intern pool types (#24422) --- .../compile_errors/@import_zon_bad_type.zig | 128 ++++++++++++++++++ .../anytype_param_requires_comptime.zig | 21 +++ .../bogus_method_call_on_slice.zig | 26 ++++ .../compile_errors/coerce_anon_struct.zig | 12 ++ test/cases/compile_errors/redundant_try.zig | 52 +++++++ test/cases/type_names.zig | 46 ++++--- 6 files changed, 265 insertions(+), 20 deletions(-) create mode 100644 test/cases/compile_errors/@import_zon_bad_type.zig create mode 100644 test/cases/compile_errors/anytype_param_requires_comptime.zig create mode 100644 test/cases/compile_errors/bogus_method_call_on_slice.zig create mode 100644 test/cases/compile_errors/coerce_anon_struct.zig create mode 100644 test/cases/compile_errors/redundant_try.zig diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig new file mode 100644 index 0000000000..7f8f718a74 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -0,0 +1,128 @@ +export fn testVoid() void { + const f: void = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInStruct() void { + const f: struct { f: [*]const u8 } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testError() void { + const f: struct { error{foo} } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInUnion() void { + const f: union(enum) { a: void, b: [*c]const u8 } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInVector() void { + const f: @Vector(0, [*c]const u8) = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInOpt() void { + const f: *const ?[*c]const u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testComptimeField() void { + const f: struct { comptime foo: ??u8 = null } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testEnumLiteral() void { + const f: @TypeOf(.foo) = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt1() void { + const f: ??u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt2() void { + const f: ?*const ?u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt3() void { + const f: *const ?*const ?*const u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testOpt() void { + const f: ?u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +const E = enum(u8) { _ }; +export fn testNonExhaustiveEnum() void { + const f: E = @import("zon/neg_inf.zon"); + _ = f; +} + +const U = union { foo: void }; +export fn testUntaggedUnion() void { + const f: U = @import("zon/neg_inf.zon"); + _ = f; +} + +const EU = union(enum) { foo: void }; +export fn testTaggedUnionVoid() void { + const f: EU = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testVisited() void { + const V = struct { + ?f32, // Adds `?f32` to the visited list + ??f32, // `?f32` is already visited, we need to detect the nested opt anyway + f32, + }; + const f: V = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testMutablePointer() void { + const f: *i32 = @import("zon/neg_inf.zon"); + _ = f; +} + +// error +// imports=zon/neg_inf.zon +// +// tmp.zig:2:29: error: type 'void' is not available in ZON +// tmp.zig:7:50: error: type '[*]const u8' is not available in ZON +// tmp.zig:7:50: note: ZON does not allow many-pointers +// tmp.zig:12:46: error: type 'error{foo}' is not available in ZON +// tmp.zig:17:65: error: type '[*c]const u8' is not available in ZON +// tmp.zig:17:65: note: ZON does not allow C pointers +// tmp.zig:22:49: error: type '[*c]const u8' is not available in ZON +// tmp.zig:22:49: note: ZON does not allow C pointers +// tmp.zig:27:45: error: type '[*c]const u8' is not available in ZON +// tmp.zig:27:45: note: ZON does not allow C pointers +// tmp.zig:32:61: error: type '??u8' is not available in ZON +// tmp.zig:32:61: note: ZON does not allow nested optionals +// tmp.zig:42:29: error: type '??u8' is not available in ZON +// tmp.zig:42:29: note: ZON does not allow nested optionals +// tmp.zig:47:36: error: type '?*const ?u8' is not available in ZON +// tmp.zig:47:36: note: ZON does not allow nested optionals +// tmp.zig:52:50: error: type '?*const ?*const u8' is not available in ZON +// tmp.zig:52:50: note: ZON does not allow nested optionals +// tmp.zig:85:26: error: type '??f32' is not available in ZON +// tmp.zig:85:26: note: ZON does not allow nested optionals +// tmp.zig:90:29: error: type '*i32' is not available in ZON +// tmp.zig:90:29: note: ZON does not allow mutable pointers +// neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' +// tmp.zig:37:38: note: imported here +// neg_inf.zon:1:1: error: expected type '?u8' +// tmp.zig:57:28: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.E' +// tmp.zig:63:26: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.U' +// tmp.zig:69:26: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.EU' +// tmp.zig:75:27: note: imported here diff --git a/test/cases/compile_errors/anytype_param_requires_comptime.zig b/test/cases/compile_errors/anytype_param_requires_comptime.zig new file mode 100644 index 0000000000..e1151a93c5 --- /dev/null +++ b/test/cases/compile_errors/anytype_param_requires_comptime.zig @@ -0,0 +1,21 @@ +const C = struct { + c: type, + b: u32, +}; +const S = struct { + fn foo(b: u32, c: anytype) void { + bar(C{ .c = c, .b = b }); + } + fn bar(_: anytype) void {} +}; + +pub export fn entry() void { + S.foo(0, u32); +} + +// error +// +//:7:25: error: unable to resolve comptime value +//:7:25: note: initializer of comptime-only struct 'tmp.C' must be comptime-known +//:2:8: note: struct requires comptime because of this field +//:2:8: note: types are not available at runtime diff --git a/test/cases/compile_errors/bogus_method_call_on_slice.zig b/test/cases/compile_errors/bogus_method_call_on_slice.zig new file mode 100644 index 0000000000..62adaa3d3e --- /dev/null +++ b/test/cases/compile_errors/bogus_method_call_on_slice.zig @@ -0,0 +1,26 @@ +var self = "aoeu"; + +fn f(m: []const u8) void { + m.copy(u8, self[0..], m); +} + +export fn entry() usize { + return @sizeOf(@TypeOf(&f)); +} + +pub export fn entry1() void { + .{}.bar(); +} + +const S = struct { foo: i32 }; +pub export fn entry2() void { + const x = S{ .foo = 1 }; + x.bar(); +} + +// error +// +// :4:6: error: no field or member function named 'copy' in '[]const u8' +// :12:8: error: no field or member function named 'bar' in '@TypeOf(.{})' +// :18:6: error: no field or member function named 'bar' in 'tmp.S' +// :15:11: note: struct declared here diff --git a/test/cases/compile_errors/coerce_anon_struct.zig b/test/cases/compile_errors/coerce_anon_struct.zig new file mode 100644 index 0000000000..5737f861f3 --- /dev/null +++ b/test/cases/compile_errors/coerce_anon_struct.zig @@ -0,0 +1,12 @@ +const A = struct { x: u32 }; +const T = struct { x: u32 }; +export fn foo() void { + const a = A{ .x = 123 }; + _ = @as(T, a); +} + +// error +// +// :5:16: error: expected type 'tmp.T', found 'tmp.A' +// :1:11: note: struct declared here +// :2:11: note: struct declared here diff --git a/test/cases/compile_errors/redundant_try.zig b/test/cases/compile_errors/redundant_try.zig new file mode 100644 index 0000000000..5472701ce0 --- /dev/null +++ b/test/cases/compile_errors/redundant_try.zig @@ -0,0 +1,52 @@ +const S = struct { x: u32 = 0 }; +const T = struct { []const u8 }; + +fn test0() !void { + const x: u8 = try 1; + _ = x; +} + +fn test1() !void { + const x: S = try .{}; + _ = x; +} + +fn test2() !void { + const x: S = try S{ .x = 123 }; + _ = x; +} + +fn test3() !void { + const x: S = try try S{ .x = 123 }; + _ = x; +} + +fn test4() !void { + const x: T = try .{"hello"}; + _ = x; +} + +fn test5() !void { + const x: error{Foo}!u32 = 123; + _ = try try x; +} + +comptime { + _ = &test0; + _ = &test1; + _ = &test2; + _ = &test3; + _ = &test4; + _ = &test5; +} + +// error +// +// :5:23: error: expected error union type, found 'comptime_int' +// :10:23: error: expected error union type, found '@TypeOf(.{})' +// :15:23: error: expected error union type, found 'tmp.S' +// :1:11: note: struct declared here +// :20:27: error: expected error union type, found 'tmp.S' +// :1:11: note: struct declared here +// :25:23: error: expected error union type, found 'struct { comptime *const [5:0]u8 = "hello" }' +// :31:13: error: expected error union type, found 'u32' diff --git a/test/cases/type_names.zig b/test/cases/type_names.zig index 6873e55641..45f9a1c790 100644 --- a/test/cases/type_names.zig +++ b/test/cases/type_names.zig @@ -46,14 +46,18 @@ const StructInStruct = struct { a: struct { b: u8 } }; const UnionInStruct = struct { a: union { b: u8 } }; const StructInUnion = union { a: struct { b: u8 } }; const UnionInUnion = union { a: union { b: u8 } }; -const StructInTuple = struct { struct { b: u8 } }; -const UnionInTuple = struct { union { b: u8 } }; +const InnerStruct = struct { b: u8 }; +const StructInTuple = struct { a: InnerStruct }; +const InnerUnion = union { b: u8 }; +const UnionInTuple = struct { a: InnerUnion }; export fn nestedTypes() void { @compileLog(@typeName(StructInStruct)); @compileLog(@typeName(UnionInStruct)); @compileLog(@typeName(StructInUnion)); @compileLog(@typeName(UnionInUnion)); + @compileLog(@typeName(StructInTuple)); + @compileLog(@typeName(UnionInTuple)); } // error @@ -61,22 +65,24 @@ export fn nestedTypes() void { // :8:5: error: found compile log statement // :19:5: note: also here // :39:5: note: also here -// :53:5: note: also here +// :55:5: note: also here // -// Compile Log Output: -// @as(*const [15:0]u8, "tmp.namespace.S") -// @as(*const [15:0]u8, "tmp.namespace.E") -// @as(*const [15:0]u8, "tmp.namespace.U") -// @as(*const [15:0]u8, "tmp.namespace.O") -// @as(*const [19:0]u8, "tmp.localVarValue.S") -// @as(*const [19:0]u8, "tmp.localVarValue.E") -// @as(*const [19:0]u8, "tmp.localVarValue.U") -// @as(*const [19:0]u8, "tmp.localVarValue.O") -// @as(*const [11:0]u8, "tmp.MakeS()") -// @as(*const [11:0]u8, "tmp.MakeE()") -// @as(*const [11:0]u8, "tmp.MakeU()") -// @as(*const [11:0]u8, "tmp.MakeO()") -// @as(*const [18:0]u8, "tmp.StructInStruct") -// @as(*const [17:0]u8, "tmp.UnionInStruct") -// @as(*const [17:0]u8, "tmp.StructInUnion") -// @as(*const [16:0]u8, "tmp.UnionInUnion") +//Compile Log Output: +//@as(*const [15:0]u8, "tmp.namespace.S") +//@as(*const [15:0]u8, "tmp.namespace.E") +//@as(*const [15:0]u8, "tmp.namespace.U") +//@as(*const [15:0]u8, "tmp.namespace.O") +//@as(*const [19:0]u8, "tmp.localVarValue.S") +//@as(*const [19:0]u8, "tmp.localVarValue.E") +//@as(*const [19:0]u8, "tmp.localVarValue.U") +//@as(*const [19:0]u8, "tmp.localVarValue.O") +//@as(*const [11:0]u8, "tmp.MakeS()") +//@as(*const [11:0]u8, "tmp.MakeE()") +//@as(*const [11:0]u8, "tmp.MakeU()") +//@as(*const [11:0]u8, "tmp.MakeO()") +//@as(*const [18:0]u8, "tmp.StructInStruct") +//@as(*const [17:0]u8, "tmp.UnionInStruct") +//@as(*const [17:0]u8, "tmp.StructInUnion") +//@as(*const [16:0]u8, "tmp.UnionInUnion") +//@as(*const [17:0]u8, "tmp.StructInTuple") +//@as(*const [16:0]u8, "tmp.UnionInTuple") From 6e5589866193a6bd73c75b126adeecd71924b6f5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 06:42:41 -0700 Subject: [PATCH 37/46] Compilation: refactor std.fs -> fs no functional change --- src/Compilation.zig | 102 ++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 461528c939..2e20c6edbe 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -684,7 +684,7 @@ pub const Directories = struct { global, }, wasi_preopens: switch (builtin.target.os.tag) { - .wasi => std.fs.wasi.Preopens, + .wasi => fs.wasi.Preopens, else => void, }, self_exe_path: switch (builtin.target.os.tag) { @@ -741,7 +741,7 @@ pub const Directories = struct { .local_cache = local_cache, }; } - fn openWasiPreopen(preopens: std.fs.wasi.Preopens, name: []const u8) Cache.Directory { + fn openWasiPreopen(preopens: fs.wasi.Preopens, name: []const u8) Cache.Directory { return .{ .path = if (std.mem.eql(u8, name, ".")) null else name, .handle = .{ @@ -755,8 +755,8 @@ pub const Directories = struct { }; const nonempty_path = if (path.len == 0) "." else path; const handle_or_err = switch (thing) { - .@"zig lib" => std.fs.cwd().openDir(nonempty_path, .{}), - .@"global cache", .@"local cache" => std.fs.cwd().makeOpenPath(nonempty_path, .{}), + .@"zig lib" => fs.cwd().openDir(nonempty_path, .{}), + .@"global cache", .@"local cache" => fs.cwd().makeOpenPath(nonempty_path, .{}), }; return .{ .path = if (path.len == 0) null else path, @@ -993,7 +993,7 @@ pub const CObject = struct { const source_line = source_line: { if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0; - const file = std.fs.cwd().openFile(file_name, .{}) catch break :source_line 0; + const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; defer file.close(); file.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; @@ -1067,7 +1067,7 @@ pub const CObject = struct { } }; - const file = try std.fs.cwd().openFile(path, .{}); + const file = try fs.cwd().openFile(path, .{}); defer file.close(); var br = std.io.bufferedReader(file.deprecatedReader()); const reader = br.reader(); @@ -1875,7 +1875,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { std.debug.lockStdErr(); defer std.debug.unlockStdErr(); - const stderr = std.fs.File.stderr().deprecatedWriter(); + const stderr = fs.File.stderr().deprecatedWriter(); nosuspend { stderr.print("compilation: {s}\n", .{options.root_name}) catch break :print; stderr.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; @@ -1901,7 +1901,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .manifest_dir = try options.dirs.local_cache.handle.makeOpenPath("h", .{}), }; // These correspond to std.zig.Server.Message.PathPrefix. - cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); + cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); cache.addPrefix(options.dirs.zig_lib); cache.addPrefix(options.dirs.local_cache); cache.addPrefix(options.dirs.global_cache); @@ -2192,7 +2192,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil comp.digest = hash.peekBin(); const digest = hash.final(); - const artifact_sub_dir = "o" ++ std.fs.path.sep_str ++ digest; + const artifact_sub_dir = "o" ++ fs.path.sep_str ++ digest; var artifact_dir = try options.dirs.local_cache.handle.makeOpenPath(artifact_sub_dir, .{}); errdefer artifact_dir.close(); const artifact_directory: Cache.Directory = .{ @@ -2604,11 +2604,11 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { // temporary directories; it doesn't have a real cache directory anyway. return; } - const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ comp.dirs.local_cache.path orelse ".", - std.fs.path.sep, + fs.path.sep, tmp_dir_sub_path, @errorName(err), }); @@ -2628,11 +2628,11 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void { if (whole.tmp_artifact_directory) |*tmp_dir| { tmp_dir.handle.close(); whole.tmp_artifact_directory = null; - const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| { log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{ comp.dirs.local_cache.path orelse ".", - std.fs.path.sep, + fs.path.sep, tmp_dir_sub_path, @errorName(err), }); @@ -2668,7 +2668,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { assert(none.tmp_artifact_directory == null); none.tmp_artifact_directory = d: { tmp_dir_rand_int = std.crypto.random.int(u64); - const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); break :d .{ .path = path, @@ -2735,7 +2735,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { // Compile the artifacts to a temporary directory. whole.tmp_artifact_directory = d: { tmp_dir_rand_int = std.crypto.random.int(u64); - const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); + const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); break :d .{ .path = path, @@ -2910,7 +2910,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { // Close tmp dir and link.File to avoid open handle during rename. whole.tmp_artifact_directory.?.handle.close(); whole.tmp_artifact_directory = null; - const s = std.fs.path.sep_str; + const s = fs.path.sep_str; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); const o_sub_path = "o" ++ s ++ hex_digest; renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| { @@ -2932,7 +2932,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { if (comp.bin_file) |lf| { lf.emit = .{ .root_dir = comp.dirs.local_cache, - .sub_path = try std.fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }), + .sub_path = try fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }), }; switch (need_writable_dance) { @@ -3105,7 +3105,7 @@ fn renameTmpIntoCache( ) !void { var seen_eaccess = false; while (true) { - std.fs.rename( + fs.rename( cache_directory.handle, tmp_dir_sub_path, cache_directory.handle, @@ -3931,7 +3931,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { // This AU is referenced and has a transitive compile error, meaning it referenced something with a compile error. // However, we haven't reported any such error. // This is a compiler bug. - const stderr = std.fs.File.stderr().deprecatedWriter(); + const stderr = fs.File.stderr().deprecatedWriter(); try stderr.writeAll("referenced transitive analysis errors, but none actually emitted\n"); try stderr.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}); while (ref) |r| { @@ -4842,7 +4842,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { defer out_dir.close(); for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| { - const basename = std.fs.path.basename(sub_path); + const basename = fs.path.basename(sub_path); comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| { comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{ sub_path, @@ -4878,7 +4878,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { } } -fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8, tar_file: std.fs.File) !void { +fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8, tar_file: fs.File) !void { const root = module.root; var mod_dir = d: { const root_dir, const sub_path = root.openInfo(comp.dirs); @@ -4973,7 +4973,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye }); const src_basename = "main.zig"; - const root_name = std.fs.path.stem(src_basename); + const root_name = fs.path.stem(src_basename); const dirs = comp.dirs.withoutLocalCache(); @@ -5068,13 +5068,13 @@ fn workerUpdateFile( prog_node: std.Progress.Node, wg: *WaitGroup, ) void { - const child_prog_node = prog_node.start(std.fs.path.basename(file.path.sub_path), 0); + const child_prog_node = prog_node.start(fs.path.basename(file.path.sub_path), 0); defer child_prog_node.end(); const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); pt.updateFile(file_index, file) catch |err| { - pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ std.fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) { + pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) { error.OutOfMemory => { comp.mutex.lock(); defer comp.mutex.unlock(); @@ -5239,7 +5239,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module const arena = arena_allocator.allocator(); const tmp_digest = man.hash.peek(); - const tmp_dir_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &tmp_digest }); + const tmp_dir_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &tmp_digest }); var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); defer zig_cache_tmp_dir.close(); const cimport_basename = "cimport.h"; @@ -5308,7 +5308,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module log.info("C import .d file: {s}", .{out_dep_path}); } - const dep_basename = std.fs.path.basename(out_dep_path); + const dep_basename = fs.path.basename(out_dep_path); try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); switch (comp.cache_use) { .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| { @@ -5321,7 +5321,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module const bin_digest = man.finalBin(); const hex_digest = Cache.binToHex(bin_digest); - const o_sub_path = "o" ++ std.fs.path.sep_str ++ hex_digest; + const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest; var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); @@ -5674,7 +5674,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const c_source_basename = std.fs.path.basename(c_object.src.src_path); + const c_source_basename = fs.path.basename(c_object.src.src_path); const child_progress_node = c_obj_prog_node.start(c_source_basename, 0); defer child_progress_node.end(); @@ -5687,7 +5687,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr const o_basename_noext = if (direct_o) comp.root_name else - c_source_basename[0 .. c_source_basename.len - std.fs.path.extension(c_source_basename).len]; + c_source_basename[0 .. c_source_basename.len - fs.path.extension(c_source_basename).len]; const target = comp.getTarget(); const o_ext = target.ofmt.fileExt(target.cpu.arch); @@ -5814,11 +5814,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } // Just to save disk space, we delete the files that are never needed again. - defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(std.fs.path.basename(diag_file_path)) catch |err| switch (err) { + defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(fs.path.basename(diag_file_path)) catch |err| switch (err) { error.FileNotFound => {}, // the file wasn't created due to an error we reported else => log.warn("failed to delete '{s}': {s}", .{ diag_file_path, @errorName(err) }), }; - defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(std.fs.path.basename(dep_file_path)) catch |err| switch (err) { + defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(fs.path.basename(dep_file_path)) catch |err| switch (err) { error.FileNotFound => {}, // the file wasn't created due to an error we reported else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }), }; @@ -5889,7 +5889,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr } if (out_dep_path) |dep_file_path| { - const dep_basename = std.fs.path.basename(dep_file_path); + const dep_basename = fs.path.basename(dep_file_path); // Add the files depended on to the cache system. try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); switch (comp.cache_use) { @@ -5909,11 +5909,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // Rename into place. const digest = man.final(); - const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); + const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); - const tmp_basename = std.fs.path.basename(out_obj_path); - try std.fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); + const tmp_basename = fs.path.basename(out_obj_path); + try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); break :blk digest; }; @@ -5935,7 +5935,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr .success = .{ .object_path = .{ .root_dir = comp.dirs.local_cache, - .sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, o_basename }), + .sub_path = try fs.path.join(gpa, &.{ "o", &digest, o_basename }), }, .lock = man.toOwnedLock(), }, @@ -5959,7 +5959,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 .rc => |rc_src| rc_src.src_path, .manifest => |src_path| src_path, }; - const src_basename = std.fs.path.basename(src_path); + const src_basename = fs.path.basename(src_path); log.debug("updating win32 resource: {s}", .{src_path}); @@ -5996,7 +5996,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // get the digest now and write the .res directly to the cache const digest = man.final(); - const o_sub_path = try std.fs.path.join(arena, &.{ "o", &digest }); + const o_sub_path = try fs.path.join(arena, &.{ "o", &digest }); var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); @@ -6082,7 +6082,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 _ = try man.addFile(rc_src.src_path, null); man.hash.addListOfBytes(rc_src.extra_flags); - const rc_basename_noext = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; + const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len]; const digest = if (try man.hit()) man.final() else blk: { var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); @@ -6127,7 +6127,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // Read depfile and update cache manifest { - const dep_basename = std.fs.path.basename(out_dep_path); + const dep_basename = fs.path.basename(out_dep_path); const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(arena, dep_basename, 50 * 1024 * 1024); defer arena.free(dep_file_contents); @@ -6155,11 +6155,11 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // Rename into place. const digest = man.final(); - const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); + const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest }); var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); - const tmp_basename = std.fs.path.basename(out_res_path); - try std.fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename); + const tmp_basename = fs.path.basename(out_res_path); + try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename); break :blk digest; }; @@ -6270,7 +6270,7 @@ fn spawnZigRc( } pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { - const s = std.fs.path.sep_str; + const s = fs.path.sep_str; const rand_int = std.crypto.random.int(u64); if (comp.dirs.local_cache.path) |p| { return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); @@ -6520,12 +6520,12 @@ pub fn addCCArgs( if (comp.config.link_libcpp) { try argv.append("-isystem"); - try argv.append(try std.fs.path.join(arena, &[_][]const u8{ + try argv.append(try fs.path.join(arena, &[_][]const u8{ comp.dirs.zig_lib.path.?, "libcxx", "include", })); try argv.append("-isystem"); - try argv.append(try std.fs.path.join(arena, &[_][]const u8{ + try argv.append(try fs.path.join(arena, &[_][]const u8{ comp.dirs.zig_lib.path.?, "libcxxabi", "include", })); @@ -6536,7 +6536,7 @@ pub fn addCCArgs( // However as noted by @dimenus, appending libc headers before compiler headers breaks // intrinsics and other compiler specific items. try argv.append("-isystem"); - try argv.append(try std.fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "include" })); + try argv.append(try fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "include" })); try argv.ensureUnusedCapacity(comp.libc_include_dir_list.len * 2); for (comp.libc_include_dir_list) |include_dir| { @@ -6554,7 +6554,7 @@ pub fn addCCArgs( if (comp.config.link_libunwind) { try argv.append("-isystem"); - try argv.append(try std.fs.path.join(arena, &[_][]const u8{ + try argv.append(try fs.path.join(arena, &[_][]const u8{ comp.dirs.zig_lib.path.?, "libunwind", "include", })); } @@ -7147,7 +7147,7 @@ fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) return (try crtFilePath(&comp.crt_files, basename)) orelse { const lci = comp.libc_installation orelse return error.LibCInstallationNotAvailable; const crt_dir_path = lci.crt_dir orelse return error.LibCInstallationMissingCrtDir; - const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename }); + const full_path = try fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename }); return Cache.Path.initCwd(full_path); }; } @@ -7211,7 +7211,7 @@ pub fn lockAndSetMiscFailure( pub fn dump_argv(argv: []const []const u8) void { std.debug.lockStdErr(); defer std.debug.unlockStdErr(); - const stderr = std.fs.File.stderr().deprecatedWriter(); + const stderr = fs.File.stderr().deprecatedWriter(); for (argv[0 .. argv.len - 1]) |arg| { nosuspend stderr.print("{s} ", .{arg}) catch return; } @@ -7542,7 +7542,7 @@ pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile { return .{ .full_object_path = .{ .root_dir = comp.dirs.local_cache, - .sub_path = try std.fs.path.join(comp.gpa, &.{ + .sub_path = try fs.path.join(comp.gpa, &.{ "o", &Cache.binToHex(comp.digest.?), comp.emit_bin.?, From 69cf40da600224734d39c6f64fb2e0905e42d54a Mon Sep 17 00:00:00 2001 From: AsmArtisan256 Date: Fri, 18 Jul 2025 20:07:05 +0100 Subject: [PATCH 38/46] std.os.uefi.protocol.file: fix getInfo() buffer alignment (#24496) * std.os.uefi.protocol.file: use @alignCast in getInfo() method to fix #24480 * std.os.uefi.protocol.file: pass alignment responsabilities to caller by redefining the buffer type instead of blindly calling @alignCast --- lib/std/os/uefi/protocol/file.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/uefi/protocol/file.zig b/lib/std/os/uefi/protocol/file.zig index f8802fa64f..0654dec14a 100644 --- a/lib/std/os/uefi/protocol/file.zig +++ b/lib/std/os/uefi/protocol/file.zig @@ -214,7 +214,7 @@ pub const File = extern struct { pub fn getInfo( self: *const File, comptime info: std.meta.Tag(Info), - buffer: []u8, + buffer: []align(@alignOf(@FieldType(Info, @tagName(info)))) u8, ) GetInfoError!*@FieldType(Info, @tagName(info)) { const InfoType = @FieldType(Info, @tagName(info)); From ec5c1fac63ee8778c3a412fc72942e4b0f30f475 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 16 Jul 2025 19:47:58 -0700 Subject: [PATCH 39/46] std.zig: finish updating to new I/O API --- lib/compiler/objcopy.zig | 18 +- lib/compiler/resinator/main.zig | 6 +- lib/compiler/test_runner.zig | 11 +- lib/std/zig.zig | 1 + lib/std/zig/LibCInstallation.zig | 7 +- lib/std/zig/Server.zig | 214 +++++++---------------- lib/std/zig/WindowsSdk.zig | 14 +- lib/std/zig/llvm.zig | 6 + lib/std/zig/llvm/BitcodeReader.zig | 28 +-- lib/std/zig/perf_test.zig | 14 +- lib/std/zig/system/linux.zig | 35 ++-- src/Compilation.zig | 85 +++++----- src/deprecated.zig | 262 ----------------------------- src/main.zig | 46 ++--- 14 files changed, 201 insertions(+), 546 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 98bf5d2bcf..52ffe208f6 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -10,6 +10,9 @@ const assert = std.debug.assert; const fatal = std.process.fatal; const Server = std.zig.Server; +var stdin_buffer: [1024]u8 = undefined; +var stdout_buffer: [1024]u8 = undefined; + pub fn main() !void { var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena_instance.deinit(); @@ -22,11 +25,8 @@ pub fn main() !void { return cmdObjCopy(gpa, arena, args[1..]); } -fn cmdObjCopy( - gpa: Allocator, - arena: Allocator, - args: []const []const u8, -) !void { +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; @@ -225,13 +225,13 @@ fn cmdObjCopy( } if (listen) { + var stdin_reader = fs.File.stdin().reader(&stdin_buffer); + var stdout_writer = fs.File.stdout().writer(&stdout_buffer); var server = try Server.init(.{ - .gpa = gpa, - .in = .stdin(), - .out = .stdout(), + .in = &stdin_reader.interface, + .out = &stdout_writer.interface, .zig_version = builtin.zig_version_string, }); - defer server.deinit(); var seen_update = false; while (true) { diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 903e0a2f71..4c952c03c4 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -13,6 +13,8 @@ const hasDisjointCodePage = @import("disjoint_code_page.zig").hasDisjointCodePag const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType; const aro = @import("aro"); +var stdout_buffer: [1024]u8 = undefined; + pub fn main() !void { var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; defer std.debug.assert(gpa.deinit() == .ok); @@ -41,12 +43,12 @@ pub fn main() !void { cli_args = args[3..]; } + var stdout_writer2 = std.fs.File.stdout().writer(&stdout_buffer); var error_handler: ErrorHandler = switch (zig_integration) { true => .{ .server = .{ - .out = std.fs.File.stdout(), + .out = &stdout_writer2.interface, .in = undefined, // won't be receiving messages - .receive_fifo = undefined, // won't be receiving messages }, }, false => .{ diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 929bd1c417..a69066f09c 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -2,7 +2,6 @@ const builtin = @import("builtin"); const std = @import("std"); -const io = std.io; const testing = std.testing; const assert = std.debug.assert; @@ -13,6 +12,8 @@ pub const std_options: std.Options = .{ var log_err_count: usize = 0; var fba_buffer: [8192]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&fba_buffer); +var stdin_buffer: [std.heap.page_size_min]u8 align(std.heap.page_size_min) = undefined; +var stdout_buffer: [std.heap.page_size_min]u8 align(std.heap.page_size_min) = undefined; const crippled = switch (builtin.zig_backend) { .stage2_powerpc, @@ -67,13 +68,13 @@ pub fn main() void { fn mainServer() !void { @disableInstrumentation(); + var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer); + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); var server = try std.zig.Server.init(.{ - .gpa = fba.allocator(), - .in = .stdin(), - .out = .stdout(), + .in = &stdin_reader.interface, + .out = &stdout_writer.interface, .zig_version = builtin.zig_version_string, }); - defer server.deinit(); if (builtin.fuzz) { const coverage_id = fuzzer_coverage_id(); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 51eac1b0a6..ad264a9b33 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -905,4 +905,5 @@ test { _ = system; _ = target; _ = c_translation; + _ = llvm; } diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index e2b7cd233d..3e03d1ff2e 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -370,7 +370,7 @@ fn findNativeIncludeDirWindows( for (installs) |install| { result_buf.shrinkAndFree(0); - try result_buf.writer().print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version }); + try result_buf.print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -417,7 +417,7 @@ fn findNativeCrtDirWindows( for (installs) |install| { result_buf.shrinkAndFree(0); - try result_buf.writer().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir }); + try result_buf.print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -484,8 +484,7 @@ fn findNativeKernel32LibDir( for (installs) |install| { result_buf.shrinkAndFree(0); - const stream = result_buf.writer(); - try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir }); + try result_buf.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 2f03c78083..38ad45e1e1 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -1,6 +1,20 @@ -in: std.fs.File, -out: std.fs.File, -receive_fifo: std.fifo.LinearFifo(u8, .Dynamic), +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; +const Cache = std.Build.Cache; +const OutMessage = std.zig.Server.Message; +const InMessage = std.zig.Client.Message; +const Reader = std.Io.Reader; +const Writer = std.Io.Writer; + +in: *Reader, +out: *Writer, pub const Message = struct { pub const Header = extern struct { @@ -94,9 +108,8 @@ pub const Message = struct { }; pub const Options = struct { - gpa: Allocator, - in: std.fs.File, - out: std.fs.File, + in: *Reader, + out: *Writer, zig_version: []const u8, }; @@ -104,96 +117,40 @@ 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; - var last_amt_zero = false; - - while (true) { - const buf = fifo.readableSlice(0); - assert(fifo.readableLength() == buf.len); - if (buf.len >= @sizeOf(Header)) { - const header: *align(1) const Header = @ptrCast(buf[0..@sizeOf(Header)]); - const bytes_len = bswap(header.bytes_len); - const tag = bswap(header.tag); - - if (buf.len - @sizeOf(Header) >= bytes_len) { - fifo.discard(@sizeOf(Header)); - return .{ - .tag = tag, - .bytes_len = bytes_len, - }; - } else { - 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); - continue; - } - } - - const write_buffer = try fifo.writableWithSize(256); - const amt = try s.in.read(write_buffer); - fifo.update(amt); - if (amt == 0) { - if (last_amt_zero) return error.BrokenPipe; - last_amt_zero = true; - } - } + return s.in.takeStruct(InMessage.Header, .little); } pub fn receiveBody_u32(s: *Server) !u32 { - const fifo = &s.receive_fifo; - const buf = fifo.readableSlice(0); - const result = @as(*align(1) const u32, @ptrCast(buf[0..4])).*; - fifo.discard(4); - return bswap(result); + return s.in.takeInt(u32, .little); } pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { - return s.serveMessage(.{ + try s.serveMessageHeader(.{ .tag = tag, - .bytes_len = @as(u32, @intCast(msg.len)), - }, &.{msg}); + .bytes_len = @intCast(msg.len), + }); + try s.out.writeAll(msg); + try s.out.flush(); } -pub fn serveMessage( - s: *const Server, - header: OutMessage.Header, - bufs: []const []const u8, -) !void { - var iovecs: [10]std.posix.iovec_const = undefined; - const header_le = bswap(header); - iovecs[0] = .{ - .base = @as([*]const u8, @ptrCast(&header_le)), - .len = @sizeOf(OutMessage.Header), - }; - for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| { - iovec.* = .{ - .base = buf.ptr, - .len = buf.len, - }; - } - try s.out.writevAll(iovecs[0 .. bufs.len + 1]); +/// Don't forget to flush! +pub fn serveMessageHeader(s: *const Server, header: OutMessage.Header) !void { + try s.out.writeStruct(header, .little); } -pub fn serveU64Message(s: *Server, tag: OutMessage.Tag, int: u64) !void { - const msg_le = bswap(int); - return s.serveMessage(.{ +pub fn serveU64Message(s: *const Server, tag: OutMessage.Tag, int: u64) !void { + try serveMessageHeader(s, .{ .tag = tag, .bytes_len = @sizeOf(u64), - }, &.{std.mem.asBytes(&msg_le)}); + }); + try s.out.writeInt(u64, int, .little); + try s.out.flush(); } pub fn serveEmitDigest( @@ -201,26 +158,22 @@ pub fn serveEmitDigest( digest: *const [Cache.bin_digest_len]u8, header: OutMessage.EmitDigest, ) !void { - try s.serveMessage(.{ + try s.serveMessageHeader(.{ .tag = .emit_digest, .bytes_len = @intCast(digest.len + @sizeOf(OutMessage.EmitDigest)), - }, &.{ - std.mem.asBytes(&header), - digest, }); + try s.out.writeStruct(header, .little); + try s.out.writeAll(digest); + try s.out.flush(); } -pub fn serveTestResults( - s: *Server, - msg: OutMessage.TestResults, -) !void { - const msg_le = bswap(msg); - try s.serveMessage(.{ +pub fn serveTestResults(s: *Server, msg: OutMessage.TestResults) !void { + try s.serveMessageHeader(.{ .tag = .test_results, .bytes_len = @intCast(@sizeOf(OutMessage.TestResults)), - }, &.{ - std.mem.asBytes(&msg_le), }); + try s.out.writeStruct(msg, .little); + try s.out.flush(); } pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { @@ -230,91 +183,38 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { }; const bytes_len = @sizeOf(OutMessage.ErrorBundle) + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; - try s.serveMessage(.{ + try s.serveMessageHeader(.{ .tag = .error_bundle, .bytes_len = @intCast(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, }); + try s.out.writeStruct(eb_hdr, .little); + try s.out.writeSliceEndian(u32, error_bundle.extra, .little); + try s.out.writeAll(error_bundle.string_bytes); + try s.out.flush(); } pub const TestMetadata = struct { - names: []u32, - expected_panic_msgs: []u32, + names: []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 = bswap(@as(u32, @intCast(test_metadata.names.len))), - .string_bytes_len = bswap(@as(u32, @intCast(test_metadata.string_bytes.len))), + .tests_len = @as(u32, @intCast(test_metadata.names.len)), + .string_bytes_len = @as(u32, @intCast(test_metadata.string_bytes.len)), }; const trailing = 2; const bytes_len = @sizeOf(OutMessage.TestMetadata) + trailing * @sizeOf(u32) * test_metadata.names.len + test_metadata.string_bytes.len; - if (need_bswap) { - bswap_u32_array(test_metadata.names); - bswap_u32_array(test_metadata.expected_panic_msgs); - } - defer if (need_bswap) { - bswap_u32_array(test_metadata.names); - bswap_u32_array(test_metadata.expected_panic_msgs); - }; - - return s.serveMessage(.{ + try s.serveMessageHeader(.{ .tag = .test_metadata, .bytes_len = @intCast(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.expected_panic_msgs), - test_metadata.string_bytes, }); + try s.out.writeStruct(header, .little); + try s.out.writeSliceEndian(u32, test_metadata.names, .little); + try s.out.writeSliceEndian(u32, test_metadata.expected_panic_msgs, .little); + try s.out.writeAll(test_metadata.string_bytes); + try s.out.flush(); } - -fn bswap(x: anytype) @TypeOf(x) { - if (!need_bswap) return x; - - const T = @TypeOf(x); - switch (@typeInfo(T)) { - .@"enum" => return @as(T, @enumFromInt(@byteSwap(@intFromEnum(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 @as(T, @bitCast(@byteSwap(@as(I, @bitCast(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; -const Cache = std.Build.Cache; diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 1bcf45fb74..61e1defb12 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -1,11 +1,12 @@ +const WindowsSdk = @This(); +const builtin = @import("builtin"); +const std = @import("std"); +const Writer = std.Io.Writer; + windows10sdk: ?Installation, windows81sdk: ?Installation, msvc_lib_dir: ?[]const u8, -const WindowsSdk = @This(); -const std = @import("std"); -const builtin = @import("builtin"); - const windows = std.os.windows; const RRF = windows.advapi32.RRF; @@ -759,14 +760,13 @@ const MsvcLibDir = struct { while (instances_dir_it.next() catch return error.PathNotFound) |entry| { if (entry.kind != .directory) continue; - var fbs = std.io.fixedBufferStream(&state_subpath_buf); - const writer = fbs.writer(); + var writer: Writer = .fixed(&state_subpath_buf); writer.writeAll(entry.name) catch unreachable; writer.writeByte(std.fs.path.sep) catch unreachable; writer.writeAll("state.json") catch unreachable; - const json_contents = instances_dir.readFileAlloc(allocator, fbs.getWritten(), std.math.maxInt(usize)) catch continue; + const json_contents = instances_dir.readFileAlloc(allocator, writer.buffered(), std.math.maxInt(usize)) catch continue; defer allocator.free(json_contents); var parsed = std.json.parseFromSlice(std.json.Value, allocator, json_contents, .{}) catch continue; diff --git a/lib/std/zig/llvm.zig b/lib/std/zig/llvm.zig index c2e1ed9c56..c45ffe9083 100644 --- a/lib/std/zig/llvm.zig +++ b/lib/std/zig/llvm.zig @@ -1,3 +1,9 @@ pub const BitcodeReader = @import("llvm/BitcodeReader.zig"); pub const bitcode_writer = @import("llvm/bitcode_writer.zig"); pub const Builder = @import("llvm/Builder.zig"); + +test { + _ = BitcodeReader; + _ = bitcode_writer; + _ = Builder; +} diff --git a/lib/std/zig/llvm/BitcodeReader.zig b/lib/std/zig/llvm/BitcodeReader.zig index ea18b7978e..f1d47e93c0 100644 --- a/lib/std/zig/llvm/BitcodeReader.zig +++ b/lib/std/zig/llvm/BitcodeReader.zig @@ -1,6 +1,11 @@ +const BitcodeReader = @This(); + +const std = @import("../../std.zig"); +const assert = std.debug.assert; + allocator: std.mem.Allocator, record_arena: std.heap.ArenaAllocator.State, -reader: std.io.AnyReader, +reader: *std.Io.Reader, keep_names: bool, bit_buffer: u32, bit_offset: u5, @@ -93,7 +98,7 @@ pub const Record = struct { }; pub const InitOptions = struct { - reader: std.io.AnyReader, + reader: *std.Io.Reader, keep_names: bool = false, }; pub fn init(allocator: std.mem.Allocator, options: InitOptions) BitcodeReader { @@ -172,7 +177,7 @@ pub fn next(bc: *BitcodeReader) !?Item { pub fn skipBlock(bc: *BitcodeReader, block: Block) !void { assert(bc.bit_offset == 0); - try bc.reader.skipBytes(@as(u34, block.len) * 4, .{}); + try bc.reader.discardAll(4 * @as(u34, block.len)); try bc.endBlock(); } @@ -371,19 +376,19 @@ fn align32Bits(bc: *BitcodeReader) void { fn read32Bits(bc: *BitcodeReader) !u32 { assert(bc.bit_offset == 0); - return bc.reader.readInt(u32, .little); + return bc.reader.takeInt(u32, .little); } fn readBytes(bc: *BitcodeReader, bytes: []u8) !void { assert(bc.bit_offset == 0); - try bc.reader.readNoEof(bytes); + try bc.reader.readSliceAll(bytes); const trailing_bytes = bytes.len % 4; if (trailing_bytes > 0) { - var bit_buffer = [1]u8{0} ** 4; - try bc.reader.readNoEof(bit_buffer[trailing_bytes..]); + var bit_buffer: [4]u8 = @splat(0); + try bc.reader.readSliceAll(bit_buffer[trailing_bytes..]); bc.bit_buffer = std.mem.readInt(u32, &bit_buffer, .little); - bc.bit_offset = @intCast(trailing_bytes * 8); + bc.bit_offset = @intCast(8 * trailing_bytes); } } @@ -509,7 +514,6 @@ const Abbrev = struct { }; }; -const assert = std.debug.assert; -const std = @import("../../std.zig"); - -const BitcodeReader = @This(); +test { + _ = &skipBlock; +} diff --git a/lib/std/zig/perf_test.zig b/lib/std/zig/perf_test.zig index 087b081475..1566a15d2d 100644 --- a/lib/std/zig/perf_test.zig +++ b/lib/std/zig/perf_test.zig @@ -1,7 +1,6 @@ const std = @import("std"); const mem = std.mem; const Tokenizer = std.zig.Tokenizer; -const io = std.io; const fmtIntSizeBin = std.fmt.fmtIntSizeBin; const source = @embedFile("../os.zig"); @@ -22,16 +21,15 @@ pub fn main() !void { const bytes_per_sec_float = @as(f64, @floatFromInt(source.len * iterations)) / elapsed_s; const bytes_per_sec = @as(u64, @intFromFloat(@floor(bytes_per_sec_float))); - var stdout_file: std.fs.File = .stdout(); - const stdout = stdout_file.deprecatedWriter(); - try stdout.print("parsing speed: {:.2}/s, {:.2} used \n", .{ - fmtIntSizeBin(bytes_per_sec), - fmtIntSizeBin(memory_used), - }); + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + try stdout.print("parsing speed: {Bi:.2}/s, {Bi:.2} used \n", .{ bytes_per_sec, memory_used }); + try stdout.flush(); } fn testOnce() usize { - var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(&fixed_buffer_mem); const allocator = fixed_buf_alloc.allocator(); _ = std.zig.Ast.parse(allocator, source, .zig) catch @panic("parse failure"); return fixed_buf_alloc.end_index; diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index 8044e1969d..df70e71f5b 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -1,7 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); const mem = std.mem; -const io = std.io; const fs = std.fs; const fmt = std.fmt; const testing = std.testing; @@ -344,8 +343,8 @@ fn testParser( expected_model: *const Target.Cpu.Model, input: []const u8, ) !void { - var fbs = io.fixedBufferStream(input); - const result = try parser.parse(arch, fbs.reader()); + var r: std.Io.Reader = .fixed(input); + const result = try parser.parse(arch, &r); try testing.expectEqual(expected_model, result.?.model); try testing.expect(expected_model.features.eql(result.?.features)); } @@ -357,20 +356,17 @@ fn testParser( // When all the lines have been analyzed the finalize method is called. fn CpuinfoParser(comptime impl: anytype) type { return struct { - fn parse(arch: Target.Cpu.Arch, reader: anytype) anyerror!?Target.Cpu { - var line_buf: [1024]u8 = undefined; + fn parse(arch: Target.Cpu.Arch, reader: *std.Io.Reader) !?Target.Cpu { var obj: impl = .{}; - - while (true) { - const line = (try reader.readUntilDelimiterOrEof(&line_buf, '\n')) orelse break; + while (reader.takeDelimiterExclusive('\n')) |line| { const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue; const key = mem.trimEnd(u8, line[0..colon_pos], " \t"); const value = mem.trimStart(u8, line[colon_pos + 1 ..], " \t"); - - if (!try obj.line_hook(key, value)) - break; + if (!try obj.line_hook(key, value)) break; + } else |err| switch (err) { + error.EndOfStream => {}, + else => |e| return e, } - return obj.finalize(arch); } }; @@ -383,15 +379,18 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 { } pub fn detectNativeCpuAndFeatures() ?Target.Cpu { - var f = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) { + var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) { else => return null, }; - defer f.close(); + defer file.close(); + + var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long. + var file_reader = file.reader(&buffer); const current_arch = builtin.cpu.arch; switch (current_arch) { .arm, .armeb, .thumb, .thumbeb => { - return ArmCpuinfoParser.parse(current_arch, f.deprecatedReader()) catch null; + return ArmCpuinfoParser.parse(current_arch, &file_reader.interface) catch null; }, .aarch64, .aarch64_be => { const registers = [12]u64{ @@ -413,13 +412,13 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu { return core; }, .sparc64 => { - return SparcCpuinfoParser.parse(current_arch, f.deprecatedReader()) catch null; + return SparcCpuinfoParser.parse(current_arch, &file_reader.interface) catch null; }, .powerpc, .powerpcle, .powerpc64, .powerpc64le => { - return PowerpcCpuinfoParser.parse(current_arch, f.deprecatedReader()) catch null; + return PowerpcCpuinfoParser.parse(current_arch, &file_reader.interface) catch null; }, .riscv64, .riscv32 => { - return RiscvCpuinfoParser.parse(current_arch, f.deprecatedReader()) catch null; + return RiscvCpuinfoParser.parse(current_arch, &file_reader.interface) catch null; }, else => {}, } diff --git a/src/Compilation.zig b/src/Compilation.zig index 2e20c6edbe..469a13b4a9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -12,6 +12,7 @@ const ThreadPool = std.Thread.Pool; const WaitGroup = std.Thread.WaitGroup; const ErrorBundle = std.zig.ErrorBundle; const fatal = std.process.fatal; +const Writer = std.io.Writer; const Value = @import("Value.zig"); const Type = @import("Type.zig"); @@ -44,6 +45,8 @@ const Builtin = @import("Builtin.zig"); const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); +const DeprecatedLinearFifo = @import("deprecated.zig").LinearFifo; + pub const Config = @import("Compilation/Config.zig"); /// General-purpose allocator. Used for both temporary and long-term storage. @@ -121,15 +124,15 @@ work_queues: [ } break :len len; } -]std.fifo.LinearFifo(Job, .Dynamic), +]DeprecatedLinearFifo(Job), /// These jobs are to invoke the Clang compiler to create an object file, which /// gets linked with the Compilation. -c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic), +c_object_work_queue: DeprecatedLinearFifo(*CObject), /// These jobs are to invoke the RC compiler to create a compiled resource file (.res), which /// gets linked with the Compilation. -win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic) else struct { +win32_resource_work_queue: if (dev.env.supports(.win32_resource)) DeprecatedLinearFifo(*Win32Resource) else struct { pub fn ensureUnusedCapacity(_: @This(), _: u0) error{}!void {} pub fn readItem(_: @This()) ?noreturn { return null; @@ -995,13 +998,13 @@ pub const CObject = struct { const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; defer file.close(); - file.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; - - var line = std.ArrayList(u8).init(eb.gpa); - defer line.deinit(); - file.deprecatedReader().readUntilDelimiterArrayList(&line, '\n', 1 << 10) catch break :source_line 0; - - break :source_line try eb.addString(line.items); + var buffer: [1024]u8 = undefined; + var file_reader = file.reader(&buffer); + file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; + var aw: Writer.Allocating = .init(eb.gpa); + defer aw.deinit(); + _ = file_reader.interface.streamDelimiterEnding(&aw.writer, '\n') catch break :source_line 0; + break :source_line try eb.addString(aw.getWritten()); }; return .{ @@ -1067,11 +1070,11 @@ pub const CObject = struct { } }; + var buffer: [1024]u8 = undefined; const file = try fs.cwd().openFile(path, .{}); defer file.close(); - var br = std.io.bufferedReader(file.deprecatedReader()); - const reader = br.reader(); - var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = reader.any() }); + var file_reader = file.reader(&buffer); + var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface }); defer bc.deinit(); var file_names: std.AutoArrayHashMapUnmanaged(u32, []const u8) = .empty; @@ -1873,15 +1876,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil if (options.verbose_llvm_cpu_features) { if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { - std.debug.lockStdErr(); - defer std.debug.unlockStdErr(); - const stderr = fs.File.stderr().deprecatedWriter(); - nosuspend { - stderr.print("compilation: {s}\n", .{options.root_name}) catch break :print; - stderr.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; - stderr.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; - stderr.print(" features: {s}\n", .{cf}) catch {}; - } + const stderr_w = std.debug.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); + stderr_w.print("compilation: {s}\n", .{options.root_name}) catch break :print; + stderr_w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; + stderr_w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; + stderr_w.print(" features: {s}\n", .{cf}) catch {}; } } @@ -2483,7 +2483,7 @@ pub fn destroy(comp: *Compilation) void { if (comp.zcu) |zcu| zcu.deinit(); comp.cache_use.deinit(); - for (comp.work_queues) |work_queue| work_queue.deinit(); + for (&comp.work_queues) |*work_queue| work_queue.deinit(); comp.c_object_work_queue.deinit(); comp.win32_resource_work_queue.deinit(); @@ -3931,11 +3931,12 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { // This AU is referenced and has a transitive compile error, meaning it referenced something with a compile error. // However, we haven't reported any such error. // This is a compiler bug. - const stderr = fs.File.stderr().deprecatedWriter(); - try stderr.writeAll("referenced transitive analysis errors, but none actually emitted\n"); - try stderr.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}); + var stderr_w = std.debug.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); + try stderr_w.writeAll("referenced transitive analysis errors, but none actually emitted\n"); + try stderr_w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}); while (ref) |r| { - try stderr.print("referenced by: {f}{s}\n", .{ + try stderr_w.print("referenced by: {f}{s}\n", .{ zcu.fmtAnalUnit(r.referencer), if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "", }); @@ -5849,7 +5850,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr try child.spawn(); - const stderr = try child.stderr.?.deprecatedReader().readAllAlloc(arena, std.math.maxInt(usize)); + var small_buffer: [1]u8 = undefined; + var stderr_reader = child.stderr.?.reader(&small_buffer); + const stderr = try stderr_reader.interface.allocRemaining(arena, .unlimited); const term = child.wait() catch |err| { return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) }); @@ -6213,13 +6216,10 @@ fn spawnZigRc( const stdout = poller.fifo(.stdout); poll: while (true) { - while (stdout.readableLength() < @sizeOf(std.zig.Server.Message.Header)) { - if (!(try poller.poll())) break :poll; - } - const header = stdout.reader().readStruct(std.zig.Server.Message.Header) catch unreachable; - while (stdout.readableLength() < header.bytes_len) { - if (!(try poller.poll())) break :poll; - } + while (stdout.readableLength() < @sizeOf(std.zig.Server.Message.Header)) if (!try poller.poll()) break :poll; + var header: std.zig.Server.Message.Header = undefined; + assert(stdout.read(std.mem.asBytes(&header)) == @sizeOf(std.zig.Server.Message.Header)); + while (stdout.readableLength() < header.bytes_len) if (!try poller.poll()) break :poll; const body = stdout.readableSliceOfLen(header.bytes_len); switch (header.tag) { @@ -7209,13 +7209,16 @@ pub fn lockAndSetMiscFailure( } pub fn dump_argv(argv: []const []const u8) void { - std.debug.lockStdErr(); - defer std.debug.unlockStdErr(); - const stderr = fs.File.stderr().deprecatedWriter(); - for (argv[0 .. argv.len - 1]) |arg| { - nosuspend stderr.print("{s} ", .{arg}) catch return; + var buffer: [64]u8 = undefined; + const stderr = std.debug.lockStderrWriter(&buffer); + defer std.debug.unlockStderrWriter(); + nosuspend { + for (argv) |arg| { + stderr.writeAll(arg) catch return; + (stderr.writableArray(1) catch return)[0] = ' '; + } + stderr.buffer[stderr.end - 1] = '\n'; } - nosuspend stderr.print("{s}\n", .{argv[argv.len - 1]}) catch {}; } pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { diff --git a/src/deprecated.zig b/src/deprecated.zig index 1f7d5c8c25..68c712b3b1 100644 --- a/src/deprecated.zig +++ b/src/deprecated.zig @@ -52,15 +52,6 @@ pub fn LinearFifo(comptime T: type) type { } } - /// Reduce allocated capacity to `size`. - pub fn shrink(self: *Self, size: usize) void { - assert(size >= self.count); - self.realign(); - self.buf = self.allocator.realloc(self.buf, size) catch |e| switch (e) { - error.OutOfMemory => return, // no problem, capacity is still correct then. - }; - } - /// Ensure that the buffer can fit at least `size` items pub fn ensureTotalCapacity(self: *Self, size: usize) !void { if (self.buf.len >= size) return; @@ -76,11 +67,6 @@ pub fn LinearFifo(comptime T: type) type { return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory); } - /// Returns number of items currently in fifo - pub fn readableLength(self: Self) usize { - return self.count; - } - /// Returns a writable slice from the 'read' end of the fifo fn readableSliceMut(self: Self, offset: usize) []T { if (offset > self.count) return &[_]T{}; @@ -95,22 +81,6 @@ pub fn LinearFifo(comptime T: type) type { } } - /// Returns a readable slice from `offset` - pub fn readableSlice(self: Self, offset: usize) []const T { - 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); @@ -143,28 +113,6 @@ pub fn LinearFifo(comptime T: type) type { return c; } - /// Read data from the fifo into `dst`, returns number of items copied. - pub fn read(self: *Self, dst: []T) usize { - var dst_left = dst; - - while (dst_left.len > 0) { - const slice = self.readableSlice(0); - if (slice.len == 0) break; - const n = @min(slice.len, dst_left.len); - @memcpy(dst_left[0..n], slice[0..n]); - self.discard(n); - dst_left = dst_left[n..]; - } - - return dst.len - dst_left.len; - } - - /// Same as `read` except it returns an error union - /// The purpose of this function existing is to match `std.io.Reader` API. - fn readFn(self: *Self, dest: []u8) error{}!usize { - return self.read(dest); - } - /// Returns number of items available in fifo pub fn writableLength(self: Self) usize { return self.buf.len - self.count; @@ -183,20 +131,6 @@ pub fn LinearFifo(comptime T: type) type { } } - /// Returns a writable buffer of at least `size` items, allocating memory as needed. - /// Use `fifo.update` once you've written data to it. - pub fn writableWithSize(self: *Self, size: usize) ![]T { - try self.ensureUnusedCapacity(size); - - // try to avoid realigning buffer - var slice = self.writableSlice(0); - if (slice.len < size) { - self.realign(); - slice = self.writableSlice(0); - } - return slice; - } - /// Update the tail location of the buffer (usually follows use of writable/writableWithSize) pub fn update(self: *Self, count: usize) void { assert(self.count + count <= self.buf.len); @@ -231,201 +165,5 @@ pub fn LinearFifo(comptime T: type) type { self.buf[tail] = item; self.update(1); } - - /// Appends the data in `src` to the fifo. - /// Allocates more memory as necessary - pub fn write(self: *Self, src: []const T) !void { - try self.ensureUnusedCapacity(src.len); - - return self.writeAssumeCapacity(src); - } - - /// Same as `write` except it returns the number of bytes written, which is always the same - /// as `bytes.len`. The purpose of this function existing is to match `std.io.Writer` API. - fn appendWrite(self: *Self, bytes: []const u8) error{OutOfMemory}!usize { - try self.write(bytes); - return bytes.len; - } - - /// Make `count` items available before the current read location - fn rewind(self: *Self, count: usize) void { - assert(self.writableLength() >= count); - - var head = self.head + (self.buf.len - count); - head &= self.buf.len - 1; - self.head = head; - self.count += count; - } - - /// Place data back into the read stream - pub fn unget(self: *Self, src: []const T) !void { - try self.ensureUnusedCapacity(src.len); - - self.rewind(src.len); - - const slice = self.readableSliceMut(0); - if (src.len < slice.len) { - @memcpy(slice[0..src.len], src); - } else { - @memcpy(slice, src[0..slice.len]); - const slice2 = self.readableSliceMut(slice.len); - @memcpy(slice2[0 .. src.len - slice.len], src[slice.len..]); - } - } - - /// Returns the item at `offset`. - /// Asserts offset is within bounds. - pub fn peekItem(self: Self, offset: usize) T { - assert(offset < self.count); - - var index = self.head + offset; - index &= self.buf.len - 1; - return self.buf[index]; - } - - 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; - 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; - } }; } - -test "LinearFifo(u8, .Dynamic) discard(0) from empty buffer should not error on overflow" { - var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator); - defer fifo.deinit(); - - // If overflow is not explicitly allowed this will crash in debug / safe mode - fifo.discard(0); -} - -test "LinearFifo(u8, .Dynamic)" { - var fifo = LinearFifo(u8, .Dynamic).init(testing.allocator); - defer fifo.deinit(); - - try fifo.write("HELLO"); - try testing.expectEqual(@as(usize, 5), fifo.readableLength()); - try testing.expectEqualSlices(u8, "HELLO", fifo.readableSlice(0)); - - { - var i: usize = 0; - while (i < 5) : (i += 1) { - try fifo.write(&[_]u8{fifo.peekItem(i)}); - } - try testing.expectEqual(@as(usize, 10), fifo.readableLength()); - try testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0)); - } - - { - try testing.expectEqual(@as(u8, 'H'), fifo.readItem().?); - try testing.expectEqual(@as(u8, 'E'), fifo.readItem().?); - try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); - try testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); - try testing.expectEqual(@as(u8, 'O'), fifo.readItem().?); - } - try testing.expectEqual(@as(usize, 5), fifo.readableLength()); - - { // Writes that wrap around - try testing.expectEqual(@as(usize, 11), fifo.writableLength()); - try testing.expectEqual(@as(usize, 6), fifo.writableSlice(0).len); - fifo.writeAssumeCapacity("6 {}, .stdio => { + var stdin_reader = fs.File.stdin().reader(&stdin_buffer); + var stdout_writer = fs.File.stdout().writer(&stdout_buffer); try serve( comp, - .stdin(), - .stdout(), + &stdin_reader.interface, + &stdout_writer.interface, test_exec_args.items, self_exe_path, arg_mode, @@ -3584,10 +3588,13 @@ fn buildOutputType( const conn = try server.accept(); defer conn.stream.close(); + var input = conn.stream.reader(&stdin_buffer); + var output = conn.stream.writer(&stdout_buffer); + try serve( comp, - .{ .handle = conn.stream.handle }, - .{ .handle = conn.stream.handle }, + input.interface(), + &output.interface, test_exec_args.items, self_exe_path, arg_mode, @@ -4053,8 +4060,8 @@ fn saveState(comp: *Compilation, incremental: bool) void { fn serve( comp: *Compilation, - in: fs.File, - out: fs.File, + in: *std.Io.Reader, + out: *std.Io.Writer, test_exec_args: []const ?[]const u8, self_exe_path: ?[]const u8, arg_mode: ArgMode, @@ -4064,12 +4071,10 @@ fn serve( const gpa = comp.gpa; var server = try Server.init(.{ - .gpa = gpa, .in = in, .out = out, .zig_version = build_options.version, }); - defer server.deinit(); var child_pid: ?std.process.Child.Id = null; @@ -5491,10 +5496,10 @@ fn jitCmd( defer comp.destroy(); if (options.server) { + var stdout_writer = fs.File.stdout().writer(&stdout_buffer); var server: std.zig.Server = .{ - .out = fs.File.stdout(), + .out = &stdout_writer.interface, .in = undefined, // won't be receiving messages - .receive_fifo = undefined, // won't be receiving messages }; try comp.update(root_prog_node); @@ -6058,7 +6063,7 @@ fn cmdAstCheck( }; } else fs.File.stdin(); defer if (zig_source_path != null) f.close(); - var file_reader: fs.File.Reader = f.reader(&stdio_buffer); + var file_reader: fs.File.Reader = f.reader(&stdin_buffer); break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| { fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) }); }; @@ -6076,7 +6081,7 @@ fn cmdAstCheck( const tree = try Ast.parse(arena, source, mode); - var stdout_writer = fs.File.stdout().writerStreaming(&stdio_buffer); + var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; switch (mode) { .zig => { @@ -6291,7 +6296,7 @@ fn detectNativeCpuWithLLVM( } fn printCpu(cpu: std.Target.Cpu) !void { - var stdout_writer = fs.File.stdout().writerStreaming(&stdio_buffer); + var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; if (cpu.model.llvm_name) |llvm_name| { @@ -6340,7 +6345,7 @@ fn cmdDumpLlvmInts( const dl = tm.createTargetDataLayout(); const context = llvm.Context.create(); - var stdout_writer = fs.File.stdout().writerStreaming(&stdio_buffer); + var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| { const int_type = context.intType(bits); @@ -6369,9 +6374,8 @@ fn cmdDumpZir( defer f.close(); const zir = try Zcu.loadZirCache(arena, f); - var stdout_writer = fs.File.stdout().writerStreaming(&stdio_buffer); + var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; - { const instruction_bytes = zir.instructions.len * // Here we don't use @sizeOf(Zir.Inst.Data) because it would include @@ -6417,7 +6421,7 @@ fn cmdChangelist( var f = fs.cwd().openFile(old_source_path, .{}) catch |err| fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); defer f.close(); - var file_reader: fs.File.Reader = f.reader(&stdio_buffer); + var file_reader: fs.File.Reader = f.reader(&stdin_buffer); break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) }); }; @@ -6425,7 +6429,7 @@ fn cmdChangelist( var f = fs.cwd().openFile(new_source_path, .{}) catch |err| fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); defer f.close(); - var file_reader: fs.File.Reader = f.reader(&stdio_buffer); + var file_reader: fs.File.Reader = f.reader(&stdin_buffer); break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) }); }; @@ -6457,7 +6461,7 @@ fn cmdChangelist( var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty; try Zcu.mapOldZirToNew(arena, old_zir, new_zir, &inst_map); - var stdout_writer = fs.File.stdout().writerStreaming(&stdio_buffer); + var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer); const stdout_bw = &stdout_writer.interface; { try stdout_bw.print("Instruction mappings:\n", .{}); @@ -6917,7 +6921,7 @@ fn cmdFetch( const name = switch (save) { .no => { - var stdout = fs.File.stdout().writerStreaming(&stdio_buffer); + var stdout = fs.File.stdout().writerStreaming(&stdout_buffer); try stdout.interface.print("{s}\n", .{package_hash_slice}); try stdout.interface.flush(); return cleanExit(); From 16ae68ec9bf8db37f445d6e07ae6269bcacb445e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 06:45:25 -0700 Subject: [PATCH 40/46] Compilation: revert some stuff --- src/Compilation.zig | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 469a13b4a9..916a025bc9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -998,13 +998,13 @@ pub const CObject = struct { const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; defer file.close(); - var buffer: [1024]u8 = undefined; - var file_reader = file.reader(&buffer); - file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; - var aw: Writer.Allocating = .init(eb.gpa); - defer aw.deinit(); - _ = file_reader.interface.streamDelimiterEnding(&aw.writer, '\n') catch break :source_line 0; - break :source_line try eb.addString(aw.getWritten()); + file.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; + + var line = std.ArrayList(u8).init(eb.gpa); + defer line.deinit(); + file.deprecatedReader().readUntilDelimiterArrayList(&line, '\n', 1 << 10) catch break :source_line 0; + + break :source_line try eb.addString(line.items); }; return .{ @@ -5850,9 +5850,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr try child.spawn(); - var small_buffer: [1]u8 = undefined; - var stderr_reader = child.stderr.?.reader(&small_buffer); - const stderr = try stderr_reader.interface.allocRemaining(arena, .unlimited); + const stderr = try child.stderr.?.deprecatedReader().readAllAlloc(arena, std.math.maxInt(usize)); const term = child.wait() catch |err| { return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) }); From 3a3fd47a8ad5a026c318dfd68b511cfa8eed7564 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 10:42:04 -0700 Subject: [PATCH 41/46] std.debug: add assertAligned --- lib/std/debug.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index a7b88e4257..655852c65e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -566,6 +566,13 @@ pub fn assertReadable(slice: []const volatile u8) void { for (slice) |*byte| _ = byte.*; } +/// Invokes detectable illegal behavior when the provided array is not aligned +/// to the provided amount. +pub fn assertAligned(ptr: anytype, comptime alignment: std.mem.Alignment) void { + const aligned_ptr: *align(alignment.toByteUnits()) anyopaque = @alignCast(@ptrCast(ptr)); + _ = aligned_ptr; +} + /// Equivalent to `@panic` but with a formatted message. pub fn panic(comptime format: []const u8, args: anytype) noreturn { @branchHint(.cold); From d12504eefa32a04840b48c9264b43463736b402e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 10:42:24 -0700 Subject: [PATCH 42/46] std.mem: add byteSwapAllElements --- lib/std/mem.zig | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 33e68eedad..1a61076f32 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -2179,22 +2179,8 @@ pub fn byteSwapAllFields(comptime S: type, ptr: *S) void { const BackingInt = std.meta.Int(.unsigned, @bitSizeOf(S)); ptr.* = @bitCast(@byteSwap(@as(BackingInt, @bitCast(ptr.*)))); }, - .array => { - for (ptr) |*item| { - switch (@typeInfo(@TypeOf(item.*))) { - .@"struct", .@"union", .array => byteSwapAllFields(@TypeOf(item.*), item), - .@"enum" => { - item.* = @enumFromInt(@byteSwap(@intFromEnum(item.*))); - }, - .bool => {}, - .float => |float_info| { - item.* = @bitCast(@byteSwap(@as(std.meta.Int(.unsigned, float_info.bits), @bitCast(item.*)))); - }, - else => { - item.* = @byteSwap(item.*); - }, - } - } + .array => |info| { + byteSwapAllElements(info.child, ptr); }, else => { ptr.* = @byteSwap(ptr.*); @@ -2258,6 +2244,24 @@ test byteSwapAllFields { }, k); } +pub fn byteSwapAllElements(comptime Elem: type, slice: []Elem) void { + for (slice) |*elem| { + switch (@typeInfo(@TypeOf(elem.*))) { + .@"struct", .@"union", .array => byteSwapAllFields(@TypeOf(elem.*), elem), + .@"enum" => { + elem.* = @enumFromInt(@byteSwap(@intFromEnum(elem.*))); + }, + .bool => {}, + .float => |float_info| { + elem.* = @bitCast(@byteSwap(@as(std.meta.Int(.unsigned, float_info.bits), @bitCast(elem.*)))); + }, + else => { + elem.* = @byteSwap(elem.*); + }, + } + } +} + /// Returns an iterator that iterates over the slices of `buffer` that are not /// any of the items in `delimiters`. /// From e40f5b32d34daa289a1d25ba03d4e2c9b98caa85 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 10:43:02 -0700 Subject: [PATCH 43/46] std.Io.Writer: add missing writeSliceSwap --- lib/std/Io/Writer.zig | 28 +++++++++++++++++++++++++++- lib/std/zig/Server.zig | 2 ++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 4b0e142fb0..11bc05a00d 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -851,6 +851,9 @@ pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.builtin.Endian } } +/// If, `endian` is not native, +/// * Asserts that the buffer storage capacity is at least enough to store `@sizeOf(Elem)` +/// * Asserts that the buffer is aligned enough for `@alignOf(Elem)`. pub inline fn writeSliceEndian( w: *Writer, Elem: type, @@ -860,7 +863,22 @@ pub inline fn writeSliceEndian( if (native_endian == endian) { return writeAll(w, @ptrCast(slice)); } else { - return w.writeArraySwap(w, Elem, slice); + return writeSliceSwap(w, Elem, slice); + } +} + +/// Asserts that the buffer storage capacity is at least enough to store `@sizeOf(Elem)` +/// +/// Asserts that the buffer is aligned enough for `@alignOf(Elem)`. +pub fn writeSliceSwap(w: *Writer, Elem: type, slice: []const Elem) Error!void { + var i: usize = 0; + while (i < slice.len) { + const dest_bytes = try w.writableSliceGreedy(@sizeOf(Elem)); + const dest: []Elem = @alignCast(@ptrCast(dest_bytes[0 .. dest_bytes.len - dest_bytes.len % @sizeOf(Elem)])); + const copy_len = @min(dest.len, slice.len - i); + @memcpy(dest[0..copy_len], slice[i..][0..copy_len]); + i += copy_len; + std.mem.byteSwapAllElements(Elem, dest); } } @@ -2630,3 +2648,11 @@ test writeStruct { }, &buffer); } } + +test writeSliceEndian { + var buffer: [4]u8 align(2) = undefined; + var w: Writer = .fixed(&buffer); + const array: [2]u16 = .{ 0x1234, 0x5678 }; + try writeSliceEndian(&w, u16, &array, .big); + try testing.expectEqualSlices(u8, &.{ 0x12, 0x34, 0x56, 0x78 }, &buffer); +} diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 38ad45e1e1..8fc016d284 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -118,6 +118,8 @@ pub fn init(options: Options) !Server { .in = options.in, .out = options.out, }; + assert(s.out.buffer.len >= 4); + std.debug.assertAligned(s.out.buffer.ptr, .@"4"); try s.serveStringMessage(.zig_version, options.zig_version); return s; } From 3df144e0d4ada4ef624a827fc9335f01d9be30a3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 11:15:12 -0700 Subject: [PATCH 44/46] frontend: align those stdio buffers --- src/main.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.zig b/src/main.zig index 9958c461b1..dc931f119b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -66,9 +66,9 @@ pub fn wasi_cwd() std.os.wasi.fd_t { const fatal = std.process.fatal; /// This can be global since stdin is a singleton. -var stdin_buffer: [4096]u8 = undefined; +var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined; /// This can be global since stdout is a singleton. -var stdout_buffer: [4096]u8 = undefined; +var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined; /// Shaming all the locations that inappropriately use an O(N) search algorithm. /// Please delete this and fix the compilation errors! From c7d75076dd57e248550025613e115ca055a005ab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 11:16:31 -0700 Subject: [PATCH 45/46] Compilation: unrevert some stuff --- src/Compilation.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 916a025bc9..b5597017c4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -998,13 +998,13 @@ pub const CObject = struct { const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0; defer file.close(); - file.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; - - var line = std.ArrayList(u8).init(eb.gpa); - defer line.deinit(); - file.deprecatedReader().readUntilDelimiterArrayList(&line, '\n', 1 << 10) catch break :source_line 0; - - break :source_line try eb.addString(line.items); + var buffer: [1024]u8 = undefined; + var file_reader = file.reader(&buffer); + file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; + var aw: Writer.Allocating = .init(eb.gpa); + defer aw.deinit(); + _ = file_reader.interface.streamDelimiterEnding(&aw.writer, '\n') catch break :source_line 0; + break :source_line try eb.addString(aw.getWritten()); }; return .{ From 7c7e081cb2f0df87c314a6d7a9b2d10bf140d591 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 18 Jul 2025 17:38:13 -0700 Subject: [PATCH 46/46] disable -fno-llvm -target wasm32-wasi testing no active maintainer, and it's failing to lower some basic stuff --- test/src/Cases.zig | 2 -- test/tests.zig | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 522fe6b385..a92f9fd158 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -800,8 +800,6 @@ const TestManifestConfigDefaults = struct { } // Windows defaults = defaults ++ "x86_64-windows" ++ ","; - // Wasm - defaults = defaults ++ "wasm32-wasi"; break :blk defaults; }; } else if (std.mem.eql(u8, key, "output_mode")) { diff --git a/test/tests.zig b/test/tests.zig index 2a6c4166fa..db4407172f 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1335,15 +1335,16 @@ const test_targets = blk: { // WASI Targets - .{ - .target = .{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - .abi = .none, - }, - .use_llvm = false, - .use_lld = false, - }, + // TODO: lowerTry for pointers + //.{ + // .target = .{ + // .cpu_arch = .wasm32, + // .os_tag = .wasi, + // .abi = .none, + // }, + // .use_llvm = false, + // .use_lld = false, + //}, .{ .target = .{ .cpu_arch = .wasm32,