From d5e3d5d74cefd64287b92d148f78353cdb84e447 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Jun 2022 16:19:21 +0300 Subject: [PATCH 1/5] Sema: make `analyzeIsNonErr` even lazier for inferred error sets --- src/Sema.zig | 22 ++++++++++++++++++++++ test/behavior/error.zig | 15 +++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/Sema.zig b/src/Sema.zig index d36af5abf2..7bc011e226 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21870,6 +21870,28 @@ fn analyzeIsNonErrComptimeOnly( if (ies.is_anyerror) break :blk; if (ies.errors.count() != 0) break :blk; if (maybe_operand_val == null) { + // Try to avoid resolving inferred error set if possible. + if (ies.errors.count() != 0) break :blk; + if (ies.is_anyerror) break :blk; + var it = ies.inferred_error_sets.keyIterator(); + while (it.next()) |other_error_set_ptr| { + const other_ies: *Module.Fn.InferredErrorSet = other_error_set_ptr.*; + if (ies == other_ies) continue; + try sema.resolveInferredErrorSet(block, src, other_ies); + if (other_ies.is_anyerror) { + ies.is_anyerror = true; + ies.is_resolved = true; + break :blk; + } + + if (other_ies.errors.count() != 0) break :blk; + } + if (ies.func == sema.owner_func) { + // We're checking the inferred errorset of the current function and none of + // its child inferred error sets contained any errors meaning that any value + // so far with this type can't contain errors either. + return Air.Inst.Ref.bool_true; + } try sema.resolveInferredErrorSet(block, src, ies); if (ies.is_anyerror) break :blk; if (ies.errors.count() == 0) return Air.Inst.Ref.bool_true; diff --git a/test/behavior/error.zig b/test/behavior/error.zig index b735d70d73..0fedd7dff0 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -754,3 +754,18 @@ test "error union payload is properly aligned" { const blk = S.foo() catch unreachable; if (blk.a != 1) unreachable; } + +test "ret_ptr doesn't cause own inferred error set to be resolved" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + const S = struct { + fn foo() !void {} + + fn doTheTest() !void { + errdefer @compileError("bad"); + + return try @This().foo(); + } + }; + try S.doTheTest(); +} From e4c0b848a46347fccced787488605ac66e83c38a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Jun 2022 17:48:53 +0300 Subject: [PATCH 2/5] Sema: allow simple else body even when all errors handled --- src/Sema.zig | 30 ++++++++++++++++++++++++++++-- test/behavior/error.zig | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 7bc011e226..550190654b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7774,7 +7774,12 @@ fn zirSwitchCapture( } switch (operand_ty.zigTypeTag()) { - .ErrorSet => return sema.bitCast(block, block.switch_else_err_ty.?, operand, operand_src), + .ErrorSet => if (block.switch_else_err_ty) |some| { + return sema.bitCast(block, some, operand, operand_src); + } else { + try block.addUnreachable(operand_src, false); + return Air.Inst.Ref.unreachable_value; + }, else => return operand, } } @@ -8194,7 +8199,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError ); } else_error_ty = Type.@"anyerror"; - } else { + } else else_validation: { var maybe_msg: ?*Module.ErrorMsg = null; errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa); @@ -8231,6 +8236,27 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } if (special_prong == .@"else" and seen_errors.count() == operand_ty.errorSetNames().len) { + + // In order to enable common patterns for generic code allow simple else bodies + // else => unreachable, + // else => return, + // else => |e| return e, + // even if all the possible errors were already handled. + const tags = sema.code.instructions.items(.tag); + for (special.body) |else_inst| switch (tags[else_inst]) { + .dbg_block_begin, + .dbg_block_end, + .dbg_stmt, + .dbg_var_val, + .switch_capture, + .ret_type, + .as_node, + .ret_node, + .@"unreachable", + => {}, + else => break, + } else break :else_validation; + return sema.fail( block, special_prong_src, diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 0fedd7dff0..ac51ec1eae 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -769,3 +769,30 @@ test "ret_ptr doesn't cause own inferred error set to be resolved" { }; try S.doTheTest(); } + +test "simple else prong allowed even when all errors handled" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const S = struct { + fn foo() !u8 { + return error.Foo; + } + }; + var value = S.foo() catch |err| switch (err) { + error.Foo => 255, + else => |e| return e, + }; + try expect(value == 255); + value = S.foo() catch |err| switch (err) { + error.Foo => 255, + else => unreachable, + }; + try expect(value == 255); + value = S.foo() catch |err| switch (err) { + error.Foo => 255, + else => return, + }; + try expect(value == 255); +} From fbd7e4506f46b73e351e1f3eb5e7cfc16ebbfc1f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Jun 2022 19:13:50 +0300 Subject: [PATCH 3/5] stage2: implement asm with multiple outputs --- src/AstGen.zig | 5 +++- src/Sema.zig | 54 ++++++++++++++++++++------------------------ src/codegen/llvm.zig | 21 ++++++++++------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 36944c1a8a..831071c479 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -6876,6 +6876,9 @@ fn asmExpr( const constraint = (try astgen.strLitAsString(constraint_token)).index; const has_arrow = token_tags[symbolic_name + 4] == .arrow; if (has_arrow) { + if (output_type_bits != 0) { + return astgen.failNode(output_node, "inline assembly allows up to one output value", .{}); + } output_type_bits |= @as(u32, 1) << @intCast(u5, i); const out_type_node = node_datas[output_node].lhs; const out_type_inst = try typeExpr(gz, scope, out_type_node); @@ -6892,7 +6895,7 @@ fn asmExpr( outputs[i] = .{ .name = name, .constraint = constraint, - .operand = try localVarRef(gz, scope, rl, node, ident_token), + .operand = try localVarRef(gz, scope, .ref, node, ident_token), }; } } diff --git a/src/Sema.zig b/src/Sema.zig index 550190654b..5159d6f5d3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11334,43 +11334,40 @@ fn zirAsm( try sema.requireRuntimeBlock(block, src); } - if (outputs_len > 1) { - return sema.fail(block, src, "TODO implement Sema for asm with more than 1 output", .{}); - } - var extra_i = extra.end; var output_type_bits = extra.data.output_type_bits; var needed_capacity: usize = @typeInfo(Air.Asm).Struct.fields.len + outputs_len + inputs_len; - const Output = struct { - constraint: []const u8, - name: []const u8, - ty: Type, - }; - const output: ?Output = if (outputs_len == 0) null else blk: { + const ConstraintName = struct { c: []const u8, n: []const u8 }; + const out_args = try sema.arena.alloc(Air.Inst.Ref, outputs_len); + const outputs = try sema.arena.alloc(ConstraintName, outputs_len); + var expr_ty = Air.Inst.Ref.void_type; + + for (out_args) |*arg, out_i| { const output = sema.code.extraData(Zir.Inst.Asm.Output, extra_i); extra_i = output.end; const is_type = @truncate(u1, output_type_bits) != 0; output_type_bits >>= 1; - if (!is_type) { - return sema.fail(block, src, "TODO implement Sema for asm with non `->` output", .{}); + if (is_type) { + // Indicate the output is the asm instruction return value. + arg.* = .none; + const out_ty = try sema.resolveType(block, ret_ty_src, output.data.operand); + expr_ty = try sema.addType(out_ty); + } else { + arg.* = try sema.resolveInst(output.data.operand); } const constraint = sema.code.nullTerminatedString(output.data.constraint); const name = sema.code.nullTerminatedString(output.data.name); needed_capacity += (constraint.len + name.len + (2 + 3)) / 4; - break :blk Output{ - .constraint = constraint, - .name = name, - .ty = try sema.resolveType(block, ret_ty_src, output.data.operand), - }; - }; + outputs[out_i] = .{ .c = constraint, .n = name }; + } const args = try sema.arena.alloc(Air.Inst.Ref, inputs_len); - const inputs = try sema.arena.alloc(struct { c: []const u8, n: []const u8 }, inputs_len); + const inputs = try sema.arena.alloc(ConstraintName, inputs_len); for (args) |*arg, arg_i| { const input = sema.code.extraData(Zir.Inst.Asm.Input, extra_i); @@ -11405,7 +11402,7 @@ fn zirAsm( const asm_air = try block.addInst(.{ .tag = .assembly, .data = .{ .ty_pl = .{ - .ty = if (output) |o| try sema.addType(o.ty) else Air.Inst.Ref.void_type, + .ty = expr_ty, .payload = sema.addExtraAssumeCapacity(Air.Asm{ .source_len = @intCast(u32, asm_source.len), .outputs_len = outputs_len, @@ -11414,18 +11411,15 @@ fn zirAsm( }), } }, }); - if (output != null) { - // Indicate the output is the asm instruction return value. - sema.air_extra.appendAssumeCapacity(@enumToInt(Air.Inst.Ref.none)); - } + sema.appendRefsAssumeCapacity(out_args); sema.appendRefsAssumeCapacity(args); - if (output) |o| { + for (outputs) |o| { const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); - mem.copy(u8, buffer, o.constraint); - buffer[o.constraint.len] = 0; - mem.copy(u8, buffer[o.constraint.len + 1 ..], o.name); - buffer[o.constraint.len + 1 + o.name.len] = 0; - sema.air_extra.items.len += (o.constraint.len + o.name.len + (2 + 3)) / 4; + mem.copy(u8, buffer, o.c); + buffer[o.c.len] = 0; + mem.copy(u8, buffer[o.c.len + 1 ..], o.n); + buffer[o.c.len + 1 + o.n.len] = 0; + sema.air_extra.items.len += (o.c.len + o.n.len + (2 + 3)) / 4; } for (inputs) |input| { const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index f7a1d732b4..a56dd8cf6f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5435,10 +5435,6 @@ pub const FuncGen = struct { const inputs = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra_i..][0..extra.data.inputs_len]); extra_i += inputs.len; - if (outputs.len > 1) { - return self.todo("implement llvm codegen for asm with more than 1 output", .{}); - } - var llvm_constraints: std.ArrayListUnmanaged(u8) = .{}; defer llvm_constraints.deinit(self.gpa); @@ -5446,7 +5442,10 @@ pub const FuncGen = struct { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const llvm_params_len = inputs.len; + const return_count: u8 = for (outputs) |output| { + if (output == .none) break 1; + } else 0; + const llvm_params_len = inputs.len + outputs.len - return_count; const llvm_param_types = try arena.alloc(*const llvm.Type, llvm_params_len); const llvm_param_values = try arena.alloc(*const llvm.Value, llvm_params_len); var llvm_param_i: usize = 0; @@ -5456,9 +5455,6 @@ pub const FuncGen = struct { try name_map.ensureUnusedCapacity(arena, outputs.len + inputs.len); for (outputs) |output| { - if (output != .none) { - return self.todo("implement inline asm with non-returned output", .{}); - } const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0); const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); @@ -5471,6 +5467,15 @@ pub const FuncGen = struct { llvm_constraints.appendAssumeCapacity(','); } llvm_constraints.appendAssumeCapacity('='); + if (output != .none) { + try llvm_constraints.ensureUnusedCapacity(self.gpa, llvm_constraints.capacity + 1); + llvm_constraints.appendAssumeCapacity('*'); + + const output_inst = try self.resolveInst(output); + llvm_param_values[llvm_param_i] = output_inst; + llvm_param_types[llvm_param_i] = output_inst.typeOf(); + llvm_param_i += 1; + } llvm_constraints.appendSliceAssumeCapacity(constraint[1..]); name_map.putAssumeCapacityNoClobber(name, {}); From 6de9eea7bce4023ae150fb5b4d417d66b9bf13fb Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Jun 2022 19:49:40 +0300 Subject: [PATCH 4/5] stage2 llvm: fix float/int conversion compiler-rt calls --- src/codegen/llvm.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a56dd8cf6f..dcdf4888ea 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5012,7 +5012,7 @@ pub const FuncGen = struct { const compiler_rt_operand_abbrev = compilerRtFloatAbbrev(operand_bits); const compiler_rt_dest_abbrev = compilerRtIntAbbrev(rt_int_bits); - const sign_prefix = if (dest_scalar_ty.isSignedInt()) "" else "un"; + const sign_prefix = if (dest_scalar_ty.isSignedInt()) "" else "uns"; var fn_name_buf: [64]u8 = undefined; const fn_name = std.fmt.bufPrintZ(&fn_name_buf, "__fix{s}{s}f{s}i", .{ @@ -9289,7 +9289,7 @@ fn needDbgVarWorkaround(dg: *DeclGen, ty: Type) bool { } fn compilerRtIntBits(bits: u16) u16 { - inline for (.{ 8, 16, 32, 64, 128 }) |b| { + inline for (.{ 32, 64, 128 }) |b| { if (bits <= b) { return b; } From 413577c881963559f7f357bfd90f4ade6d6de20d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Jun 2022 19:14:07 +0300 Subject: [PATCH 5/5] std: adjust for stage2 semantics --- lib/std/io/bit_reader.zig | 10 +++------- lib/std/io/stream_source.zig | 1 + lib/std/math/big/rational.zig | 2 ++ lib/std/net.zig | 11 ++++++++--- lib/std/net/test.zig | 4 ++++ lib/std/priority_queue.zig | 1 + lib/std/simd.zig | 1 + 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/std/io/bit_reader.zig b/lib/std/io/bit_reader.zig index aebb189942..15262f67a2 100644 --- a/lib/std/io/bit_reader.zig +++ b/lib/std/io/bit_reader.zig @@ -87,13 +87,9 @@ pub fn BitReader(endian: std.builtin.Endian, comptime ReaderType: type) type { //copy bytes until we have enough bits, then leave the rest in bit_buffer while (out_bits.* < bits) { const n = bits - out_bits.*; - const next_byte = self.forward_reader.readByte() catch |err| { - if (err == error.EndOfStream) { - return @intCast(U, out_buffer); - } - //@BUG: See #1810. Not sure if the bug is that I have to do this for some - // streams, or that I don't for streams with emtpy errorsets. - return @errSetCast(Error, err); + const next_byte = self.forward_reader.readByte() catch |err| switch (err) { + error.EndOfStream => return @intCast(U, out_buffer), + else => |e| return e, }; switch (endian) { diff --git a/lib/std/io/stream_source.zig b/lib/std/io/stream_source.zig index ce5256028c..2345a97855 100644 --- a/lib/std/io/stream_source.zig +++ b/lib/std/io/stream_source.zig @@ -114,6 +114,7 @@ test "StreamSource (mutable buffer)" { } test "StreamSource (const buffer)" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; const buffer: [64]u8 = "Hello, World!".* ++ ([1]u8{0xAA} ** 51); var source = StreamSource{ .const_buffer = std.io.fixedBufferStream(&buffer) }; diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig index de6804ca01..e83b017335 100644 --- a/lib/std/math/big/rational.zig +++ b/lib/std/math/big/rational.zig @@ -573,6 +573,7 @@ test "big.rational setFloatString" { } test "big.rational toFloat" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; var a = try Rational.init(testing.allocator); defer a.deinit(); @@ -586,6 +587,7 @@ test "big.rational toFloat" { } test "big.rational set/to Float round-trip" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; var a = try Rational.init(testing.allocator); defer a.deinit(); var prng = std.rand.DefaultPrng.init(0x5EED); diff --git a/lib/std/net.zig b/lib/std/net.zig index 2bd3e6cfb1..235ad8496a 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1342,7 +1342,8 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { }; defer file.close(); - const stream = std.io.bufferedReader(file.reader()).reader(); + var buf_reader = std.io.bufferedReader(file.reader()); + const stream = buf_reader.reader(); var line_buf: [512]u8 = undefined; while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { error.StreamTooLong => blk: { @@ -1353,7 +1354,10 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { }, else => |e| return e, }) |line| { - const no_comment_line = mem.split(u8, line, "#").next().?; + const no_comment_line = no_comment_line: { + var split = mem.split(u8, line, "#"); + break :no_comment_line split.next().?; + }; var line_it = mem.tokenize(u8, no_comment_line, " \t"); const token = line_it.next() orelse continue; @@ -1363,7 +1367,8 @@ fn getResolvConf(allocator: mem.Allocator, rc: *ResolvConf) !void { const name = colon_it.next().?; const value_txt = colon_it.next() orelse continue; const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { - error.Overflow => 255, + // TODO https://github.com/ziglang/zig/issues/11812 + error.Overflow => @as(u8, 255), error.InvalidCharacter => continue, }; if (mem.eql(u8, name, "ndots")) { diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index f2946777bd..710eb91376 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -5,6 +5,7 @@ const mem = std.mem; const testing = std.testing; test "parse and render IPv6 addresses" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest; var buffer: [100]u8 = undefined; @@ -67,6 +68,7 @@ test "invalid but parseable IPv6 scope ids" { } test "parse and render IPv4 addresses" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest; var buffer: [18]u8 = undefined; @@ -91,6 +93,7 @@ test "parse and render IPv4 addresses" { } test "parse and render UNIX addresses" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest; if (!net.has_unix_sockets) return error.SkipZigTest; @@ -104,6 +107,7 @@ test "parse and render UNIX addresses" { } test "resolve DNS" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; if (builtin.os.tag == .wasi) return error.SkipZigTest; if (builtin.os.tag == .windows) { diff --git a/lib/std/priority_queue.zig b/lib/std/priority_queue.zig index ebc13a9974..51671bca78 100644 --- a/lib/std/priority_queue.zig +++ b/lib/std/priority_queue.zig @@ -399,6 +399,7 @@ test "std.PriorityQueue: fromOwnedSlice trivial case 1" { } test "std.PriorityQueue: fromOwnedSlice" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 }; const heap_items = try testing.allocator.dupe(u32, items[0..]); var queue = PQlt.fromOwnedSlice(testing.allocator, heap_items[0..], {}); diff --git a/lib/std/simd.zig b/lib/std/simd.zig index a30622aef6..a7ce0ab3fd 100644 --- a/lib/std/simd.zig +++ b/lib/std/simd.zig @@ -160,6 +160,7 @@ pub fn extract( } test "vector patterns" { + if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; const base = @Vector(4, u32){ 10, 20, 30, 40 }; const other_base = @Vector(4, u32){ 55, 66, 77, 88 };