Access a ZIR instruction through closure. May tunnel through arbitrarily
many namespaces, adding closure captures as required.
Returns the index of the closure_get instruction added to gz.
fn tunnelThroughClosure(
gz: *GenZir,
/// The node which references the value to be captured.
inner_ref_node: Ast.Node.Index,
/// The number of namespaces being tunnelled through. At least 1.
num_tunnels: u32,
/// The value being captured.
value: union(enum)
fn tunnelThroughClosure(
gz: *GenZir,
/// The node which references the value to be captured.
inner_ref_node: Ast.Node.Index,
/// The number of namespaces being tunnelled through. At least 1.
num_tunnels: u32,
/// The value being captured.
value: union(enum) {
ref: Zir.Inst.Ref,
ref_load: Zir.Inst.Ref,
decl_val: Zir.NullTerminatedString,
decl_ref: Zir.NullTerminatedString,
},
/// The location of the value's declaration.
decl_src: union(enum) {
token: Ast.TokenIndex,
node: Ast.Node.Index,
},
name_str_index: Zir.NullTerminatedString,
) !Zir.Inst.Ref {
switch (value) {
.ref => |v| if (v.toIndex() == null) return v, // trivial value; do not need tunnel
.ref_load => |v| assert(v.toIndex() != null), // there are no constant pointer refs
.decl_val, .decl_ref => {},
}
const astgen = gz.astgen;
const gpa = astgen.gpa;
// Otherwise we need a tunnel. First, figure out the path of namespaces we
// are tunneling through. This is usually only going to be one or two, so
// use an BFA to optimize for the common case.
var bfa_buf: [2]usize = undefined;
var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), astgen.arena);
var intermediate_tunnels = try bfa.allocator().alloc(*Scope.Namespace, num_tunnels - 1);
const root_ns = ns: {
var i: usize = num_tunnels - 1;
var scope: *Scope = gz.parent;
while (i > 0) {
if (scope.cast(Scope.Namespace)) |mid_ns| {
i -= 1;
intermediate_tunnels[i] = mid_ns;
}
scope = scope.parent().?;
}
while (true) {
if (scope.cast(Scope.Namespace)) |ns| break :ns ns;
scope = scope.parent().?;
}
};
// Now that we know the scopes we're tunneling through, begin adding
// captures as required, starting with the outermost namespace.
const root_capture: Zir.Inst.Capture = .wrap(switch (value) {
.ref => |v| .{ .instruction = v.toIndex().? },
.ref_load => |v| .{ .instruction_load = v.toIndex().? },
.decl_val => |str| .{ .decl_val = str },
.decl_ref => |str| .{ .decl_ref = str },
});
const root_gop = try root_ns.captures.getOrPut(gpa, root_capture);
root_gop.value_ptr.* = name_str_index;
var cur_capture_index = std.math.cast(u16, root_gop.index) orelse return astgen.failNodeNotes(
root_ns.node,
"this compiler implementation only supports up to 65536 captures per namespace",
.{},
&.{
switch (decl_src) {
.token => |t| try astgen.errNoteTok(t, "captured value here", .{}),
.node => |n| try astgen.errNoteNode(n, "captured value here", .{}),
},
try astgen.errNoteNode(inner_ref_node, "value used here", .{}),
},
);
for (intermediate_tunnels) |tunnel_ns| {
const tunnel_gop = try tunnel_ns.captures.getOrPut(gpa, .wrap(.{ .nested = cur_capture_index }));
tunnel_gop.value_ptr.* = name_str_index;
cur_capture_index = std.math.cast(u16, tunnel_gop.index) orelse return astgen.failNodeNotes(
tunnel_ns.node,
"this compiler implementation only supports up to 65536 captures per namespace",
.{},
&.{
switch (decl_src) {
.token => |t| try astgen.errNoteTok(t, "captured value here", .{}),
.node => |n| try astgen.errNoteNode(n, "captured value here", .{}),
},
try astgen.errNoteNode(inner_ref_node, "value used here", .{}),
},
);
}
// Incorporate the capture index into the source hash, so that changes in
// the order of captures cause suitable re-analysis.
astgen.src_hasher.update(std.mem.asBytes(&cur_capture_index));
// Add an instruction to get the value from the closure.
return gz.addExtendedNodeSmall(.closure_get, inner_ref_node, cur_capture_index);
}