authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-18 17:01:45-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-20 02:47:20-07:00
logcd04b49041200b36c5af23ac3700cbfa82f037ca
tree415600adaea3553fd3bf3020958812672f6aa97c
parent5626bb45d24cd4a57a4f6a1c0f41ec0feb276f7b

stage2: fix `@call` when used in a comptime or nosuspend block

`@call` allows specifying the modifier explicitly, however it can still appear in a context that overrides the modifier. This commit adds flags to the BuiltinCall ZIR encoding. Since we have unused bits I also threw in the ensure_result_used mechanism. I also deleted a behavior test that was checking for bound function behavior where I think stage2 behavior is correct and stage1 behavior is incorrect.

5 files changed, 94 insertions(+), 36 deletions(-)

src/AstGen.zig+14-1
...@@ -71,6 +71,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {...@@ -71,6 +71,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
71 Zir.Inst.Ref => @enumToInt(@field(extra, field.name)),71 Zir.Inst.Ref => @enumToInt(@field(extra, field.name)),
72 i32 => @bitCast(u32, @field(extra, field.name)),72 i32 => @bitCast(u32, @field(extra, field.name)),
73 Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)),73 Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)),
74 Zir.Inst.BuiltinCall.Flags => @bitCast(u32, @field(extra, field.name)),
74 Zir.Inst.SwitchBlock.Bits => @bitCast(u32, @field(extra, field.name)),75 Zir.Inst.SwitchBlock.Bits => @bitCast(u32, @field(extra, field.name)),
75 Zir.Inst.ExtendedFunc.Bits => @bitCast(u32, @field(extra, field.name)),76 Zir.Inst.ExtendedFunc.Bits => @bitCast(u32, @field(extra, field.name)),
76 else => @compileError("bad field type"),77 else => @compileError("bad field type"),
...@@ -2213,6 +2214,14 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner...@@ -2213,6 +2214,14 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
2213 slot.* = @bitCast(u32, flags);2214 slot.* = @bitCast(u32, flags);
2214 break :b true;2215 break :b true;
2215 },2216 },
2217 .builtin_call => {
2218 const extra_index = gz.astgen.instructions.items(.data)[inst].pl_node.payload_index;
2219 const slot = &gz.astgen.extra.items[extra_index];
2220 var flags = @bitCast(Zir.Inst.BuiltinCall.Flags, slot.*);
2221 flags.ensure_result_used = true;
2222 slot.* = @bitCast(u32, flags);
2223 break :b true;
2224 },
22162225
2217 // ZIR instructions that might be a type other than `noreturn` or `void`.2226 // ZIR instructions that might be a type other than `noreturn` or `void`.
2218 .add,2227 .add,
...@@ -2412,7 +2421,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner...@@ -2412,7 +2421,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
2412 .atomic_load,2421 .atomic_load,
2413 .atomic_rmw,2422 .atomic_rmw,
2414 .mul_add,2423 .mul_add,
2415 .builtin_call,
2416 .field_parent_ptr,2424 .field_parent_ptr,
2417 .maximum,2425 .maximum,
2418 .minimum,2426 .minimum,
...@@ -7502,6 +7510,11 @@ fn builtinCall(...@@ -7502,6 +7510,11 @@ fn builtinCall(
7502 .options = options,7510 .options = options,
7503 .callee = callee,7511 .callee = callee,
7504 .args = args,7512 .args = args,
7513 .flags = .{
7514 .is_nosuspend = gz.nosuspend_node != 0,
7515 .is_comptime = gz.force_comptime,
7516 .ensure_result_used = false,
7517 },
7505 });7518 });
7506 return rvalue(gz, rl, result, node);7519 return rvalue(gz, rl, result, node);
7507 },7520 },
src/Sema.zig+42-7
...@@ -16097,12 +16097,12 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -16097,12 +16097,12 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
16097 const args_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };16097 const args_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
16098 const call_src = inst_data.src();16098 const call_src = inst_data.src();
1609916099
16100 const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index);16100 const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data;
16101 var func = sema.resolveInst(extra.data.callee);16101 var func = sema.resolveInst(extra.callee);
16102 const options = sema.resolveInst(extra.data.options);16102 const options = sema.resolveInst(extra.options);
16103 const args = sema.resolveInst(extra.data.args);16103 const args = sema.resolveInst(extra.args);
1610416104
16105 const modifier: std.builtin.CallOptions.Modifier = modifier: {16105 const wanted_modifier: std.builtin.CallOptions.Modifier = modifier: {
16106 const call_options_ty = try sema.getBuiltinType(block, options_src, "CallOptions");16106 const call_options_ty = try sema.getBuiltinType(block, options_src, "CallOptions");
16107 const coerced_options = try sema.coerce(block, call_options_ty, options, options_src);16107 const coerced_options = try sema.coerce(block, call_options_ty, options, options_src);
1610816108
...@@ -16118,6 +16118,41 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -16118,6 +16118,41 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
16118 break :modifier modifier_val.toEnum(std.builtin.CallOptions.Modifier);16118 break :modifier modifier_val.toEnum(std.builtin.CallOptions.Modifier);
16119 };16119 };
1612016120
16121 const modifier: std.builtin.CallOptions.Modifier = switch (wanted_modifier) {
16122 // These can be upgraded to comptime or nosuspend calls.
16123 .auto, .never_tail, .no_async => m: {
16124 if (extra.flags.is_comptime) {
16125 break :m .compile_time;
16126 }
16127 if (extra.flags.is_nosuspend) {
16128 break :m .no_async;
16129 }
16130 break :m wanted_modifier;
16131 },
16132 // These can be upgraded to comptime. nosuspend bit can be safely ignored.
16133 .always_tail, .always_inline, .compile_time => m: {
16134 if (extra.flags.is_comptime) {
16135 break :m .compile_time;
16136 }
16137 break :m wanted_modifier;
16138 },
16139 .async_kw => m: {
16140 if (extra.flags.is_nosuspend) {
16141 return sema.fail(block, options_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{});
16142 }
16143 if (extra.flags.is_comptime) {
16144 return sema.fail(block, options_src, "modifier 'async_kw' cannot be used in combination with comptime function call", .{});
16145 }
16146 break :m wanted_modifier;
16147 },
16148 .never_inline => m: {
16149 if (extra.flags.is_comptime) {
16150 return sema.fail(block, options_src, "modifier 'never_inline' cannot be used in combination with comptime function call", .{});
16151 }
16152 break :m wanted_modifier;
16153 },
16154 };
16155
16121 const args_ty = sema.typeOf(args);16156 const args_ty = sema.typeOf(args);
16122 if (!args_ty.isTuple() and args_ty.tag() != .empty_struct_literal) {16157 if (!args_ty.isTuple() and args_ty.tag() != .empty_struct_literal) {
16123 return sema.fail(block, args_src, "expected a tuple, found {}", .{args_ty.fmt(sema.mod)});16158 return sema.fail(block, args_src, "expected a tuple, found {}", .{args_ty.fmt(sema.mod)});
...@@ -16141,8 +16176,8 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -16141,8 +16176,8 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
16141 resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);16176 resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
16142 }16177 }
16143 }16178 }
1614416179 const ensure_result_used = extra.flags.ensure_result_used;
16145 return sema.analyzeCall(block, func, func_src, call_src, modifier, false, resolved_args);16180 return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args);
16146}16181}
1614716182
16148fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {16183fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
src/Zir.zig+22-4
...@@ -72,6 +72,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en...@@ -72,6 +72,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en
72 Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]),72 Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]),
73 i32 => @bitCast(i32, code.extra[i]),73 i32 => @bitCast(i32, code.extra[i]),
74 Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]),74 Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]),
75 Inst.BuiltinCall.Flags => @bitCast(Inst.BuiltinCall.Flags, code.extra[i]),
75 Inst.SwitchBlock.Bits => @bitCast(Inst.SwitchBlock.Bits, code.extra[i]),76 Inst.SwitchBlock.Bits => @bitCast(Inst.SwitchBlock.Bits, code.extra[i]),
76 Inst.ExtendedFunc.Bits => @bitCast(Inst.ExtendedFunc.Bits, code.extra[i]),77 Inst.ExtendedFunc.Bits => @bitCast(Inst.ExtendedFunc.Bits, code.extra[i]),
77 else => @compileError("bad field type"),78 else => @compileError("bad field type"),
...@@ -280,8 +281,13 @@ pub const Inst = struct {...@@ -280,8 +281,13 @@ pub const Inst = struct {
280 /// Uses the `break` union field.281 /// Uses the `break` union field.
281 break_inline,282 break_inline,
282 /// Function call.283 /// Function call.
283 /// Uses `pl_node`. AST node is the function call. Payload is `Call`.284 /// Uses the `pl_node` union field with payload `Call`.
285 /// AST node is the function call.
284 call,286 call,
287 /// Implements the `@call` builtin.
288 /// Uses the `pl_node` union field with payload `BuiltinCall`.
289 /// AST node is the builtin call.
290 builtin_call,
285 /// `<`291 /// `<`
286 /// Uses the `pl_node` union field. Payload is `Bin`.292 /// Uses the `pl_node` union field. Payload is `Bin`.
287 cmp_lt,293 cmp_lt,
...@@ -916,9 +922,6 @@ pub const Inst = struct {...@@ -916,9 +922,6 @@ pub const Inst = struct {
916 /// The addend communicates the type of the builtin.922 /// The addend communicates the type of the builtin.
917 /// The mulends need to be coerced to the same type.923 /// The mulends need to be coerced to the same type.
918 mul_add,924 mul_add,
919 /// Implements the `@call` builtin.
920 /// Uses the `pl_node` union field with payload `BuiltinCall`.
921 builtin_call,
922 /// Implements the `@fieldParentPtr` builtin.925 /// Implements the `@fieldParentPtr` builtin.
923 /// Uses the `pl_node` union field with payload `FieldParentPtr`.926 /// Uses the `pl_node` union field with payload `FieldParentPtr`.
924 field_parent_ptr,927 field_parent_ptr,
...@@ -2733,9 +2736,24 @@ pub const Inst = struct {...@@ -2733,9 +2736,24 @@ pub const Inst = struct {
2733 };2736 };
27342737
2735 pub const BuiltinCall = struct {2738 pub const BuiltinCall = struct {
2739 // Note: Flags *must* come first so that unusedResultExpr
2740 // can find it when it goes to modify them.
2741 flags: Flags,
2736 options: Ref,2742 options: Ref,
2737 callee: Ref,2743 callee: Ref,
2738 args: Ref,2744 args: Ref,
2745
2746 pub const Flags = packed struct {
2747 is_nosuspend: bool,
2748 is_comptime: bool,
2749 ensure_result_used: bool,
2750 _: u29 = undefined,
2751
2752 comptime {
2753 if (@sizeOf(Flags) != 4 or @bitSizeOf(Flags) != 32)
2754 @compileError("Layout of BuiltinCall.Flags needs to be updated!");
2755 }
2756 };
2739 };2757 };
27402758
2741 /// This data is stored inside extra, with two sets of trailing `Ref`:2759 /// This data is stored inside extra, with two sets of trailing `Ref`:
src/print_zir.zig+7-2
...@@ -365,7 +365,7 @@ const Writer = struct {...@@ -365,7 +365,7 @@ const Writer = struct {
365 .@"export" => try self.writePlNodeExport(stream, inst),365 .@"export" => try self.writePlNodeExport(stream, inst),
366 .export_value => try self.writePlNodeExportValue(stream, inst),366 .export_value => try self.writePlNodeExportValue(stream, inst),
367367
368 .call => try self.writePlNodeCall(stream, inst),368 .call => try self.writeCall(stream, inst),
369369
370 .block,370 .block,
371 .block_inline,371 .block_inline,
...@@ -793,6 +793,11 @@ const Writer = struct {...@@ -793,6 +793,11 @@ const Writer = struct {
793 fn writeBuiltinCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {793 fn writeBuiltinCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
794 const inst_data = self.code.instructions.items(.data)[inst].pl_node;794 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
795 const extra = self.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data;795 const extra = self.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data;
796
797 try self.writeFlag(stream, "nodiscard ", extra.flags.ensure_result_used);
798 try self.writeFlag(stream, "nosuspend ", extra.flags.is_nosuspend);
799 try self.writeFlag(stream, "comptime ", extra.flags.is_comptime);
800
796 try self.writeInstRef(stream, extra.options);801 try self.writeInstRef(stream, extra.options);
797 try stream.writeAll(", ");802 try stream.writeAll(", ");
798 try self.writeInstRef(stream, extra.callee);803 try self.writeInstRef(stream, extra.callee);
...@@ -1144,7 +1149,7 @@ const Writer = struct {...@@ -1144,7 +1149,7 @@ const Writer = struct {
1144 try self.writeSrc(stream, src);1149 try self.writeSrc(stream, src);
1145 }1150 }
11461151
1147 fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {1152 fn writeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
1148 const inst_data = self.code.instructions.items(.data)[inst].pl_node;1153 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
1149 const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);1154 const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
1150 const args = self.code.refSlice(extra.end, extra.data.flags.args_len);1155 const args = self.code.refSlice(extra.end, extra.data.flags.args_len);
test/behavior/call.zig+9-22
...@@ -19,7 +19,11 @@ test "super basic invocations" {...@@ -19,7 +19,11 @@ test "super basic invocations" {
19}19}
2020
21test "basic invocations" {21test "basic invocations" {
22 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO22 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
23 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
24 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
25 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
26 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
2327
24 const foo = struct {28 const foo = struct {
25 fn foo() i32 {29 fn foo() i32 {
...@@ -41,7 +45,10 @@ test "basic invocations" {...@@ -41,7 +45,10 @@ test "basic invocations" {
41 }45 }
42 {46 {
43 // call of non comptime-known function47 // call of non comptime-known function
44 var alias_foo = foo;48 var alias_foo = switch (builtin.zig_backend) {
49 .stage1 => foo,
50 else => &foo,
51 };
45 try expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234);52 try expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234);
46 try expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234);53 try expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234);
47 try expect(@call(.{ .modifier = .never_inline }, alias_foo, .{}) == 1234);54 try expect(@call(.{ .modifier = .never_inline }, alias_foo, .{}) == 1234);
...@@ -79,26 +86,6 @@ test "tuple parameters" {...@@ -79,26 +86,6 @@ test "tuple parameters" {
79 }86 }
80}87}
8188
82test "comptime call with bound function as parameter" {
83 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
84
85 const S = struct {
86 fn ReturnType(func: anytype) type {
87 return switch (@typeInfo(@TypeOf(func))) {
88 .BoundFn => |info| info,
89 else => unreachable,
90 }.return_type orelse void;
91 }
92
93 fn call_me_maybe() ?i32 {
94 return 123;
95 }
96 };
97
98 var inst: S = undefined;
99 try expectEqual(?i32, S.ReturnType(inst.call_me_maybe));
100}
101
102test "result location of function call argument through runtime condition and struct init" {89test "result location of function call argument through runtime condition and struct init" {
103 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO90 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
104 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO91 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO