| author | |
| committer | |
| log | cd04b49041200b36c5af23ac3700cbfa82f037ca |
| tree | 415600adaea3553fd3bf3020958812672f6aa97c |
| parent | 5626bb45d24cd4a57a4f6a1c0f41ec0feb276f7b |
`@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 | 71 | Zir.Inst.Ref => @enumToInt(@field(extra, field.name)), |
| 72 | 72 | i32 => @bitCast(u32, @field(extra, field.name)), |
| 73 | 73 | Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)), |
| 74 | Zir.Inst.BuiltinCall.Flags => @bitCast(u32, @field(extra, field.name)), | |
| 74 | 75 | Zir.Inst.SwitchBlock.Bits => @bitCast(u32, @field(extra, field.name)), |
| 75 | 76 | Zir.Inst.ExtendedFunc.Bits => @bitCast(u32, @field(extra, field.name)), |
| 76 | 77 | else => @compileError("bad field type"), |
| ... | ... | @@ -2213,6 +2214,14 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner |
| 2213 | 2214 | slot.* = @bitCast(u32, flags); |
| 2214 | 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 | }, | |
| 2216 | 2225 | |
| 2217 | 2226 | // ZIR instructions that might be a type other than `noreturn` or `void`. |
| 2218 | 2227 | .add, |
| ... | ... | @@ -2412,7 +2421,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner |
| 2412 | 2421 | .atomic_load, |
| 2413 | 2422 | .atomic_rmw, |
| 2414 | 2423 | .mul_add, |
| 2415 | .builtin_call, | |
| 2416 | 2424 | .field_parent_ptr, |
| 2417 | 2425 | .maximum, |
| 2418 | 2426 | .minimum, |
| ... | ... | @@ -7502,6 +7510,11 @@ fn builtinCall( |
| 7502 | 7510 | .options = options, |
| 7503 | 7511 | .callee = callee, |
| 7504 | 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 | 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 | 16097 | const args_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; |
| 16098 | 16098 | const call_src = inst_data.src(); |
| 16099 | 16099 | |
| 16100 | const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index); | |
| 16101 | var func = sema.resolveInst(extra.data.callee); | |
| 16102 | const options = sema.resolveInst(extra.data.options); | |
| 16103 | const args = sema.resolveInst(extra.data.args); | |
| 16100 | const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data; | |
| 16101 | var func = sema.resolveInst(extra.callee); | |
| 16102 | const options = sema.resolveInst(extra.options); | |
| 16103 | const args = sema.resolveInst(extra.args); | |
| 16104 | 16104 | |
| 16105 | const modifier: std.builtin.CallOptions.Modifier = modifier: { | |
| 16105 | const wanted_modifier: std.builtin.CallOptions.Modifier = modifier: { | |
| 16106 | 16106 | const call_options_ty = try sema.getBuiltinType(block, options_src, "CallOptions"); |
| 16107 | 16107 | const coerced_options = try sema.coerce(block, call_options_ty, options, options_src); |
| 16108 | 16108 | |
| ... | ... | @@ -16118,6 +16118,41 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 16118 | 16118 | break :modifier modifier_val.toEnum(std.builtin.CallOptions.Modifier); |
| 16119 | 16119 | }; |
| 16120 | 16120 | |
| 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 | 16156 | const args_ty = sema.typeOf(args); |
| 16122 | 16157 | if (!args_ty.isTuple() and args_ty.tag() != .empty_struct_literal) { |
| 16123 | 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 | 16176 | resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty); |
| 16142 | 16177 | } |
| 16143 | 16178 | } |
| 16144 | ||
| 16145 | return sema.analyzeCall(block, func, func_src, call_src, modifier, false, resolved_args); | |
| 16179 | const ensure_result_used = extra.flags.ensure_result_used; | |
| 16180 | return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args); | |
| 16146 | 16181 | } |
| 16147 | 16182 | |
| 16148 | 16183 | fn 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 | 72 | Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]), |
| 73 | 73 | i32 => @bitCast(i32, code.extra[i]), |
| 74 | 74 | Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]), |
| 75 | Inst.BuiltinCall.Flags => @bitCast(Inst.BuiltinCall.Flags, code.extra[i]), | |
| 75 | 76 | Inst.SwitchBlock.Bits => @bitCast(Inst.SwitchBlock.Bits, code.extra[i]), |
| 76 | 77 | Inst.ExtendedFunc.Bits => @bitCast(Inst.ExtendedFunc.Bits, code.extra[i]), |
| 77 | 78 | else => @compileError("bad field type"), |
| ... | ... | @@ -280,8 +281,13 @@ pub const Inst = struct { |
| 280 | 281 | /// Uses the `break` union field. |
| 281 | 282 | break_inline, |
| 282 | 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 | 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 | 292 | /// Uses the `pl_node` union field. Payload is `Bin`. |
| 287 | 293 | cmp_lt, |
| ... | ... | @@ -916,9 +922,6 @@ pub const Inst = struct { |
| 916 | 922 | /// The addend communicates the type of the builtin. |
| 917 | 923 | /// The mulends need to be coerced to the same type. |
| 918 | 924 | mul_add, |
| 919 | /// Implements the `@call` builtin. | |
| 920 | /// Uses the `pl_node` union field with payload `BuiltinCall`. | |
| 921 | builtin_call, | |
| 922 | 925 | /// Implements the `@fieldParentPtr` builtin. |
| 923 | 926 | /// Uses the `pl_node` union field with payload `FieldParentPtr`. |
| 924 | 927 | field_parent_ptr, |
| ... | ... | @@ -2733,9 +2736,24 @@ pub const Inst = struct { |
| 2733 | 2736 | }; |
| 2734 | 2737 | |
| 2735 | 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 | 2742 | options: Ref, |
| 2737 | 2743 | callee: Ref, |
| 2738 | 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 | }; |
| 2740 | 2758 | |
| 2741 | 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 | 365 | .@"export" => try self.writePlNodeExport(stream, inst), |
| 366 | 366 | .export_value => try self.writePlNodeExportValue(stream, inst), |
| 367 | 367 | |
| 368 | .call => try self.writePlNodeCall(stream, inst), | |
| 368 | .call => try self.writeCall(stream, inst), | |
| 369 | 369 | |
| 370 | 370 | .block, |
| 371 | 371 | .block_inline, |
| ... | ... | @@ -793,6 +793,11 @@ const Writer = struct { |
| 793 | 793 | fn writeBuiltinCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { |
| 794 | 794 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; |
| 795 | 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 | 801 | try self.writeInstRef(stream, extra.options); |
| 797 | 802 | try stream.writeAll(", "); |
| 798 | 803 | try self.writeInstRef(stream, extra.callee); |
| ... | ... | @@ -1144,7 +1149,7 @@ const Writer = struct { |
| 1144 | 1149 | try self.writeSrc(stream, src); |
| 1145 | 1150 | } |
| 1146 | 1151 | |
| 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 | 1153 | const inst_data = self.code.instructions.items(.data)[inst].pl_node; |
| 1149 | 1154 | const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index); |
| 1150 | 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 | 19 | } |
| 20 | 20 | |
| 21 | 21 | test "basic invocations" { |
| 22 | if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO | |
| 22 | 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 | |
| 23 | 27 | |
| 24 | 28 | const foo = struct { |
| 25 | 29 | fn foo() i32 { |
| ... | ... | @@ -41,7 +45,10 @@ test "basic invocations" { |
| 41 | 45 | } |
| 42 | 46 | { |
| 43 | 47 | // 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 | 52 | try expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234); |
| 46 | 53 | try expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234); |
| 47 | 54 | try expect(@call(.{ .modifier = .never_inline }, alias_foo, .{}) == 1234); |
| ... | ... | @@ -79,26 +86,6 @@ test "tuple parameters" { |
| 79 | 86 | } |
| 80 | 87 | } |
| 81 | 88 | |
| 82 | test "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 | ||
| 102 | 89 | test "result location of function call argument through runtime condition and struct init" { |
| 103 | 90 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO |
| 104 | 91 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |