mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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% |
+--------------+----------+----------+--------+
This commit is contained in:
parent
7077e90b3f
commit
38b83d9d93
@ -169,7 +169,7 @@ const FutexImpl = struct {
|
||||
}
|
||||
}
|
||||
|
||||
inline fn lockFast(self: *@This(), comptime casFn: []const u8) bool {
|
||||
inline fn lockFast(self: *@This(), comptime cas_fn_name: []const u8) bool {
|
||||
// On x86, use `lock bts` instead of `lock cmpxchg` as:
|
||||
// - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
|
||||
// - `lock bts` is smaller instruction-wise which makes it better for inlining
|
||||
@ -180,7 +180,8 @@ const FutexImpl = struct {
|
||||
|
||||
// Acquire barrier ensures grabbing the lock happens before the critical section
|
||||
// and that the previous lock holder's critical section happens before we grab the lock.
|
||||
return @field(self.state, casFn)(unlocked, locked, .Acquire, .Monotonic) == null;
|
||||
const casFn = @field(@TypeOf(self.state), cas_fn_name);
|
||||
return casFn(&self.state, unlocked, locked, .Acquire, .Monotonic) == null;
|
||||
}
|
||||
|
||||
fn lockSlow(self: *@This()) void {
|
||||
|
||||
@ -167,8 +167,8 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
|
||||
pub fn hash(msg: []const u8, key: *const [key_length]u8) T {
|
||||
const aligned_len = msg.len - (msg.len % 8);
|
||||
var c = Self.init(key);
|
||||
@call(.always_inline, c.update, .{msg[0..aligned_len]});
|
||||
return @call(.always_inline, c.final, .{msg[aligned_len..]});
|
||||
@call(.always_inline, update, .{ &c, msg[0..aligned_len] });
|
||||
return @call(.always_inline, final, .{ &c, msg[aligned_len..] });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -64,9 +64,13 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
|
||||
/// Strategy is provided to determine if pointers should be followed or not.
|
||||
pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
|
||||
const Key = @TypeOf(key);
|
||||
const Hasher = switch (@typeInfo(@TypeOf(hasher))) {
|
||||
.Pointer => |ptr| ptr.child,
|
||||
else => @TypeOf(hasher),
|
||||
};
|
||||
|
||||
if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) {
|
||||
@call(.always_inline, hasher.update, .{mem.asBytes(&key)});
|
||||
@call(.always_inline, Hasher.update, .{ hasher, mem.asBytes(&key) });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -89,12 +93,12 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
|
||||
// TODO Check if the situation is better after #561 is resolved.
|
||||
.Int => {
|
||||
if (comptime meta.trait.hasUniqueRepresentation(Key)) {
|
||||
@call(.always_inline, hasher.update, .{std.mem.asBytes(&key)});
|
||||
@call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key) });
|
||||
} else {
|
||||
// Take only the part containing the key value, the remaining
|
||||
// bytes are undefined and must not be hashed!
|
||||
const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable;
|
||||
@call(.always_inline, hasher.update, .{std.mem.asBytes(&key)[0..byte_size]});
|
||||
@call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key)[0..byte_size] });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ const WyhashStateless = struct {
|
||||
|
||||
var off: usize = 0;
|
||||
while (off < b.len) : (off += 32) {
|
||||
@call(.always_inline, self.round, .{b[off..][0..32]});
|
||||
@call(.always_inline, round, .{ self, b[off..][0..32] });
|
||||
}
|
||||
|
||||
self.msg_len += b.len;
|
||||
@ -121,8 +121,8 @@ const WyhashStateless = struct {
|
||||
const aligned_len = input.len - (input.len % 32);
|
||||
|
||||
var c = WyhashStateless.init(seed);
|
||||
@call(.always_inline, c.update, .{input[0..aligned_len]});
|
||||
return @call(.always_inline, c.final, .{input[aligned_len..]});
|
||||
@call(.always_inline, update, .{ &c, input[0..aligned_len] });
|
||||
return @call(.always_inline, final, .{ &c, input[aligned_len..] });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
149
src/AstGen.zig
149
src/AstGen.zig
@ -2482,7 +2482,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
|
||||
switch (zir_tags[inst]) {
|
||||
// For some instructions, modify the zir data
|
||||
// so we can avoid a separate ensure_result_used instruction.
|
||||
.call => {
|
||||
.call, .field_call => {
|
||||
const extra_index = gz.astgen.instructions.items(.data)[inst].pl_node.payload_index;
|
||||
const slot = &gz.astgen.extra.items[extra_index];
|
||||
var flags = @bitCast(Zir.Inst.Call.Flags, slot.*);
|
||||
@ -2557,7 +2557,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
|
||||
.field_ptr,
|
||||
.field_ptr_init,
|
||||
.field_val,
|
||||
.field_call_bind,
|
||||
.field_ptr_named,
|
||||
.field_val_named,
|
||||
.func,
|
||||
@ -8516,7 +8515,7 @@ fn builtinCall(
|
||||
},
|
||||
.call => {
|
||||
const modifier = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .modifier_type } }, params[0]);
|
||||
const callee = try calleeExpr(gz, scope, params[1]);
|
||||
const callee = try expr(gz, scope, .{ .rl = .none }, params[1]);
|
||||
const args = try expr(gz, scope, .{ .rl = .none }, params[2]);
|
||||
const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{
|
||||
.modifier = modifier,
|
||||
@ -8976,7 +8975,10 @@ fn callExpr(
|
||||
} });
|
||||
}
|
||||
|
||||
assert(callee != .none);
|
||||
switch (callee) {
|
||||
.direct => |obj| assert(obj != .none),
|
||||
.field => |field| assert(field.obj_ptr != .none),
|
||||
}
|
||||
assert(node != 0);
|
||||
|
||||
const call_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
|
||||
@ -9015,89 +9017,98 @@ fn callExpr(
|
||||
else => false,
|
||||
};
|
||||
|
||||
const payload_index = try addExtra(astgen, Zir.Inst.Call{
|
||||
.callee = callee,
|
||||
.flags = .{
|
||||
.pop_error_return_trace = !propagate_error_trace,
|
||||
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
|
||||
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
|
||||
switch (callee) {
|
||||
.direct => |callee_obj| {
|
||||
const payload_index = try addExtra(astgen, Zir.Inst.Call{
|
||||
.callee = callee_obj,
|
||||
.flags = .{
|
||||
.pop_error_return_trace = !propagate_error_trace,
|
||||
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
|
||||
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
|
||||
},
|
||||
});
|
||||
if (call.ast.params.len != 0) {
|
||||
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
|
||||
}
|
||||
gz.astgen.instructions.set(call_index, .{
|
||||
.tag = .call,
|
||||
.data = .{ .pl_node = .{
|
||||
.src_node = gz.nodeIndexToRelative(node),
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
},
|
||||
.field => |callee_field| {
|
||||
const payload_index = try addExtra(astgen, Zir.Inst.FieldCall{
|
||||
.obj_ptr = callee_field.obj_ptr,
|
||||
.field_name_start = callee_field.field_name_start,
|
||||
.flags = .{
|
||||
.pop_error_return_trace = !propagate_error_trace,
|
||||
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
|
||||
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
|
||||
},
|
||||
});
|
||||
if (call.ast.params.len != 0) {
|
||||
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
|
||||
}
|
||||
gz.astgen.instructions.set(call_index, .{
|
||||
.tag = .field_call,
|
||||
.data = .{ .pl_node = .{
|
||||
.src_node = gz.nodeIndexToRelative(node),
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
},
|
||||
});
|
||||
if (call.ast.params.len != 0) {
|
||||
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
|
||||
}
|
||||
gz.astgen.instructions.set(call_index, .{
|
||||
.tag = .call,
|
||||
.data = .{ .pl_node = .{
|
||||
.src_node = gz.nodeIndexToRelative(node),
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
return rvalue(gz, ri, call_inst, node); // TODO function call with result location
|
||||
}
|
||||
|
||||
/// calleeExpr generates the function part of a call expression (f in f(x)), or the
|
||||
/// callee argument to the @call() builtin. If the lhs is a field access or the
|
||||
/// @field() builtin, we need to generate a special field_call_bind instruction
|
||||
/// instead of the normal field_val or field_ptr. If this is a inst.func() call,
|
||||
/// this instruction will capture the value of the first argument before evaluating
|
||||
/// the other arguments. We need to use .ref here to guarantee we will be able to
|
||||
/// promote an lvalue to an address if the first parameter requires it. This
|
||||
/// unfortunately also means we need to take a reference to any types on the lhs.
|
||||
const Callee = union(enum) {
|
||||
field: struct {
|
||||
/// A *pointer* to the object the field is fetched on, so that we can
|
||||
/// promote the lvalue to an address if the first parameter requires it.
|
||||
obj_ptr: Zir.Inst.Ref,
|
||||
/// Offset into `string_bytes`.
|
||||
field_name_start: u32,
|
||||
},
|
||||
direct: Zir.Inst.Ref,
|
||||
};
|
||||
|
||||
/// calleeExpr generates the function part of a call expression (f in f(x)), but
|
||||
/// *not* the callee argument to the @call() builtin. Its purpose is to
|
||||
/// distinguish between standard calls and method call syntax `a.b()`. Thus, if
|
||||
/// the lhs is a field access, we return using the `field` union field;
|
||||
/// otherwise, we use the `direct` union field.
|
||||
fn calleeExpr(
|
||||
gz: *GenZir,
|
||||
scope: *Scope,
|
||||
node: Ast.Node.Index,
|
||||
) InnerError!Zir.Inst.Ref {
|
||||
) InnerError!Callee {
|
||||
const astgen = gz.astgen;
|
||||
const tree = astgen.tree;
|
||||
|
||||
const tag = tree.nodes.items(.tag)[node];
|
||||
switch (tag) {
|
||||
.field_access => return addFieldAccess(.field_call_bind, gz, scope, .{ .rl = .ref }, node),
|
||||
|
||||
.builtin_call_two,
|
||||
.builtin_call_two_comma,
|
||||
.builtin_call,
|
||||
.builtin_call_comma,
|
||||
=> {
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
.field_access => {
|
||||
const main_tokens = tree.nodes.items(.main_token);
|
||||
const builtin_token = main_tokens[node];
|
||||
const builtin_name = tree.tokenSlice(builtin_token);
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
const object_node = node_datas[node].lhs;
|
||||
const dot_token = main_tokens[node];
|
||||
const field_ident = dot_token + 1;
|
||||
const str_index = try astgen.identAsString(field_ident);
|
||||
// Capture the object by reference so we can promote it to an
|
||||
// address in Sema if needed.
|
||||
const lhs = try expr(gz, scope, .{ .rl = .ref }, object_node);
|
||||
|
||||
var inline_params: [2]Ast.Node.Index = undefined;
|
||||
var params: []Ast.Node.Index = switch (tag) {
|
||||
.builtin_call,
|
||||
.builtin_call_comma,
|
||||
=> tree.extra_data[node_datas[node].lhs..node_datas[node].rhs],
|
||||
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
|
||||
try emitDbgStmt(gz, cursor);
|
||||
|
||||
.builtin_call_two,
|
||||
.builtin_call_two_comma,
|
||||
=> blk: {
|
||||
inline_params = .{ node_datas[node].lhs, node_datas[node].rhs };
|
||||
const len: usize = if (inline_params[0] == 0) @as(usize, 0) else if (inline_params[1] == 0) @as(usize, 1) else @as(usize, 2);
|
||||
break :blk inline_params[0..len];
|
||||
},
|
||||
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// If anything is wrong, fall back to builtinCall.
|
||||
// It will emit any necessary compile errors and notes.
|
||||
if (std.mem.eql(u8, builtin_name, "@field") and params.len == 2) {
|
||||
const lhs = try expr(gz, scope, .{ .rl = .ref }, params[0]);
|
||||
const field_name = try comptimeExpr(gz, scope, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[1]);
|
||||
return gz.addExtendedPayload(.field_call_bind_named, Zir.Inst.FieldNamedNode{
|
||||
.node = gz.nodeIndexToRelative(node),
|
||||
.lhs = lhs,
|
||||
.field_name = field_name,
|
||||
});
|
||||
}
|
||||
|
||||
return builtinCall(gz, scope, .{ .rl = .none }, node, params);
|
||||
return .{ .field = .{
|
||||
.obj_ptr = lhs,
|
||||
.field_name_start = str_index,
|
||||
} };
|
||||
},
|
||||
else => return expr(gz, scope, .{ .rl = .none }, node),
|
||||
else => return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2141,7 +2141,7 @@ fn walkInstruction(
|
||||
.expr = .{ .declRef = decl_status },
|
||||
};
|
||||
},
|
||||
.field_val, .field_call_bind, .field_ptr, .field_type => {
|
||||
.field_val, .field_ptr, .field_type => {
|
||||
// TODO: field type uses Zir.Inst.FieldType, it just happens to have the
|
||||
// same layout as Zir.Inst.Field :^)
|
||||
const pl_node = data[inst_index].pl_node;
|
||||
@ -2163,7 +2163,6 @@ fn walkInstruction(
|
||||
|
||||
const lhs = @enumToInt(lhs_extra.data.lhs) - Ref.typed_value_map.len;
|
||||
if (tags[lhs] != .field_val and
|
||||
tags[lhs] != .field_call_bind and
|
||||
tags[lhs] != .field_ptr and
|
||||
tags[lhs] != .field_type) break :blk lhs_extra.data.lhs;
|
||||
|
||||
@ -2191,7 +2190,7 @@ fn walkInstruction(
|
||||
const wr = blk: {
|
||||
if (@enumToInt(lhs_ref) >= Ref.typed_value_map.len) {
|
||||
const lhs_inst = @enumToInt(lhs_ref) - Ref.typed_value_map.len;
|
||||
if (tags[lhs_inst] == .call) {
|
||||
if (tags[lhs_inst] == .call or tags[lhs_inst] == .field_call) {
|
||||
break :blk DocData.WalkResult{
|
||||
.expr = .{
|
||||
.comptimeExpr = 0,
|
||||
|
||||
@ -2489,8 +2489,21 @@ pub const SrcLoc = struct {
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const node = src_loc.declRelativeToNodeIndex(node_off);
|
||||
var buf: [1]Ast.Node.Index = undefined;
|
||||
const tok_index = switch (node_tags[node]) {
|
||||
.field_access => node_datas[node].rhs,
|
||||
.call_one,
|
||||
.call_one_comma,
|
||||
.async_call_one,
|
||||
.async_call_one_comma,
|
||||
.call,
|
||||
.call_comma,
|
||||
.async_call,
|
||||
.async_call_comma,
|
||||
=> blk: {
|
||||
const full = tree.fullCall(&buf, node).?;
|
||||
break :blk tree.lastToken(full.ast.fn_expr);
|
||||
},
|
||||
else => tree.firstToken(node) - 2,
|
||||
};
|
||||
const start = tree.tokens.items(.start)[tok_index];
|
||||
@ -3083,7 +3096,8 @@ pub const LazySrcLoc = union(enum) {
|
||||
/// The payload is offset from the containing Decl AST node.
|
||||
/// The source location points to the field name of:
|
||||
/// * a field access expression (`a.b`), or
|
||||
/// * the operand ("b" node) of a field initialization expression (`.a = b`)
|
||||
/// * the callee of a method call (`a.b()`), or
|
||||
/// * the operand ("b" node) of a field initialization expression (`.a = b`), or
|
||||
/// The Decl is determined contextually.
|
||||
node_offset_field_name: i32,
|
||||
/// The source location points to the pointer of a pointer deref expression,
|
||||
|
||||
176
src/Sema.zig
176
src/Sema.zig
@ -920,7 +920,8 @@ fn analyzeBodyInner(
|
||||
.bool_br_and => try sema.zirBoolBr(block, inst, false),
|
||||
.bool_br_or => try sema.zirBoolBr(block, inst, true),
|
||||
.c_import => try sema.zirCImport(block, inst),
|
||||
.call => try sema.zirCall(block, inst),
|
||||
.call => try sema.zirCall(block, inst, .direct),
|
||||
.field_call => try sema.zirCall(block, inst, .field),
|
||||
.closure_get => try sema.zirClosureGet(block, inst),
|
||||
.cmp_lt => try sema.zirCmp(block, inst, .lt),
|
||||
.cmp_lte => try sema.zirCmp(block, inst, .lte),
|
||||
@ -952,7 +953,6 @@ fn analyzeBodyInner(
|
||||
.field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
|
||||
.field_val => try sema.zirFieldVal(block, inst),
|
||||
.field_val_named => try sema.zirFieldValNamed(block, inst),
|
||||
.field_call_bind => try sema.zirFieldCallBind(block, inst),
|
||||
.func => try sema.zirFunc(block, inst, false),
|
||||
.func_inferred => try sema.zirFunc(block, inst, true),
|
||||
.func_fancy => try sema.zirFuncFancy(block, inst),
|
||||
@ -1149,7 +1149,6 @@ fn analyzeBodyInner(
|
||||
.wasm_memory_size => try sema.zirWasmMemorySize( block, extended),
|
||||
.wasm_memory_grow => try sema.zirWasmMemoryGrow( block, extended),
|
||||
.prefetch => try sema.zirPrefetch( block, extended),
|
||||
.field_call_bind_named => try sema.zirFieldCallBindNamed(block, extended),
|
||||
.err_set_cast => try sema.zirErrSetCast( block, extended),
|
||||
.await_nosuspend => try sema.zirAwaitNosuspend( block, extended),
|
||||
.select => try sema.zirSelect( block, extended),
|
||||
@ -6262,38 +6261,50 @@ fn zirCall(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
inst: Zir.Inst.Index,
|
||||
comptime kind: enum { direct, field },
|
||||
) CompileError!Air.Inst.Ref {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
|
||||
const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
|
||||
const callee_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
|
||||
const call_src = inst_data.src();
|
||||
const extra = sema.code.extraData(Zir.Inst.Call, inst_data.payload_index);
|
||||
const ExtraType = switch (kind) {
|
||||
.direct => Zir.Inst.Call,
|
||||
.field => Zir.Inst.FieldCall,
|
||||
};
|
||||
const extra = sema.code.extraData(ExtraType, inst_data.payload_index);
|
||||
const args_len = extra.data.flags.args_len;
|
||||
|
||||
const modifier = @intToEnum(std.builtin.CallModifier, extra.data.flags.packed_modifier);
|
||||
const ensure_result_used = extra.data.flags.ensure_result_used;
|
||||
const pop_error_return_trace = extra.data.flags.pop_error_return_trace;
|
||||
|
||||
var func = try sema.resolveInst(extra.data.callee);
|
||||
const callee: ResolvedFieldCallee = switch (kind) {
|
||||
.direct => .{ .direct = try sema.resolveInst(extra.data.callee) },
|
||||
.field => blk: {
|
||||
const object_ptr = try sema.resolveInst(extra.data.obj_ptr);
|
||||
const field_name = sema.code.nullTerminatedString(extra.data.field_name_start);
|
||||
const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
|
||||
break :blk try sema.fieldCallBind(block, callee_src, object_ptr, field_name, field_name_src);
|
||||
},
|
||||
};
|
||||
var resolved_args: []Air.Inst.Ref = undefined;
|
||||
var arg_index: u32 = 0;
|
||||
|
||||
const func_type = sema.typeOf(func);
|
||||
|
||||
// Desugar bound functions here
|
||||
var bound_arg_src: ?LazySrcLoc = null;
|
||||
if (func_type.tag() == .bound_fn) {
|
||||
bound_arg_src = func_src;
|
||||
const bound_func = try sema.resolveValue(block, .unneeded, func, "");
|
||||
const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data;
|
||||
func = bound_data.func_inst;
|
||||
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len + 1);
|
||||
resolved_args[arg_index] = bound_data.arg0_inst;
|
||||
arg_index += 1;
|
||||
} else {
|
||||
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len);
|
||||
var func: Air.Inst.Ref = undefined;
|
||||
var arg_index: u32 = 0;
|
||||
switch (callee) {
|
||||
.direct => |func_inst| {
|
||||
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len);
|
||||
func = func_inst;
|
||||
},
|
||||
.method => |method| {
|
||||
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len + 1);
|
||||
func = method.func_inst;
|
||||
resolved_args[0] = method.arg0_inst;
|
||||
arg_index += 1;
|
||||
bound_arg_src = callee_src;
|
||||
},
|
||||
}
|
||||
|
||||
const callee_ty = sema.typeOf(func);
|
||||
@ -6308,10 +6319,11 @@ fn zirCall(
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return sema.fail(block, func_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)});
|
||||
return sema.fail(block, callee_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)});
|
||||
};
|
||||
|
||||
const total_args = args_len + @boolToInt(bound_arg_src != null);
|
||||
try sema.checkCallArgumentCount(block, func, func_src, func_ty, total_args, bound_arg_src != null);
|
||||
try sema.checkCallArgumentCount(block, func, callee_src, func_ty, total_args, bound_arg_src != null);
|
||||
|
||||
const args_body = sema.code.extra[extra.end..];
|
||||
|
||||
@ -6369,7 +6381,7 @@ fn zirCall(
|
||||
!block.is_comptime and !block.is_typeof and (input_is_error or pop_error_return_trace))
|
||||
{
|
||||
const call_inst: Air.Inst.Ref = if (modifier == .always_tail) undefined else b: {
|
||||
break :b try sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
|
||||
break :b try sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
|
||||
};
|
||||
|
||||
const return_ty = sema.typeOf(call_inst);
|
||||
@ -6398,11 +6410,11 @@ fn zirCall(
|
||||
}
|
||||
|
||||
if (modifier == .always_tail) // Perform the call *after* the restore, so that a tail call is possible.
|
||||
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
|
||||
return sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
|
||||
|
||||
return call_inst;
|
||||
} else {
|
||||
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
|
||||
return sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9467,19 +9479,6 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index, initializing: b
|
||||
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, initializing);
|
||||
}
|
||||
|
||||
fn zirFieldCallBind(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
|
||||
const src = inst_data.src();
|
||||
const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
|
||||
const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
|
||||
const field_name = sema.code.nullTerminatedString(extra.field_name_start);
|
||||
const object_ptr = try sema.resolveInst(extra.lhs);
|
||||
return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src);
|
||||
}
|
||||
|
||||
fn zirFieldValNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -9506,18 +9505,6 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
|
||||
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
|
||||
}
|
||||
|
||||
fn zirFieldCallBindNamed(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const extra = sema.code.extraData(Zir.Inst.FieldNamedNode, extended.operand).data;
|
||||
const src = LazySrcLoc.nodeOffset(extra.node);
|
||||
const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
|
||||
const object_ptr = try sema.resolveInst(extra.lhs);
|
||||
const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime-known");
|
||||
return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src);
|
||||
}
|
||||
|
||||
fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -21673,25 +21660,9 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
|
||||
return sema.fail(block, args_src, "expected a tuple, found '{}'", .{args_ty.fmt(sema.mod)});
|
||||
}
|
||||
|
||||
var resolved_args: []Air.Inst.Ref = undefined;
|
||||
|
||||
// Desugar bound functions here
|
||||
var bound_arg_src: ?LazySrcLoc = null;
|
||||
if (sema.typeOf(func).tag() == .bound_fn) {
|
||||
bound_arg_src = func_src;
|
||||
const bound_func = try sema.resolveValue(block, .unneeded, func, "");
|
||||
const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data;
|
||||
func = bound_data.func_inst;
|
||||
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount() + 1);
|
||||
resolved_args[0] = bound_data.arg0_inst;
|
||||
for (resolved_args[1..], 0..) |*resolved, i| {
|
||||
resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
|
||||
}
|
||||
} else {
|
||||
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount());
|
||||
for (resolved_args, 0..) |*resolved, i| {
|
||||
resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
|
||||
}
|
||||
var resolved_args: []Air.Inst.Ref = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount());
|
||||
for (resolved_args, 0..) |*resolved, i| {
|
||||
resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
|
||||
}
|
||||
|
||||
const callee_ty = sema.typeOf(func);
|
||||
@ -21708,10 +21679,10 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
|
||||
}
|
||||
return sema.fail(block, func_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)});
|
||||
};
|
||||
try sema.checkCallArgumentCount(block, func, func_src, func_ty, resolved_args.len, bound_arg_src != null);
|
||||
try sema.checkCallArgumentCount(block, func, func_src, func_ty, resolved_args.len, false);
|
||||
|
||||
const ensure_result_used = extra.flags.ensure_result_used;
|
||||
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, null);
|
||||
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, null, null);
|
||||
}
|
||||
|
||||
fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||
@ -24175,6 +24146,16 @@ fn fieldPtr(
|
||||
return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name);
|
||||
}
|
||||
|
||||
const ResolvedFieldCallee = union(enum) {
|
||||
/// The LHS of the call was an actual field with this value.
|
||||
direct: Air.Inst.Ref,
|
||||
/// This is a method call, with the function and first argument given.
|
||||
method: struct {
|
||||
func_inst: Air.Inst.Ref,
|
||||
arg0_inst: Air.Inst.Ref,
|
||||
},
|
||||
};
|
||||
|
||||
fn fieldCallBind(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
@ -24182,7 +24163,7 @@ fn fieldCallBind(
|
||||
raw_ptr: Air.Inst.Ref,
|
||||
field_name: []const u8,
|
||||
field_name_src: LazySrcLoc,
|
||||
) CompileError!Air.Inst.Ref {
|
||||
) CompileError!ResolvedFieldCallee {
|
||||
// When editing this function, note that there is corresponding logic to be edited
|
||||
// in `fieldVal`. This function takes a pointer and returns a pointer.
|
||||
|
||||
@ -24202,7 +24183,6 @@ fn fieldCallBind(
|
||||
else
|
||||
raw_ptr;
|
||||
|
||||
const arena = sema.arena;
|
||||
find_field: {
|
||||
switch (concrete_ty.zigTypeTag()) {
|
||||
.Struct => {
|
||||
@ -24216,7 +24196,7 @@ fn fieldCallBind(
|
||||
return sema.finishFieldCallBind(block, src, ptr_ty, field.ty, field_index, object_ptr);
|
||||
} else if (struct_ty.isTuple()) {
|
||||
if (mem.eql(u8, field_name, "len")) {
|
||||
return sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount());
|
||||
return .{ .direct = try sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount()) };
|
||||
}
|
||||
if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| {
|
||||
if (field_index >= struct_ty.structFieldCount()) break :find_field;
|
||||
@ -24243,7 +24223,7 @@ fn fieldCallBind(
|
||||
},
|
||||
.Type => {
|
||||
const namespace = try sema.analyzeLoad(block, src, object_ptr, src);
|
||||
return sema.fieldVal(block, src, namespace, field_name, field_name_src);
|
||||
return .{ .direct = try sema.fieldVal(block, src, namespace, field_name, field_name_src) };
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@ -24272,54 +24252,47 @@ fn fieldCallBind(
|
||||
first_param_type.childType().eql(concrete_ty, sema.mod)))
|
||||
{
|
||||
// zig fmt: on
|
||||
// Note that if the param type is generic poison, we know that it must
|
||||
// specifically be `anytype` since it's the first parameter, meaning we
|
||||
// can safely assume it can be a pointer.
|
||||
// TODO: bound fn calls on rvalues should probably
|
||||
// generate a by-value argument somehow.
|
||||
const ty = Type.Tag.bound_fn.init();
|
||||
const value = try Value.Tag.bound_fn.create(arena, .{
|
||||
return .{ .method = .{
|
||||
.func_inst = decl_val,
|
||||
.arg0_inst = object_ptr,
|
||||
});
|
||||
return sema.addConstant(ty, value);
|
||||
} };
|
||||
} else if (first_param_type.eql(concrete_ty, sema.mod)) {
|
||||
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
|
||||
const ty = Type.Tag.bound_fn.init();
|
||||
const value = try Value.Tag.bound_fn.create(arena, .{
|
||||
return .{ .method = .{
|
||||
.func_inst = decl_val,
|
||||
.arg0_inst = deref,
|
||||
});
|
||||
return sema.addConstant(ty, value);
|
||||
} };
|
||||
} else if (first_param_type.zigTypeTag() == .Optional) {
|
||||
var opt_buf: Type.Payload.ElemType = undefined;
|
||||
const child = first_param_type.optionalChild(&opt_buf);
|
||||
if (child.eql(concrete_ty, sema.mod)) {
|
||||
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
|
||||
const ty = Type.Tag.bound_fn.init();
|
||||
const value = try Value.Tag.bound_fn.create(arena, .{
|
||||
return .{ .method = .{
|
||||
.func_inst = decl_val,
|
||||
.arg0_inst = deref,
|
||||
});
|
||||
return sema.addConstant(ty, value);
|
||||
} };
|
||||
} else if (child.zigTypeTag() == .Pointer and
|
||||
child.ptrSize() == .One and
|
||||
child.childType().eql(concrete_ty, sema.mod))
|
||||
{
|
||||
const ty = Type.Tag.bound_fn.init();
|
||||
const value = try Value.Tag.bound_fn.create(arena, .{
|
||||
return .{ .method = .{
|
||||
.func_inst = decl_val,
|
||||
.arg0_inst = object_ptr,
|
||||
});
|
||||
return sema.addConstant(ty, value);
|
||||
} };
|
||||
}
|
||||
} else if (first_param_type.zigTypeTag() == .ErrorUnion and
|
||||
first_param_type.errorUnionPayload().eql(concrete_ty, sema.mod))
|
||||
{
|
||||
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
|
||||
const ty = Type.Tag.bound_fn.init();
|
||||
const value = try Value.Tag.bound_fn.create(arena, .{
|
||||
return .{ .method = .{
|
||||
.func_inst = decl_val,
|
||||
.arg0_inst = deref,
|
||||
});
|
||||
return sema.addConstant(ty, value);
|
||||
} };
|
||||
}
|
||||
}
|
||||
break :found_decl decl_idx;
|
||||
@ -24351,7 +24324,7 @@ fn finishFieldCallBind(
|
||||
field_ty: Type,
|
||||
field_index: u32,
|
||||
object_ptr: Air.Inst.Ref,
|
||||
) CompileError!Air.Inst.Ref {
|
||||
) CompileError!ResolvedFieldCallee {
|
||||
const arena = sema.arena;
|
||||
const ptr_field_ty = try Type.ptr(arena, sema.mod, .{
|
||||
.pointee_type = field_ty,
|
||||
@ -24362,7 +24335,7 @@ fn finishFieldCallBind(
|
||||
const container_ty = ptr_ty.childType();
|
||||
if (container_ty.zigTypeTag() == .Struct) {
|
||||
if (container_ty.structFieldValueComptime(field_index)) |default_val| {
|
||||
return sema.addConstant(field_ty, default_val);
|
||||
return .{ .direct = try sema.addConstant(field_ty, default_val) };
|
||||
}
|
||||
}
|
||||
|
||||
@ -24375,12 +24348,12 @@ fn finishFieldCallBind(
|
||||
.field_index = field_index,
|
||||
}),
|
||||
);
|
||||
return sema.analyzeLoad(block, src, pointer, src);
|
||||
return .{ .direct = try sema.analyzeLoad(block, src, pointer, src) };
|
||||
}
|
||||
|
||||
try sema.requireRuntimeBlock(block, src, null);
|
||||
const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty);
|
||||
return sema.analyzeLoad(block, src, ptr_inst, src);
|
||||
return .{ .direct = try sema.analyzeLoad(block, src, ptr_inst, src) };
|
||||
}
|
||||
|
||||
fn namespaceLookup(
|
||||
@ -31281,7 +31254,6 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
|
||||
|
||||
.inferred_alloc_mut => unreachable,
|
||||
.inferred_alloc_const => unreachable,
|
||||
.bound_fn => unreachable,
|
||||
|
||||
.array,
|
||||
.array_sentinel,
|
||||
@ -32666,7 +32638,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
|
||||
.single_const_pointer,
|
||||
.single_mut_pointer,
|
||||
.pointer,
|
||||
.bound_fn,
|
||||
=> return null,
|
||||
|
||||
.optional => {
|
||||
@ -33308,7 +33279,6 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
|
||||
|
||||
.inferred_alloc_mut => unreachable,
|
||||
.inferred_alloc_const => unreachable,
|
||||
.bound_fn => unreachable,
|
||||
|
||||
.array,
|
||||
.array_sentinel,
|
||||
|
||||
@ -499,10 +499,6 @@ pub fn print(
|
||||
// TODO these should not appear in this function
|
||||
.inferred_alloc => return writer.writeAll("(inferred allocation value)"),
|
||||
.inferred_alloc_comptime => return writer.writeAll("(inferred comptime allocation value)"),
|
||||
.bound_fn => {
|
||||
const bound_func = val.castTag(.bound_fn).?.data;
|
||||
return writer.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst });
|
||||
},
|
||||
.generic_poison_type => return writer.writeAll("(generic poison type)"),
|
||||
.generic_poison => return writer.writeAll("(generic poison)"),
|
||||
.runtime_value => return writer.writeAll("[runtime value]"),
|
||||
|
||||
52
src/Zir.zig
52
src/Zir.zig
@ -297,6 +297,14 @@ pub const Inst = struct {
|
||||
/// Uses the `pl_node` union field with payload `Call`.
|
||||
/// AST node is the function call.
|
||||
call,
|
||||
/// Function call using `a.b()` syntax.
|
||||
/// Uses the named field as the callee. If there is no such field, searches in the type for
|
||||
/// a decl matching the field name. The decl is resolved and we ensure that it's a function
|
||||
/// which can accept the object as the first parameter, with one pointer fixup. This
|
||||
/// function is then used as the callee, with the object as an implicit first parameter.
|
||||
/// Uses the `pl_node` union field with payload `FieldCall`.
|
||||
/// AST node is the function call.
|
||||
field_call,
|
||||
/// Implements the `@call` builtin.
|
||||
/// Uses the `pl_node` union field with payload `BuiltinCall`.
|
||||
/// AST node is the builtin call.
|
||||
@ -432,15 +440,6 @@ pub const Inst = struct {
|
||||
/// This instruction also accepts a pointer.
|
||||
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
|
||||
field_val,
|
||||
/// Given a pointer to a struct or object that contains virtual fields, returns the
|
||||
/// named field. If there is no named field, searches in the type for a decl that
|
||||
/// matches the field name. The decl is resolved and we ensure that it's a function
|
||||
/// which can accept the object as the first parameter, with one pointer fixup. If
|
||||
/// all of that works, this instruction produces a special "bound function" value
|
||||
/// which contains both the function and the saved first parameter value.
|
||||
/// Bound functions may only be used as the function parameter to a `call` or
|
||||
/// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler.
|
||||
field_call_bind,
|
||||
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
|
||||
/// to the named field. The field name is a comptime instruction. Used by @field.
|
||||
/// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed.
|
||||
@ -1051,6 +1050,7 @@ pub const Inst = struct {
|
||||
.bool_br_or,
|
||||
.bool_not,
|
||||
.call,
|
||||
.field_call,
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_eq,
|
||||
@ -1083,7 +1083,6 @@ pub const Inst = struct {
|
||||
.field_ptr,
|
||||
.field_ptr_init,
|
||||
.field_val,
|
||||
.field_call_bind,
|
||||
.field_ptr_named,
|
||||
.field_val_named,
|
||||
.func,
|
||||
@ -1361,6 +1360,7 @@ pub const Inst = struct {
|
||||
.bool_br_or,
|
||||
.bool_not,
|
||||
.call,
|
||||
.field_call,
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_eq,
|
||||
@ -1383,7 +1383,6 @@ pub const Inst = struct {
|
||||
.field_ptr,
|
||||
.field_ptr_init,
|
||||
.field_val,
|
||||
.field_call_bind,
|
||||
.field_ptr_named,
|
||||
.field_val_named,
|
||||
.func,
|
||||
@ -1601,6 +1600,7 @@ pub const Inst = struct {
|
||||
.check_comptime_control_flow = .un_node,
|
||||
.for_len = .pl_node,
|
||||
.call = .pl_node,
|
||||
.field_call = .pl_node,
|
||||
.cmp_lt = .pl_node,
|
||||
.cmp_lte = .pl_node,
|
||||
.cmp_eq = .pl_node,
|
||||
@ -1641,7 +1641,6 @@ pub const Inst = struct {
|
||||
.field_val = .pl_node,
|
||||
.field_ptr_named = .pl_node,
|
||||
.field_val_named = .pl_node,
|
||||
.field_call_bind = .pl_node,
|
||||
.func = .pl_node,
|
||||
.func_inferred = .pl_node,
|
||||
.func_fancy = .pl_node,
|
||||
@ -1955,16 +1954,6 @@ pub const Inst = struct {
|
||||
/// The `@prefetch` builtin.
|
||||
/// `operand` is payload index to `BinNode`.
|
||||
prefetch,
|
||||
/// Given a pointer to a struct or object that contains virtual fields, returns the
|
||||
/// named field. If there is no named field, searches in the type for a decl that
|
||||
/// matches the field name. The decl is resolved and we ensure that it's a function
|
||||
/// which can accept the object as the first parameter, with one pointer fixup. If
|
||||
/// all of that works, this instruction produces a special "bound function" value
|
||||
/// which contains both the function and the saved first parameter value.
|
||||
/// Bound functions may only be used as the function parameter to a `call` or
|
||||
/// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler.
|
||||
/// Uses `pl_node` field. The AST node is the `@field` builtin. Payload is FieldNamedNode.
|
||||
field_call_bind_named,
|
||||
/// Implements the `@fence` builtin.
|
||||
/// `operand` is payload index to `UnNode`.
|
||||
fence,
|
||||
@ -2913,6 +2902,19 @@ pub const Inst = struct {
|
||||
};
|
||||
};
|
||||
|
||||
/// Stored inside extra, with trailing arguments according to `args_len`.
|
||||
/// Implicit 0. arg_0_start: u32, // always same as `args_len`
|
||||
/// 1. arg_end: u32, // for each `args_len`
|
||||
/// arg_N_start is the same as arg_N-1_end
|
||||
pub const FieldCall = struct {
|
||||
// Note: Flags *must* come first so that unusedResultExpr
|
||||
// can find it when it goes to modify them.
|
||||
flags: Call.Flags,
|
||||
obj_ptr: Ref,
|
||||
/// Offset into `string_bytes`.
|
||||
field_name_start: u32,
|
||||
};
|
||||
|
||||
pub const TypeOfPeer = struct {
|
||||
src_node: i32,
|
||||
body_len: u32,
|
||||
@ -3187,12 +3189,6 @@ pub const Inst = struct {
|
||||
field_name: Ref,
|
||||
};
|
||||
|
||||
pub const FieldNamedNode = struct {
|
||||
node: i32,
|
||||
lhs: Ref,
|
||||
field_name: Ref,
|
||||
};
|
||||
|
||||
pub const As = struct {
|
||||
dest_type: Ref,
|
||||
operand: Ref,
|
||||
|
||||
@ -369,7 +369,6 @@ const Writer = struct {
|
||||
.inferred_alloc_const => try s.writeAll("(inferred_alloc_const)"),
|
||||
.inferred_alloc_mut => try s.writeAll("(inferred_alloc_mut)"),
|
||||
.generic_poison => try s.writeAll("(generic_poison)"),
|
||||
.bound_fn => try s.writeAll("(bound_fn)"),
|
||||
else => try ty.print(s, w.module),
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,7 +362,8 @@ const Writer = struct {
|
||||
.@"export" => try self.writePlNodeExport(stream, inst),
|
||||
.export_value => try self.writePlNodeExportValue(stream, inst),
|
||||
|
||||
.call => try self.writeCall(stream, inst),
|
||||
.call => try self.writeCall(stream, inst, .direct),
|
||||
.field_call => try self.writeCall(stream, inst, .field),
|
||||
|
||||
.block,
|
||||
.block_comptime,
|
||||
@ -392,7 +393,6 @@ const Writer = struct {
|
||||
.field_ptr,
|
||||
.field_ptr_init,
|
||||
.field_val,
|
||||
.field_call_bind,
|
||||
=> try self.writePlNodeField(stream, inst),
|
||||
|
||||
.field_ptr_named,
|
||||
@ -543,15 +543,6 @@ const Writer = struct {
|
||||
try self.writeSrc(stream, src);
|
||||
},
|
||||
|
||||
.field_call_bind_named => {
|
||||
const extra = self.code.extraData(Zir.Inst.FieldNamedNode, extended.operand).data;
|
||||
const src = LazySrcLoc.nodeOffset(extra.node);
|
||||
try self.writeInstRef(stream, extra.lhs);
|
||||
try stream.writeAll(", ");
|
||||
try self.writeInstRef(stream, extra.field_name);
|
||||
try stream.writeAll(") ");
|
||||
try self.writeSrc(stream, src);
|
||||
},
|
||||
.builtin_async_call => try self.writeBuiltinAsyncCall(stream, extended),
|
||||
.cmpxchg => try self.writeCmpxchg(stream, extended),
|
||||
}
|
||||
@ -1176,9 +1167,18 @@ const Writer = struct {
|
||||
try self.writeSrc(stream, src);
|
||||
}
|
||||
|
||||
fn writeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||
fn writeCall(
|
||||
self: *Writer,
|
||||
stream: anytype,
|
||||
inst: Zir.Inst.Index,
|
||||
comptime kind: enum { direct, field },
|
||||
) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
|
||||
const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
|
||||
const ExtraType = switch (kind) {
|
||||
.direct => Zir.Inst.Call,
|
||||
.field => Zir.Inst.FieldCall,
|
||||
};
|
||||
const extra = self.code.extraData(ExtraType, inst_data.payload_index);
|
||||
const args_len = extra.data.flags.args_len;
|
||||
const body = self.code.extra[extra.end..];
|
||||
|
||||
@ -1186,7 +1186,14 @@ const Writer = struct {
|
||||
try stream.writeAll("nodiscard ");
|
||||
}
|
||||
try stream.print(".{s}, ", .{@tagName(@intToEnum(std.builtin.CallModifier, extra.data.flags.packed_modifier))});
|
||||
try self.writeInstRef(stream, extra.data.callee);
|
||||
switch (kind) {
|
||||
.direct => try self.writeInstRef(stream, extra.data.callee),
|
||||
.field => {
|
||||
const field_name = self.code.nullTerminatedString(extra.data.field_name_start);
|
||||
try self.writeInstRef(stream, extra.data.obj_ptr);
|
||||
try stream.print(", {}", .{std.zig.fmtId(field_name)});
|
||||
},
|
||||
}
|
||||
try stream.writeAll(", [");
|
||||
|
||||
self.indent += 2;
|
||||
|
||||
18
src/type.zig
18
src/type.zig
@ -156,8 +156,6 @@ pub const Type = extern union {
|
||||
.union_tagged,
|
||||
.type_info,
|
||||
=> return .Union,
|
||||
|
||||
.bound_fn => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -933,7 +931,6 @@ pub const Type = extern union {
|
||||
// for example, a was resolved into .union_tagged but b was one of these tags.
|
||||
.type_info => unreachable, // needed to resolve the type before now
|
||||
|
||||
.bound_fn => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1242,7 +1239,6 @@ pub const Type = extern union {
|
||||
// we can't hash these based on tags because they wouldn't match the expanded version.
|
||||
.type_info => unreachable, // needed to resolve the type before now
|
||||
|
||||
.bound_fn => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1349,7 +1345,6 @@ pub const Type = extern union {
|
||||
.type_info,
|
||||
.@"anyframe",
|
||||
.generic_poison,
|
||||
.bound_fn,
|
||||
=> unreachable,
|
||||
|
||||
.array_u8,
|
||||
@ -1613,7 +1608,6 @@ pub const Type = extern union {
|
||||
.comptime_int,
|
||||
.comptime_float,
|
||||
.noreturn,
|
||||
.bound_fn,
|
||||
=> return writer.writeAll(@tagName(t)),
|
||||
|
||||
.enum_literal => return writer.writeAll("@Type(.EnumLiteral)"),
|
||||
@ -1949,7 +1943,6 @@ pub const Type = extern union {
|
||||
.inferred_alloc_const => unreachable,
|
||||
.inferred_alloc_mut => unreachable,
|
||||
.generic_poison => unreachable,
|
||||
.bound_fn => unreachable,
|
||||
|
||||
// TODO get rid of these Type.Tag values.
|
||||
.atomic_order => unreachable,
|
||||
@ -2468,7 +2461,6 @@ pub const Type = extern union {
|
||||
.enum_literal,
|
||||
.empty_struct,
|
||||
.empty_struct_literal,
|
||||
.bound_fn,
|
||||
// These are function *bodies*, not pointers.
|
||||
// Special exceptions have to be made when emitting functions due to
|
||||
// this returning false.
|
||||
@ -2703,7 +2695,6 @@ pub const Type = extern union {
|
||||
|
||||
.inferred_alloc_mut => unreachable,
|
||||
.inferred_alloc_const => unreachable,
|
||||
.bound_fn => unreachable,
|
||||
|
||||
.array,
|
||||
.array_sentinel,
|
||||
@ -3182,7 +3173,6 @@ pub const Type = extern union {
|
||||
.noreturn,
|
||||
.inferred_alloc_const,
|
||||
.inferred_alloc_mut,
|
||||
.bound_fn,
|
||||
=> unreachable,
|
||||
|
||||
.generic_poison => unreachable,
|
||||
@ -3282,7 +3272,6 @@ pub const Type = extern union {
|
||||
.fn_ccc_void_no_args => unreachable, // represents machine code; not a pointer
|
||||
.function => unreachable, // represents machine code; not a pointer
|
||||
.@"opaque" => unreachable, // no size available
|
||||
.bound_fn => unreachable,
|
||||
.noreturn => unreachable,
|
||||
.inferred_alloc_const => unreachable,
|
||||
.inferred_alloc_mut => unreachable,
|
||||
@ -3630,7 +3619,6 @@ pub const Type = extern union {
|
||||
.inferred_alloc_mut => unreachable,
|
||||
.@"opaque" => unreachable,
|
||||
.generic_poison => unreachable,
|
||||
.bound_fn => unreachable,
|
||||
|
||||
.void => return 0,
|
||||
.bool, .u1 => return 1,
|
||||
@ -5042,7 +5030,6 @@ pub const Type = extern union {
|
||||
.single_const_pointer,
|
||||
.single_mut_pointer,
|
||||
.pointer,
|
||||
.bound_fn,
|
||||
=> return null,
|
||||
|
||||
.optional => {
|
||||
@ -5245,7 +5232,6 @@ pub const Type = extern union {
|
||||
|
||||
.inferred_alloc_mut => unreachable,
|
||||
.inferred_alloc_const => unreachable,
|
||||
.bound_fn => unreachable,
|
||||
|
||||
.array,
|
||||
.array_sentinel,
|
||||
@ -6081,7 +6067,6 @@ pub const Type = extern union {
|
||||
inferred_alloc_mut,
|
||||
/// Same as `inferred_alloc_mut` but the local is `var` not `const`.
|
||||
inferred_alloc_const, // See last_no_payload_tag below.
|
||||
bound_fn,
|
||||
// After this, the tag requires a payload.
|
||||
|
||||
array_u8,
|
||||
@ -6126,7 +6111,7 @@ pub const Type = extern union {
|
||||
enum_full,
|
||||
enum_nonexhaustive,
|
||||
|
||||
pub const last_no_payload_tag = Tag.bound_fn;
|
||||
pub const last_no_payload_tag = Tag.inferred_alloc_const;
|
||||
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
|
||||
|
||||
pub fn Type(comptime t: Tag) type {
|
||||
@ -6199,7 +6184,6 @@ pub const Type = extern union {
|
||||
.extern_options,
|
||||
.type_info,
|
||||
.@"anyframe",
|
||||
.bound_fn,
|
||||
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
|
||||
|
||||
.array_u8,
|
||||
|
||||
@ -183,10 +183,6 @@ pub const Value = extern union {
|
||||
/// Used to coordinate alloc_inferred, store_to_inferred_ptr, and resolve_inferred_alloc
|
||||
/// instructions for comptime code.
|
||||
inferred_alloc_comptime,
|
||||
/// Used sometimes as the result of field_call_bind. This value is always temporary,
|
||||
/// and refers directly to the air. It will never be referenced by the air itself.
|
||||
/// TODO: This is probably a bad encoding, maybe put temp data in the sema instead.
|
||||
bound_fn,
|
||||
/// The ABI alignment of the payload type.
|
||||
lazy_align,
|
||||
/// The ABI size of the payload type.
|
||||
@ -326,7 +322,6 @@ pub const Value = extern union {
|
||||
.inferred_alloc_comptime => Payload.InferredAllocComptime,
|
||||
.aggregate => Payload.Aggregate,
|
||||
.@"union" => Payload.Union,
|
||||
.bound_fn => Payload.BoundFn,
|
||||
.comptime_field_ptr => Payload.ComptimeFieldPtr,
|
||||
};
|
||||
}
|
||||
@ -477,7 +472,6 @@ pub const Value = extern union {
|
||||
.extern_options_type,
|
||||
.type_info_type,
|
||||
.generic_poison,
|
||||
.bound_fn,
|
||||
=> unreachable,
|
||||
|
||||
.ty, .lazy_align, .lazy_size => {
|
||||
@ -837,10 +831,6 @@ pub const Value = extern union {
|
||||
try out_stream.writeAll("(opt_payload_ptr)");
|
||||
val = val.castTag(.opt_payload_ptr).?.data.container_ptr;
|
||||
},
|
||||
.bound_fn => {
|
||||
const bound_func = val.castTag(.bound_fn).?.data;
|
||||
return out_stream.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -5657,16 +5647,6 @@ pub const Value = extern union {
|
||||
val: Value,
|
||||
},
|
||||
};
|
||||
|
||||
pub const BoundFn = struct {
|
||||
pub const base_tag = Tag.bound_fn;
|
||||
|
||||
base: Payload = Payload{ .tag = base_tag },
|
||||
data: struct {
|
||||
func_inst: Air.Inst.Ref,
|
||||
arg0_inst: Air.Inst.Ref,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/// Big enough to fit any non-BigInt value
|
||||
|
||||
@ -86,18 +86,6 @@ test "@field field calls" {
|
||||
const pv = &v;
|
||||
const pcv: *const HasFuncs = pv;
|
||||
|
||||
try expect(@field(v, "get")() == 0);
|
||||
@field(v, "inc")();
|
||||
try expect(v.state == 1);
|
||||
try expect(@field(v, "get")() == 1);
|
||||
|
||||
@field(pv, "inc")();
|
||||
try expect(v.state == 2);
|
||||
try expect(@field(pv, "get")() == 2);
|
||||
try expect(@field(v, "getPtr")().* == 2);
|
||||
try expect(@field(pcv, "get")() == 2);
|
||||
try expect(@field(pcv, "getPtr")().* == 2);
|
||||
|
||||
v.func_field = HasFuncs.one;
|
||||
try expect(@field(v, "func_field")(0) == 1);
|
||||
try expect(@field(pv, "func_field")(0) == 1);
|
||||
|
||||
@ -6,10 +6,6 @@ pub export fn entry() void {
|
||||
var s: S = undefined;
|
||||
s.foo(true);
|
||||
}
|
||||
pub export fn entry2() void {
|
||||
var s: S = undefined;
|
||||
@call(.auto, s.foo, .{true});
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
@ -17,5 +13,3 @@ pub export fn entry2() void {
|
||||
//
|
||||
// :7:6: error: member function expected 2 argument(s), found 1
|
||||
// :3:5: note: function declared here
|
||||
// :11:19: error: member function expected 2 argument(s), found 1
|
||||
// :3:5: note: function declared here
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user