Skip to content

Commit 32148b4

Browse files
mluggandrewrk
authored andcommitted
Zir: eliminate field_call_bind and field_call_bind_named
This commit removes the `field_call_bind` and `field_call_bind_named` ZIR instructions, replacing them with a `field_call` instruction which does the bind and call in one. `field_call_bind` is an unfortunate instruction. It's tied into one very specific usage pattern - its result can only be used as a callee. This means that it creates a value of a "pseudo-type" of sorts, `bound_fn` - this type used to exist in Zig, but now we just hide it from the user and have AstGen ensure it's only used in one way. This is quite silly - `Type` and `Value` should, as much as possible, reflect real Zig types and values. It makes sense to instead encode the `a.b()` syntax as its own ZIR instruction, so that's what we do here. This commit introduces a new instruction, `field_call`. It's like `call`, but rather than a callee ref, it contains a ref to the object pointer (`&a` in `a.b()`) and the string field name (`b`). This eliminates `bound_fn` from the language, and slightly decreases the size of generated ZIR - stats below. This commit does remove a few usages which used to be allowed: - `@field(a, "b")()` - `@call(.auto, a.b, .{})` - `@call(.auto, @field(a, "b"), .{})` These forms used to work just like `a.b()`, but are no longer allowed. I believe this is the correct choice for a few reasons: - `a.b()` is a purely *syntactic* form; for instance, `(a.b)()` is not valid. This means it is *not* inconsistent to not allow it in these cases; the special case here isn't "a field access as a callee", but rather this exact syntactic form. - The second argument to `@call` looks much more visually distinct from the callee in standard call syntax. To me, this makes it seem strange for that argument to not work like a normal expression in this context. - A more practical argument: it's confusing! `@field` and `@call` are used in very different contexts to standard function calls: the former normally hints at some comptime machinery, and the latter that you want more precise control over parts of a function call. In these contexts, you don't want implicit arguments adding extra confusion: you want to be very explicit about what you're doing. Lastly, some stats. I mentioned before that this change slightly reduces the size of ZIR - this is due to two instructions (`field_call_bind` then `call`) being replaced with one (`field_call`). Here are some numbers: +--------------+----------+----------+--------+ | File | Before | After | Change | +--------------+----------+----------+--------+ | Sema.zig | 4.72M | 4.53M | -4% | | AstGen.zig | 1.52M | 1.48M | -3% | | hash_map.zig | 283.9K | 276.2K | -3% | | math.zig | 312.6K | 305.3K | -2% | +--------------+----------+----------+--------+
1 parent 7077e90 commit 32148b4

16 files changed

+231
-288
lines changed

lib/std/Thread/Mutex.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ const FutexImpl = struct {
169169
}
170170
}
171171

172-
inline fn lockFast(self: *@This(), comptime casFn: []const u8) bool {
172+
inline fn lockFast(self: *@This(), comptime cas_fn_name: []const u8) bool {
173173
// On x86, use `lock bts` instead of `lock cmpxchg` as:
174174
// - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
175175
// - `lock bts` is smaller instruction-wise which makes it better for inlining
@@ -180,7 +180,8 @@ const FutexImpl = struct {
180180

181181
// Acquire barrier ensures grabbing the lock happens before the critical section
182182
// and that the previous lock holder's critical section happens before we grab the lock.
183-
return @field(self.state, casFn)(unlocked, locked, .Acquire, .Monotonic) == null;
183+
const casFn = @field(@TypeOf(self.state), cas_fn_name);
184+
return casFn(&self.state, unlocked, locked, .Acquire, .Monotonic) == null;
184185
}
185186

186187
fn lockSlow(self: *@This()) void {

lib/std/crypto/siphash.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
167167
pub fn hash(msg: []const u8, key: *const [key_length]u8) T {
168168
const aligned_len = msg.len - (msg.len % 8);
169169
var c = Self.init(key);
170-
@call(.always_inline, c.update, .{msg[0..aligned_len]});
171-
return @call(.always_inline, c.final, .{msg[aligned_len..]});
170+
@call(.always_inline, update, .{ &c, msg[0..aligned_len] });
171+
return @call(.always_inline, final, .{ &c, msg[aligned_len..] });
172172
}
173173
};
174174
}

lib/std/hash/auto_hash.zig

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,13 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
6464
/// Strategy is provided to determine if pointers should be followed or not.
6565
pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
6666
const Key = @TypeOf(key);
67+
const Hasher = switch (@typeInfo(@TypeOf(hasher))) {
68+
.Pointer => |ptr| ptr.child,
69+
else => @TypeOf(hasher),
70+
};
6771

6872
if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) {
69-
@call(.always_inline, hasher.update, .{mem.asBytes(&key)});
73+
@call(.always_inline, Hasher.update, .{ hasher, mem.asBytes(&key) });
7074
return;
7175
}
7276

@@ -89,12 +93,12 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
8993
// TODO Check if the situation is better after #561 is resolved.
9094
.Int => {
9195
if (comptime meta.trait.hasUniqueRepresentation(Key)) {
92-
@call(.always_inline, hasher.update, .{std.mem.asBytes(&key)});
96+
@call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key) });
9397
} else {
9498
// Take only the part containing the key value, the remaining
9599
// bytes are undefined and must not be hashed!
96100
const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable;
97-
@call(.always_inline, hasher.update, .{std.mem.asBytes(&key)[0..byte_size]});
101+
@call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key)[0..byte_size] });
98102
}
99103
},
100104

lib/std/hash/wyhash.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const WyhashStateless = struct {
6565

6666
var off: usize = 0;
6767
while (off < b.len) : (off += 32) {
68-
@call(.always_inline, self.round, .{b[off..][0..32]});
68+
@call(.always_inline, round, .{ self, b[off..][0..32] });
6969
}
7070

7171
self.msg_len += b.len;
@@ -121,8 +121,8 @@ const WyhashStateless = struct {
121121
const aligned_len = input.len - (input.len % 32);
122122

123123
var c = WyhashStateless.init(seed);
124-
@call(.always_inline, c.update, .{input[0..aligned_len]});
125-
return @call(.always_inline, c.final, .{input[aligned_len..]});
124+
@call(.always_inline, update, .{ &c, input[0..aligned_len] });
125+
return @call(.always_inline, final, .{ &c, input[aligned_len..] });
126126
}
127127
};
128128

src/AstGen.zig

Lines changed: 80 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2482,7 +2482,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
24822482
switch (zir_tags[inst]) {
24832483
// For some instructions, modify the zir data
24842484
// so we can avoid a separate ensure_result_used instruction.
2485-
.call => {
2485+
.call, .field_call => {
24862486
const extra_index = gz.astgen.instructions.items(.data)[inst].pl_node.payload_index;
24872487
const slot = &gz.astgen.extra.items[extra_index];
24882488
var flags = @bitCast(Zir.Inst.Call.Flags, slot.*);
@@ -2557,7 +2557,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
25572557
.field_ptr,
25582558
.field_ptr_init,
25592559
.field_val,
2560-
.field_call_bind,
25612560
.field_ptr_named,
25622561
.field_val_named,
25632562
.func,
@@ -8516,7 +8515,7 @@ fn builtinCall(
85168515
},
85178516
.call => {
85188517
const modifier = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .modifier_type } }, params[0]);
8519-
const callee = try calleeExpr(gz, scope, params[1]);
8518+
const callee = try expr(gz, scope, .{ .rl = .none }, params[1]);
85208519
const args = try expr(gz, scope, .{ .rl = .none }, params[2]);
85218520
const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{
85228521
.modifier = modifier,
@@ -8976,7 +8975,10 @@ fn callExpr(
89768975
} });
89778976
}
89788977

8979-
assert(callee != .none);
8978+
switch (callee) {
8979+
.direct => |obj| assert(obj != .none),
8980+
.field => |field| assert(field.obj_ptr != .none),
8981+
}
89808982
assert(node != 0);
89818983

89828984
const call_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
@@ -9015,89 +9017,98 @@ fn callExpr(
90159017
else => false,
90169018
};
90179019

9018-
const payload_index = try addExtra(astgen, Zir.Inst.Call{
9019-
.callee = callee,
9020-
.flags = .{
9021-
.pop_error_return_trace = !propagate_error_trace,
9022-
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
9023-
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
9020+
switch (callee) {
9021+
.direct => |callee_obj| {
9022+
const payload_index = try addExtra(astgen, Zir.Inst.Call{
9023+
.callee = callee_obj,
9024+
.flags = .{
9025+
.pop_error_return_trace = !propagate_error_trace,
9026+
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
9027+
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
9028+
},
9029+
});
9030+
if (call.ast.params.len != 0) {
9031+
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
9032+
}
9033+
gz.astgen.instructions.set(call_index, .{
9034+
.tag = .call,
9035+
.data = .{ .pl_node = .{
9036+
.src_node = gz.nodeIndexToRelative(node),
9037+
.payload_index = payload_index,
9038+
} },
9039+
});
9040+
},
9041+
.field => |callee_field| {
9042+
const payload_index = try addExtra(astgen, Zir.Inst.FieldCall{
9043+
.obj_ptr = callee_field.obj_ptr,
9044+
.field_name_start = callee_field.field_name_start,
9045+
.flags = .{
9046+
.pop_error_return_trace = !propagate_error_trace,
9047+
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
9048+
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
9049+
},
9050+
});
9051+
if (call.ast.params.len != 0) {
9052+
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
9053+
}
9054+
gz.astgen.instructions.set(call_index, .{
9055+
.tag = .field_call,
9056+
.data = .{ .pl_node = .{
9057+
.src_node = gz.nodeIndexToRelative(node),
9058+
.payload_index = payload_index,
9059+
} },
9060+
});
90249061
},
9025-
});
9026-
if (call.ast.params.len != 0) {
9027-
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
90289062
}
9029-
gz.astgen.instructions.set(call_index, .{
9030-
.tag = .call,
9031-
.data = .{ .pl_node = .{
9032-
.src_node = gz.nodeIndexToRelative(node),
9033-
.payload_index = payload_index,
9034-
} },
9035-
});
90369063
return rvalue(gz, ri, call_inst, node); // TODO function call with result location
90379064
}
90389065

9039-
/// calleeExpr generates the function part of a call expression (f in f(x)), or the
9040-
/// callee argument to the @call() builtin. If the lhs is a field access or the
9041-
/// @field() builtin, we need to generate a special field_call_bind instruction
9042-
/// instead of the normal field_val or field_ptr. If this is a inst.func() call,
9043-
/// this instruction will capture the value of the first argument before evaluating
9044-
/// the other arguments. We need to use .ref here to guarantee we will be able to
9045-
/// promote an lvalue to an address if the first parameter requires it. This
9046-
/// unfortunately also means we need to take a reference to any types on the lhs.
9066+
const Callee = union(enum) {
9067+
field: struct {
9068+
/// A *pointer* to the object the field is fetched on, so that we can
9069+
/// promote the lvalue to an address if the first parameter requires it.
9070+
obj_ptr: Zir.Inst.Ref,
9071+
/// Offset into `string_bytes`.
9072+
field_name_start: u32,
9073+
},
9074+
direct: Zir.Inst.Ref,
9075+
};
9076+
9077+
/// calleeExpr generates the function part of a call expression (f in f(x)), but
9078+
/// *not* the callee argument to the @call() builtin. Its purpose is to
9079+
/// distinguish between standard calls and method call syntax `a.b()`. Thus, if
9080+
/// the lhs is a field access, we return using the `field` union field;
9081+
/// otherwise, we use the `direct` union field.
90479082
fn calleeExpr(
90489083
gz: *GenZir,
90499084
scope: *Scope,
90509085
node: Ast.Node.Index,
9051-
) InnerError!Zir.Inst.Ref {
9086+
) InnerError!Callee {
90529087
const astgen = gz.astgen;
90539088
const tree = astgen.tree;
90549089

90559090
const tag = tree.nodes.items(.tag)[node];
90569091
switch (tag) {
9057-
.field_access => return addFieldAccess(.field_call_bind, gz, scope, .{ .rl = .ref }, node),
9058-
9059-
.builtin_call_two,
9060-
.builtin_call_two_comma,
9061-
.builtin_call,
9062-
.builtin_call_comma,
9063-
=> {
9064-
const node_datas = tree.nodes.items(.data);
9092+
.field_access => {
90659093
const main_tokens = tree.nodes.items(.main_token);
9066-
const builtin_token = main_tokens[node];
9067-
const builtin_name = tree.tokenSlice(builtin_token);
9068-
9069-
var inline_params: [2]Ast.Node.Index = undefined;
9070-
var params: []Ast.Node.Index = switch (tag) {
9071-
.builtin_call,
9072-
.builtin_call_comma,
9073-
=> tree.extra_data[node_datas[node].lhs..node_datas[node].rhs],
9074-
9075-
.builtin_call_two,
9076-
.builtin_call_two_comma,
9077-
=> blk: {
9078-
inline_params = .{ node_datas[node].lhs, node_datas[node].rhs };
9079-
const len: usize = if (inline_params[0] == 0) @as(usize, 0) else if (inline_params[1] == 0) @as(usize, 1) else @as(usize, 2);
9080-
break :blk inline_params[0..len];
9081-
},
9082-
9083-
else => unreachable,
9084-
};
9094+
const node_datas = tree.nodes.items(.data);
9095+
const object_node = node_datas[node].lhs;
9096+
const dot_token = main_tokens[node];
9097+
const field_ident = dot_token + 1;
9098+
const str_index = try astgen.identAsString(field_ident);
9099+
// Capture the object by reference so we can promote it to an
9100+
// address in Sema if needed.
9101+
const lhs = try expr(gz, scope, .{ .rl = .ref }, object_node);
90859102

9086-
// If anything is wrong, fall back to builtinCall.
9087-
// It will emit any necessary compile errors and notes.
9088-
if (std.mem.eql(u8, builtin_name, "@field") and params.len == 2) {
9089-
const lhs = try expr(gz, scope, .{ .rl = .ref }, params[0]);
9090-
const field_name = try comptimeExpr(gz, scope, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[1]);
9091-
return gz.addExtendedPayload(.field_call_bind_named, Zir.Inst.FieldNamedNode{
9092-
.node = gz.nodeIndexToRelative(node),
9093-
.lhs = lhs,
9094-
.field_name = field_name,
9095-
});
9096-
}
9103+
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
9104+
try emitDbgStmt(gz, cursor);
90979105

9098-
return builtinCall(gz, scope, .{ .rl = .none }, node, params);
9106+
return .{ .field = .{
9107+
.obj_ptr = lhs,
9108+
.field_name_start = str_index,
9109+
} };
90999110
},
9100-
else => return expr(gz, scope, .{ .rl = .none }, node),
9111+
else => return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) },
91019112
}
91029113
}
91039114

