From ff72b8a8194573bc1d7f95cbf3228b363194c775 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Feb 2022 12:38:25 +0200 Subject: [PATCH 1/2] stage2: evaluate TypeOf arguments in a separate scope --- src/AstGen.zig | 58 +++++++++++++++++++++++++++---------- src/Sema.zig | 44 +++++++++++++++++++++++++++- src/Zir.zig | 11 +++++++ src/print_zir.zig | 19 ++++++++++-- test/behavior.zig | 2 +- test/behavior/bugs/5474.zig | 54 ++++++++++++++++++---------------- 6 files changed, 142 insertions(+), 46 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 74e3ae17b1..45b074a848 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2159,6 +2159,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .negate, .negate_wrap, .typeof, + .typeof_builtin, .xor, .optional_type, .optional_payload_safe, @@ -6875,29 +6876,54 @@ fn typeOf( scope: *Scope, rl: ResultLoc, node: Ast.Node.Index, - params: []const Ast.Node.Index, + args: []const Ast.Node.Index, ) InnerError!Zir.Inst.Ref { - if (params.len < 1) { + if (args.len < 1) { return gz.astgen.failNode(node, "expected at least 1 argument, found 0", .{}); } - if (params.len == 1) { - const expr_result = try reachableExpr(gz, scope, .none, params[0], node); - const result = try gz.addUnNode(.typeof, expr_result, node); - return rvalue(gz, rl, result, node); - } + const gpa = gz.astgen.gpa; + if (args.len == 1) { + const typeof_inst = try gz.makeBlockInst(.typeof_builtin, node); - const payload_index = try addExtra(gz.astgen, Zir.Inst.NodeMultiOp{ + var typeof_scope = gz.makeSubBlock(scope); + typeof_scope.force_comptime = false; + defer typeof_scope.unstack(); + + const ty_expr = try reachableExpr(&typeof_scope, &typeof_scope.base, .none, args[0], node); + if (!gz.refIsNoReturn(ty_expr)) { + _ = try typeof_scope.addBreak(.break_inline, typeof_inst, ty_expr); + } + try typeof_scope.setBlockBody(typeof_inst); + + // typeof_scope unstacked now, can add new instructions to gz + try gz.instructions.append(gpa, typeof_inst); + return rvalue(gz, rl, indexToRef(typeof_inst), node); + } + const payload_size: u32 = std.meta.fields(Zir.Inst.TypeOfPeer).len; + const payload_index = try reserveExtra(gz.astgen, payload_size + args.len); + var args_index = payload_index + payload_size; + + const typeof_inst = try gz.addExtendedMultiOpPayloadIndex(.typeof_peer, payload_index, args.len); + + var typeof_scope = gz.makeSubBlock(scope); + typeof_scope.force_comptime = false; + + for (args) |arg, i| { + const param_ref = try reachableExpr(&typeof_scope, &typeof_scope.base, .none, arg, node); + gz.astgen.extra.items[args_index + i] = @enumToInt(param_ref); + } + _ = try typeof_scope.addBreak(.break_inline, refToIndex(typeof_inst).?, .void_value); + + const body = typeof_scope.instructionsSlice(); + gz.astgen.setExtra(payload_index, Zir.Inst.TypeOfPeer{ + .body_len = @intCast(u32, body.len), + .body_index = @intCast(u32, gz.astgen.extra.items.len), .src_node = gz.nodeIndexToRelative(node), }); - var extra_index = try reserveExtra(gz.astgen, params.len); - for (params) |param| { - const param_ref = try reachableExpr(gz, scope, .none, param, node); - gz.astgen.extra.items[extra_index] = @enumToInt(param_ref); - extra_index += 1; - } + try gz.astgen.extra.appendSlice(gpa, body); + typeof_scope.unstack(); - const result = try gz.addExtendedMultiOpPayloadIndex(.typeof_peer, payload_index, params.len); - return rvalue(gz, rl, result, node); + return rvalue(gz, rl, typeof_inst, node); } fn builtinCall( diff --git a/src/Sema.zig b/src/Sema.zig index ef9ba41bec..eca88f8922 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -124,6 +124,7 @@ pub const Block = struct { runtime_index: u32 = 0, is_comptime: bool, + is_typeof: bool = false, /// when null, it is determined by build mode, changed by @setRuntimeSafety want_safety: ?bool = null, @@ -181,6 +182,7 @@ pub const Block = struct { .label = null, .inlining = parent.inlining, .is_comptime = parent.is_comptime, + .is_typeof = parent.is_typeof, .runtime_cond = parent.runtime_cond, .runtime_loop = parent.runtime_loop, .runtime_index = parent.runtime_index, @@ -682,6 +684,7 @@ fn analyzeBodyInner( .size_of => try sema.zirSizeOf(block, inst), .bit_size_of => try sema.zirBitSizeOf(block, inst), .typeof => try sema.zirTypeof(block, inst), + .typeof_builtin => try sema.zirTypeofBuiltin(block, inst), .log2_int_type => try sema.zirLog2IntType(block, inst), .typeof_log2_int_type => try sema.zirTypeofLog2IntType(block, inst), .xor => try sema.zirBitwise(block, inst, .xor), @@ -10574,6 +10577,29 @@ fn zirTypeof(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. return sema.addType(operand_ty); } +fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const pl_node = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; + + var child_block: Block = .{ + .parent = block, + .sema = sema, + .src_decl = block.src_decl, + .namespace = block.namespace, + .wip_capture_scope = block.wip_capture_scope, + .instructions = .{}, + .inlining = block.inlining, + .is_comptime = false, + .is_typeof = true, + }; + defer child_block.instructions.deinit(sema.gpa); + + const operand = try sema.resolveBody(&child_block, body, inst); + const operand_ty = sema.typeOf(operand); + return sema.addType(operand_ty); +} + fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); @@ -10624,8 +10650,24 @@ fn zirTypeofPeer( const tracy = trace(@src()); defer tracy.end(); - const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand); + const extra = sema.code.extraData(Zir.Inst.TypeOfPeer, extended.operand); const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const body = sema.code.extra[extra.data.body_index..][0..extra.data.body_len]; + + var child_block: Block = .{ + .parent = block, + .sema = sema, + .src_decl = block.src_decl, + .namespace = block.namespace, + .wip_capture_scope = block.wip_capture_scope, + .instructions = .{}, + .inlining = block.inlining, + .is_comptime = false, + .is_typeof = true, + }; + defer child_block.instructions.deinit(sema.gpa); + _ = try sema.analyzeBody(&child_block, body); + const args = sema.code.refSlice(extra.end, extended.small); const inst_list = try sema.gpa.alloc(Air.Inst.Ref, args.len); diff --git a/src/Zir.zig b/src/Zir.zig index c74b2c1d8f..3795c28daf 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -547,6 +547,9 @@ pub const Inst = struct { /// Returns the type of a value. /// Uses the `un_node` field. typeof, + /// Implements `@TypeOf` for one operand. + /// Uses the `pl_node` field. + typeof_builtin, /// Given a value, look at the type of it, which must be an integer type. /// Returns the integer type for the RHS of a shift operation. /// Uses the `un_node` field. @@ -1067,6 +1070,7 @@ pub const Inst = struct { .negate, .negate_wrap, .typeof, + .typeof_builtin, .xor, .optional_type, .optional_payload_safe, @@ -1429,6 +1433,7 @@ pub const Inst = struct { .ptr_cast = .pl_node, .truncate = .pl_node, .align_cast = .pl_node, + .typeof_builtin = .pl_node, .has_decl = .pl_node, .has_field = .pl_node, @@ -2391,6 +2396,12 @@ pub const Inst = struct { }; }; + pub const TypeOfPeer = struct { + src_node: i32, + body_len: u32, + body_index: u32, + }; + pub const BuiltinCall = struct { options: Ref, callee: Ref, diff --git a/src/print_zir.zig b/src/print_zir.zig index 66c3ad7668..9ef67e3213 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -374,6 +374,7 @@ const Writer = struct { .validate_array_init, .validate_array_init_comptime, .c_import, + .typeof_builtin, => try self.writePlNodeBlock(stream, inst), .condbr, @@ -458,9 +459,8 @@ const Writer = struct { .variable => try self.writeVarExtended(stream, extended), .alloc => try self.writeAllocExtended(stream, extended), - .compile_log, - .typeof_peer, - => try self.writeNodeMultiOp(stream, extended), + .compile_log => try self.writeNodeMultiOp(stream, extended), + .typeof_peer => try self.writeTypeofPeer(stream, extended), .add_with_overflow, .sub_with_overflow, @@ -1966,6 +1966,19 @@ const Writer = struct { try self.writeSrc(stream, src); } + fn writeTypeofPeer(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.TypeOfPeer, extended.operand); + const body = self.code.extra[extra.data.body_index..][0..extra.data.body_len]; + try self.writeBracedBody(stream, body); + try stream.writeAll(",["); + const args = self.code.refSlice(extra.end, extended.small); + for (args) |arg, i| { + if (i != 0) try stream.writeAll(", "); + try self.writeInstRef(stream, arg); + } + try stream.writeAll("])"); + } + fn writeBoolBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].bool_br; const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index); diff --git a/test/behavior.zig b/test/behavior.zig index ed2012281c..80b408b829 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -102,6 +102,7 @@ test { builtin.zig_backend != .stage2_wasm) { // Tests that pass for stage1, llvm backend, C backend + _ = @import("behavior/bugs/5474.zig"); _ = @import("behavior/bugs/9584.zig"); _ = @import("behavior/bugs/10970.zig"); _ = @import("behavior/cast_int.zig"); @@ -152,7 +153,6 @@ test { _ = @import("behavior/bugs/4328.zig"); _ = @import("behavior/bugs/5398.zig"); _ = @import("behavior/bugs/5413.zig"); - _ = @import("behavior/bugs/5474.zig"); _ = @import("behavior/bugs/5487.zig"); _ = @import("behavior/bugs/6456.zig"); _ = @import("behavior/bugs/6781.zig"); diff --git a/test/behavior/bugs/5474.zig b/test/behavior/bugs/5474.zig index 8a0180a799..937cfc160c 100644 --- a/test/behavior/bugs/5474.zig +++ b/test/behavior/bugs/5474.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); // baseline (control) struct with array of scalar const Box0 = struct { @@ -25,33 +26,36 @@ const Box2 = struct { }; }; -fn doTest() !void { - // var - { - var box0: Box0 = .{ .items = undefined }; - try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == false); +fn mutable() !void { + var box0: Box0 = .{ .items = undefined }; + try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == false); - var box1: Box1 = .{ .items = undefined }; - try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == false); + var box1: Box1 = .{ .items = undefined }; + try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == false); - var box2: Box2 = .{ .items = undefined }; - try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == false); - } - - // const - { - const box0: Box0 = .{ .items = undefined }; - try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == true); - - const box1: Box1 = .{ .items = undefined }; - try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == true); - - const box2: Box2 = .{ .items = undefined }; - try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == true); - } + var box2: Box2 = .{ .items = undefined }; + try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == false); } -test "pointer-to-array constness for zero-size elements" { - try doTest(); - comptime try doTest(); +fn constant() !void { + const box0: Box0 = .{ .items = undefined }; + try std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == true); + + const box1: Box1 = .{ .items = undefined }; + try std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == true); + + const box2: Box2 = .{ .items = undefined }; + try std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == true); +} + +test "pointer-to-array constness for zero-size elements, var" { + try mutable(); + comptime try mutable(); +} + +test "pointer-to-array constness for zero-size elements, const" { + if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + + try constant(); + comptime try constant(); } From 315d4e84425ddff888de8d464f657981e4c45da7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Feb 2022 18:06:06 +0200 Subject: [PATCH 2/2] stage2: do not require function when evaluating typeOf We only care about the instructions type; it will never actually be codegen'd. --- src/Sema.zig | 5 +++-- test/behavior.zig | 2 +- test/behavior/bugs/4328.zig | 23 ++++++++++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index eca88f8922..41398016e5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -13427,7 +13427,7 @@ fn zirBuiltinExtern( } fn requireFunctionBlock(sema: *Sema, block: *Block, src: LazySrcLoc) !void { - if (sema.func == null) { + if (sema.func == null and !block.is_typeof) { return sema.fail(block, src, "instruction illegal outside function body", .{}); } } @@ -14194,7 +14194,8 @@ fn fieldCallBind( if (first_param_tag == .var_args_param or first_param_tag == .generic_poison or ( first_param_type.zigTypeTag() == .Pointer and - first_param_type.ptrSize() == .One and + (first_param_type.ptrSize() == .One or + first_param_type.ptrSize() == .C) and first_param_type.childType().eql(concrete_ty))) { // zig fmt: on diff --git a/test/behavior.zig b/test/behavior.zig index 80b408b829..12d840b195 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -38,6 +38,7 @@ test { _ = @import("behavior/bugs/3112.zig"); _ = @import("behavior/bugs/3367.zig"); _ = @import("behavior/bugs/3586.zig"); + _ = @import("behavior/bugs/4328.zig"); _ = @import("behavior/bugs/4560.zig"); _ = @import("behavior/bugs/4769_a.zig"); _ = @import("behavior/bugs/4769_b.zig"); @@ -150,7 +151,6 @@ test { _ = @import("behavior/bugs/1851.zig"); _ = @import("behavior/bugs/3384.zig"); _ = @import("behavior/bugs/3779.zig"); - _ = @import("behavior/bugs/4328.zig"); _ = @import("behavior/bugs/5398.zig"); _ = @import("behavior/bugs/5413.zig"); _ = @import("behavior/bugs/5487.zig"); diff --git a/test/behavior/bugs/4328.zig b/test/behavior/bugs/4328.zig index 26b96d8d6f..cd7386939c 100644 --- a/test/behavior/bugs/4328.zig +++ b/test/behavior/bugs/4328.zig @@ -1,4 +1,5 @@ -const expectEqual = @import("std").testing.expectEqual; +const expect = @import("std").testing.expect; +const builtin = @import("builtin"); const FILE = extern struct { dummy_field: u8, @@ -16,18 +17,20 @@ const S = extern struct { }; test "Extern function calls in @TypeOf" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + const Test = struct { fn test_fn_1(a: anytype, b: anytype) @TypeOf(printf("%d %s\n", a, b)) { return 0; } - fn test_fn_2(a: anytype) @TypeOf((S{ .state = 0 }).s_do_thing(a)) { + fn test_fn_2(s: anytype, a: anytype) @TypeOf(s.s_do_thing(a)) { return 1; } fn doTheTest() !void { - try expectEqual(c_int, @TypeOf(test_fn_1(0, 42))); - try expectEqual(c_short, @TypeOf(test_fn_2(0))); + try expect(@TypeOf(test_fn_1(0, 42)) == c_int); + try expect(@TypeOf(test_fn_2(&S{ .state = 1 }, 0)) == c_short); } }; @@ -36,13 +39,15 @@ test "Extern function calls in @TypeOf" { } test "Peer resolution of extern function calls in @TypeOf" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + const Test = struct { fn test_fn() @TypeOf(ftell(null), fputs(null, null)) { return 0; } fn doTheTest() !void { - try expectEqual(c_long, @TypeOf(test_fn())); + try expect(@TypeOf(test_fn()) == c_long); } }; @@ -51,6 +56,10 @@ test "Peer resolution of extern function calls in @TypeOf" { } test "Extern function calls, dereferences and field access in @TypeOf" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + const Test = struct { fn test_fn_1(a: c_long) @TypeOf(fopen("test", "r").*) { _ = a; @@ -63,8 +72,8 @@ test "Extern function calls, dereferences and field access in @TypeOf" { } fn doTheTest() !void { - try expectEqual(FILE, @TypeOf(test_fn_1(0))); - try expectEqual(u8, @TypeOf(test_fn_2(0))); + try expect(@TypeOf(test_fn_1(0)) == FILE); + try expect(@TypeOf(test_fn_2(0)) == u8); } };