src/Autodoc.zig

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,7 @@ fn walkInstruction(
21412141
.expr = .{ .declRef = decl_status },
21422142
};
21432143
},
2144-
.field_val, .field_call_bind, .field_ptr, .field_type => {
2144+
.field_val, .field_ptr, .field_type => {
21452145
// TODO: field type uses Zir.Inst.FieldType, it just happens to have the
21462146
// same layout as Zir.Inst.Field :^)
21472147
const pl_node = data[inst_index].pl_node;
@@ -2163,7 +2163,6 @@ fn walkInstruction(
21632163

21642164
const lhs = @enumToInt(lhs_extra.data.lhs) - Ref.typed_value_map.len;
21652165
if (tags[lhs] != .field_val and
2166-
tags[lhs] != .field_call_bind and
21672166
tags[lhs] != .field_ptr and
21682167
tags[lhs] != .field_type) break :blk lhs_extra.data.lhs;
21692168

@@ -2191,7 +2190,7 @@ fn walkInstruction(
21912190
const wr = blk: {
21922191
if (@enumToInt(lhs_ref) >= Ref.typed_value_map.len) {
21932192
const lhs_inst = @enumToInt(lhs_ref) - Ref.typed_value_map.len;
2194-
if (tags[lhs_inst] == .call) {
2193+
if (tags[lhs_inst] == .call or tags[lhs_inst] == .field_call) {
21952194
break :blk DocData.WalkResult{
21962195
.expr = .{
21972196
.comptimeExpr = 0,

src/Module.zig

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2489,8 +2489,21 @@ pub const SrcLoc = struct {
24892489
const node_datas = tree.nodes.items(.data);
24902490
const node_tags = tree.nodes.items(.tag);
24912491
const node = src_loc.declRelativeToNodeIndex(node_off);
2492+
var buf: [1]Ast.Node.Index = undefined;
24922493
const tok_index = switch (node_tags[node]) {
24932494
.field_access => node_datas[node].rhs,
2495+
.call_one,
2496+
.call_one_comma,
2497+
.async_call_one,
2498+
.async_call_one_comma,
2499+
.call,
2500+
.call_comma,
2501+
.async_call,
2502+
.async_call_comma,
2503+
=> blk: {
2504+
const full = tree.fullCall(&buf, node).?;
2505+
break :blk tree.lastToken(full.ast.fn_expr);
2506+
},
24942507
else => tree.firstToken(node) - 2,
24952508
};
24962509
const start = tree.tokens.items(.start)[tok_index];
@@ -3083,7 +3096,8 @@ pub const LazySrcLoc = union(enum) {
30833096
/// The payload is offset from the containing Decl AST node.
30843097
/// The source location points to the field name of:
30853098
/// * a field access expression (`a.b`), or
3086-
/// * the operand ("b" node) of a field initialization expression (`.a = b`)
3099+
/// * the callee of a method call (`a.b()`), or
3100+
/// * the operand ("b" node) of a field initialization expression (`.a = b`), or
30873101
/// The Decl is determined contextually.
30883102
node_offset_field_name: i32,
30893103
/// The source location points to the pointer of a pointer deref expression,

0 commit comments

Comments
 (0)