Commit a0a847f2e4
Changed files (7)
test
behavior
src/AstGen.zig
@@ -124,7 +124,7 @@ pub fn generate(gpa: *Allocator, tree: Ast) Allocator.Error!Zir {
container_decl,
.Auto,
)) |struct_decl_ref| {
- astgen.extra.items[@enumToInt(Zir.ExtraIndex.main_struct)] = @enumToInt(struct_decl_ref);
+ assert(refToIndex(struct_decl_ref).? == 0);
} else |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {}, // Handled via compile_errors below.
@@ -2078,9 +2078,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.union_init_ptr,
.field_type,
.field_type_ref,
- .opaque_decl,
- .opaque_decl_anon,
- .opaque_decl_func,
.error_set_decl,
.error_set_decl_anon,
.error_set_decl_func,
@@ -2162,6 +2159,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.await_nosuspend,
.ret_err_value_code,
.extended,
+ .closure_get,
=> break :b false,
// ZIR instructions that are always `noreturn`.
@@ -2205,6 +2203,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.set_cold,
.set_float_mode,
.set_runtime_safety,
+ .closure_capture,
=> break :b true,
}
} else switch (maybe_unused_result) {
@@ -3534,8 +3533,9 @@ fn structDeclInner(
container_decl: Ast.full.ContainerDecl,
layout: std.builtin.TypeInfo.ContainerLayout,
) InnerError!Zir.Inst.Ref {
+ const decl_inst = try gz.reserveInstructionIndex();
+
if (container_decl.ast.members.len == 0) {
- const decl_inst = try gz.reserveInstructionIndex();
try gz.setStruct(decl_inst, .{
.src_node = node,
.layout = layout,
@@ -3553,11 +3553,19 @@ fn structDeclInner(
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
+ var namespace: Scope.Namespace = .{
+ .parent = scope,
+ .node = node,
+ .inst = decl_inst,
+ .declaring_gz = gz,
+ };
+ defer namespace.deinit(gpa);
+
// The struct_decl instruction introduces a scope in which the decls of the struct
// are in scope, so that field types, alignments, and default value expressions
// can refer to decls within the struct itself.
var block_scope: GenZir = .{
- .parent = scope,
+ .parent = &namespace.base,
.decl_node_index = node,
.decl_line = gz.calcLine(node),
.astgen = astgen,
@@ -3566,9 +3574,6 @@ fn structDeclInner(
};
defer block_scope.instructions.deinit(gpa);
- var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
- defer namespace.decls.deinit(gpa);
-
try astgen.scanDecls(&namespace, container_decl.ast.members);
var wip_decls: WipDecls = .{};
@@ -3773,7 +3778,6 @@ fn structDeclInner(
}
}
- const decl_inst = try gz.reserveInstructionIndex();
if (block_scope.instructions.items.len != 0) {
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
}
@@ -3787,11 +3791,18 @@ fn structDeclInner(
.known_has_bits = known_has_bits,
});
- try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
- @boolToInt(field_index != 0) + fields_data.items.len +
+ // zig fmt: off
+ try astgen.extra.ensureUnusedCapacity(gpa,
+ bit_bag.items.len +
+ @boolToInt(wip_decls.decl_index != 0) +
+ wip_decls.payload.items.len +
block_scope.instructions.items.len +
- wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
- wip_decls.payload.items.len);
+ wip_decls.bit_bag.items.len +
+ @boolToInt(field_index != 0) +
+ fields_data.items.len
+ );
+ // zig fmt: on
+
astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
if (wip_decls.decl_index != 0) {
astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -3818,17 +3829,27 @@ fn unionDeclInner(
arg_node: Ast.Node.Index,
have_auto_enum: bool,
) InnerError!Zir.Inst.Ref {
+ const decl_inst = try gz.reserveInstructionIndex();
+
const astgen = gz.astgen;
const gpa = astgen.gpa;
const tree = astgen.tree;
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
+ var namespace: Scope.Namespace = .{
+ .parent = scope,
+ .node = node,
+ .inst = decl_inst,
+ .declaring_gz = gz,
+ };
+ defer namespace.deinit(gpa);
+
// The union_decl instruction introduces a scope in which the decls of the union
// are in scope, so that field types, alignments, and default value expressions
// can refer to decls within the union itself.
var block_scope: GenZir = .{
- .parent = scope,
+ .parent = &namespace.base,
.decl_node_index = node,
.decl_line = gz.calcLine(node),
.astgen = astgen,
@@ -3837,13 +3858,10 @@ fn unionDeclInner(
};
defer block_scope.instructions.deinit(gpa);
- var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
- defer namespace.decls.deinit(gpa);
-
try astgen.scanDecls(&namespace, members);
const arg_inst: Zir.Inst.Ref = if (arg_node != 0)
- try typeExpr(gz, &namespace.base, arg_node)
+ try typeExpr(&block_scope, &namespace.base, arg_node)
else
.none;
@@ -4056,7 +4074,6 @@ fn unionDeclInner(
}
}
- const decl_inst = try gz.reserveInstructionIndex();
if (block_scope.instructions.items.len != 0) {
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
}
@@ -4071,11 +4088,18 @@ fn unionDeclInner(
.auto_enum_tag = have_auto_enum,
});
- try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
- 1 + fields_data.items.len +
+ // zig fmt: off
+ try astgen.extra.ensureUnusedCapacity(gpa,
+ bit_bag.items.len +
+ @boolToInt(wip_decls.decl_index != 0) +
+ wip_decls.payload.items.len +
block_scope.instructions.items.len +
- wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
- wip_decls.payload.items.len);
+ wip_decls.bit_bag.items.len +
+ 1 + // cur_bit_bag
+ fields_data.items.len
+ );
+ // zig fmt: on
+
astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
if (wip_decls.decl_index != 0) {
astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -4238,10 +4262,20 @@ fn containerDecl(
// how structs are handled above.
const nonexhaustive = counts.nonexhaustive_node != 0;
+ const decl_inst = try gz.reserveInstructionIndex();
+
+ var namespace: Scope.Namespace = .{
+ .parent = scope,
+ .node = node,
+ .inst = decl_inst,
+ .declaring_gz = gz,
+ };
+ defer namespace.deinit(gpa);
+
// The enum_decl instruction introduces a scope in which the decls of the enum
// are in scope, so that tag values can refer to decls within the enum itself.
var block_scope: GenZir = .{
- .parent = scope,
+ .parent = &namespace.base,
.decl_node_index = node,
.decl_line = gz.calcLine(node),
.astgen = astgen,
@@ -4250,13 +4284,10 @@ fn containerDecl(
};
defer block_scope.instructions.deinit(gpa);
- var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
- defer namespace.decls.deinit(gpa);
-
try astgen.scanDecls(&namespace, container_decl.ast.members);
const arg_inst: Zir.Inst.Ref = if (container_decl.ast.arg != 0)
- try comptimeExpr(gz, &namespace.base, .{ .ty = .type_type }, container_decl.ast.arg)
+ try comptimeExpr(&block_scope, &namespace.base, .{ .ty = .type_type }, container_decl.ast.arg)
else
.none;
@@ -4451,7 +4482,6 @@ fn containerDecl(
}
}
- const decl_inst = try gz.reserveInstructionIndex();
if (block_scope.instructions.items.len != 0) {
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
}
@@ -4465,11 +4495,18 @@ fn containerDecl(
.decls_len = @intCast(u32, wip_decls.decl_index),
});
- try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
- 1 + fields_data.items.len +
+ // zig fmt: off
+ try astgen.extra.ensureUnusedCapacity(gpa,
+ bit_bag.items.len +
+ @boolToInt(wip_decls.decl_index != 0) +
+ wip_decls.payload.items.len +
block_scope.instructions.items.len +
- wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
- wip_decls.payload.items.len);
+ wip_decls.bit_bag.items.len +
+ 1 + // cur_bit_bag
+ fields_data.items.len
+ );
+ // zig fmt: on
+
astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
if (wip_decls.decl_index != 0) {
astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -4486,8 +4523,15 @@ fn containerDecl(
.keyword_opaque => {
assert(container_decl.ast.arg == 0);
- var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
- defer namespace.decls.deinit(gpa);
+ const decl_inst = try gz.reserveInstructionIndex();
+
+ var namespace: Scope.Namespace = .{
+ .parent = scope,
+ .node = node,
+ .inst = decl_inst,
+ .declaring_gz = gz,
+ };
+ defer namespace.deinit(gpa);
try astgen.scanDecls(&namespace, container_decl.ast.members);
@@ -4625,21 +4669,20 @@ fn containerDecl(
wip_decls.cur_bit_bag >>= @intCast(u5, empty_slot_count * WipDecls.bits_per_field);
}
}
- const tag: Zir.Inst.Tag = switch (gz.anon_name_strategy) {
- .parent => .opaque_decl,
- .anon => .opaque_decl_anon,
- .func => .opaque_decl_func,
- };
- const decl_inst = try gz.addBlock(tag, node);
- try gz.instructions.append(gpa, decl_inst);
- try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.OpaqueDecl).Struct.fields.len +
- wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
- wip_decls.payload.items.len);
- const zir_datas = astgen.instructions.items(.data);
- zir_datas[decl_inst].pl_node.payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.OpaqueDecl{
+ try gz.setOpaque(decl_inst, .{
+ .src_node = node,
.decls_len = @intCast(u32, wip_decls.decl_index),
});
+
+ // zig fmt: off
+ try astgen.extra.ensureUnusedCapacity(gpa,
+ wip_decls.bit_bag.items.len +
+ @boolToInt(wip_decls.decl_index != 0) +
+ wip_decls.payload.items.len
+ );
+ // zig fmt: on
+
astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
if (wip_decls.decl_index != 0) {
astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -6380,6 +6423,7 @@ fn identifier(
const astgen = gz.astgen;
const tree = astgen.tree;
+ const gpa = astgen.gpa;
const main_tokens = tree.nodes.items(.main_token);
const ident_token = main_tokens[ident];
@@ -6426,16 +6470,28 @@ fn identifier(
const name_str_index = try astgen.identAsString(ident_token);
var s = scope;
var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
- var hit_namespace: Ast.Node.Index = 0;
+ var num_namespaces_out: u32 = 0;
+ var capturing_namespace: ?*Scope.Namespace = null;
while (true) switch (s.tag) {
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
if (local_val.name == name_str_index) {
- local_val.used = true;
// Locals cannot shadow anything, so we do not need to look for ambiguous
// references in this case.
- return rvalue(gz, rl, local_val.inst, ident);
+ local_val.used = true;
+
+ const value_inst = try tunnelThroughClosure(
+ gz,
+ ident,
+ num_namespaces_out,
+ capturing_namespace,
+ local_val.inst,
+ local_val.token_src,
+ gpa,
+ );
+
+ return rvalue(gz, rl, value_inst, ident);
}
s = local_val.parent;
},
@@ -6443,16 +6499,29 @@ fn identifier(
const local_ptr = s.cast(Scope.LocalPtr).?;
if (local_ptr.name == name_str_index) {
local_ptr.used = true;
- if (hit_namespace != 0 and !local_ptr.maybe_comptime) {
+
+ // Can't close over a runtime variable
+ if (num_namespaces_out != 0 and !local_ptr.maybe_comptime) {
return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{
try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}),
- try astgen.errNoteNode(hit_namespace, "crosses namespace boundary here", .{}),
+ try astgen.errNoteNode(capturing_namespace.?.node, "crosses namespace boundary here", .{}),
});
}
+
+ const ptr_inst = try tunnelThroughClosure(
+ gz,
+ ident,
+ num_namespaces_out,
+ capturing_namespace,
+ local_ptr.ptr,
+ local_ptr.token_src,
+ gpa,
+ );
+
switch (rl) {
- .ref, .none_or_ref => return local_ptr.ptr,
+ .ref, .none_or_ref => return ptr_inst,
else => {
- const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident);
+ const loaded = try gz.addUnNode(.load, ptr_inst, ident);
return rvalue(gz, rl, loaded, ident);
},
}
@@ -6473,7 +6542,8 @@ fn identifier(
// We found a match but must continue looking for ambiguous references to decls.
found_already = i;
}
- hit_namespace = ns.node;
+ num_namespaces_out += 1;
+ capturing_namespace = ns;
s = ns.parent;
},
.top => break,
@@ -6493,6 +6563,37 @@ fn identifier(
}
}
+/// Adds a capture to a namespace, if needed.
+/// Returns the index of the closure_capture instruction.
+fn tunnelThroughClosure(
+ gz: *GenZir,
+ inner_ref_node: Ast.Node.Index,
+ num_tunnels: u32,
+ ns: ?*Scope.Namespace,
+ value: Zir.Inst.Ref,
+ token: Ast.TokenIndex,
+ gpa: *Allocator,
+) !Zir.Inst.Ref {
+ // For trivial values, we don't need a tunnel.
+ // Just return the ref.
+ if (num_tunnels == 0 or refToIndex(value) == null) {
+ return value;
+ }
+
+ // Otherwise we need a tunnel. Check if this namespace
+ // already has one for this value.
+ const gop = try ns.?.captures.getOrPut(gpa, refToIndex(value).?);
+ if (!gop.found_existing) {
+ // Make a new capture for this value
+ const capture_ref = try ns.?.declaring_gz.?.addUnTok(.closure_capture, value, token);
+ gop.value_ptr.* = refToIndex(capture_ref).?;
+ }
+
+ // Add an instruction to get the value from the closure into
+ // our current context
+ return try gz.addInstNode(.closure_get, gop.value_ptr.*, inner_ref_node);
+}
+
fn stringLiteral(
gz: *GenZir,
rl: ResultLoc,
@@ -8961,6 +9062,17 @@ const Scope = struct {
return @fieldParentPtr(T, "base", base);
}
+ fn parent(base: *Scope) ?*Scope {
+ return switch (base.tag) {
+ .gen_zir => base.cast(GenZir).?.parent,
+ .local_val => base.cast(LocalVal).?.parent,
+ .local_ptr => base.cast(LocalPtr).?.parent,
+ .defer_normal, .defer_error => base.cast(Defer).?.parent,
+ .namespace => base.cast(Namespace).?.parent,
+ .top => null,
+ };
+ }
+
const Tag = enum {
gen_zir,
local_val,
@@ -8986,7 +9098,7 @@ const Scope = struct {
const LocalVal = struct {
const base_tag: Tag = .local_val;
base: Scope = Scope{ .tag = base_tag },
- /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`.
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`.
parent: *Scope,
gen_zir: *GenZir,
inst: Zir.Inst.Ref,
@@ -9005,7 +9117,7 @@ const Scope = struct {
const LocalPtr = struct {
const base_tag: Tag = .local_ptr;
base: Scope = Scope{ .tag = base_tag },
- /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`.
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`.
parent: *Scope,
gen_zir: *GenZir,
ptr: Zir.Inst.Ref,
@@ -9023,7 +9135,7 @@ const Scope = struct {
const Defer = struct {
base: Scope,
- /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`.
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`.
parent: *Scope,
defer_node: Ast.Node.Index,
};
@@ -9034,11 +9146,27 @@ const Scope = struct {
const base_tag: Tag = .namespace;
base: Scope = Scope{ .tag = base_tag },
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`.
parent: *Scope,
/// Maps string table index to the source location of declaration,
/// for the purposes of reporting name shadowing compile errors.
decls: std.AutoHashMapUnmanaged(u32, Ast.Node.Index) = .{},
node: Ast.Node.Index,
+ inst: Zir.Inst.Index,
+
+ /// The astgen scope containing this namespace.
+ /// Only valid during astgen.
+ declaring_gz: ?*GenZir,
+
+ /// Map from the raw captured value to the instruction
+ /// ref of the capture for decls in this namespace
+ captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},
+
+ pub fn deinit(self: *Namespace, gpa: *Allocator) void {
+ self.decls.deinit(gpa);
+ self.captures.deinit(gpa);
+ self.* = undefined;
+ }
};
const Top = struct {
@@ -9061,6 +9189,7 @@ const GenZir = struct {
decl_node_index: Ast.Node.Index,
/// The containing decl line index, absolute.
decl_line: u32,
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`.
parent: *Scope,
/// All `GenZir` scopes for the same ZIR share this.
astgen: *AstGen,
@@ -9096,6 +9225,12 @@ const GenZir = struct {
suspend_node: Ast.Node.Index = 0,
nosuspend_node: Ast.Node.Index = 0,
+ /// Namespace members are lazy. When executing a decl within a namespace,
+ /// any references to external instructions need to be treated specially.
+ /// This list tracks those references. See also .closure_capture and .closure_get.
+ /// Keys are the raw instruction index, values are the closure_capture instruction.
+ captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},
+
fn makeSubBlock(gz: *GenZir, scope: *Scope) GenZir {
return .{
.force_comptime = gz.force_comptime,
@@ -9810,6 +9945,22 @@ const GenZir = struct {
});
}
+ fn addInstNode(
+ gz: *GenZir,
+ tag: Zir.Inst.Tag,
+ inst: Zir.Inst.Index,
+ /// Absolute node index. This function does the conversion to offset from Decl.
+ src_node: Ast.Node.Index,
+ ) !Zir.Inst.Ref {
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .inst_node = .{
+ .inst = inst,
+ .src_node = gz.nodeIndexToRelative(src_node),
+ } },
+ });
+ }
+
fn addNodeExtended(
gz: *GenZir,
opcode: Zir.Inst.Extended,
@@ -10111,6 +10262,37 @@ const GenZir = struct {
});
}
+ fn setOpaque(gz: *GenZir, inst: Zir.Inst.Index, args: struct {
+ src_node: Ast.Node.Index,
+ decls_len: u32,
+ }) !void {
+ const astgen = gz.astgen;
+ const gpa = astgen.gpa;
+
+ try astgen.extra.ensureUnusedCapacity(gpa, 2);
+ const payload_index = @intCast(u32, astgen.extra.items.len);
+
+ if (args.src_node != 0) {
+ const node_offset = gz.nodeIndexToRelative(args.src_node);
+ astgen.extra.appendAssumeCapacity(@bitCast(u32, node_offset));
+ }
+ if (args.decls_len != 0) {
+ astgen.extra.appendAssumeCapacity(args.decls_len);
+ }
+ astgen.instructions.set(inst, .{
+ .tag = .extended,
+ .data = .{ .extended = .{
+ .opcode = .opaque_decl,
+ .small = @bitCast(u16, Zir.Inst.OpaqueDecl.Small{
+ .has_src_node = args.src_node != 0,
+ .has_decls_len = args.decls_len != 0,
+ .name_strategy = gz.anon_name_strategy,
+ }),
+ .operand = payload_index,
+ } },
+ });
+ }
+
fn add(gz: *GenZir, inst: Zir.Inst) !Zir.Inst.Ref {
return indexToRef(try gz.addAsIndex(inst));
}
src/Module.zig
@@ -275,6 +275,56 @@ pub const DeclPlusEmitH = struct {
emit_h: EmitH,
};
+pub const CaptureScope = struct {
+ parent: ?*CaptureScope,
+
+ /// Values from this decl's evaluation that will be closed over in
+ /// child decls. Values stored in the value_arena of the linked decl.
+ /// During sema, this map is backed by the gpa. Once sema completes,
+ /// it is reallocated using the value_arena.
+ captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, TypedValue) = .{},
+};
+
+pub const WipCaptureScope = struct {
+ scope: *CaptureScope,
+ finalized: bool,
+ gpa: *Allocator,
+ perm_arena: *Allocator,
+
+ pub fn init(gpa: *Allocator, perm_arena: *Allocator, parent: ?*CaptureScope) !@This() {
+ const scope = try perm_arena.create(CaptureScope);
+ scope.* = .{ .parent = parent };
+ return @This(){
+ .scope = scope,
+ .finalized = false,
+ .gpa = gpa,
+ .perm_arena = perm_arena,
+ };
+ }
+
+ pub fn finalize(noalias self: *@This()) !void {
+ assert(!self.finalized);
+ // use a temp to avoid unintentional aliasing due to RLS
+ const tmp = try self.scope.captures.clone(self.perm_arena);
+ self.scope.captures = tmp;
+ self.finalized = true;
+ }
+
+ pub fn reset(noalias self: *@This(), parent: ?*CaptureScope) !void {
+ if (!self.finalized) try self.finalize();
+ self.scope = try self.perm_arena.create(CaptureScope);
+ self.scope.* = .{ .parent = parent };
+ self.finalized = false;
+ }
+
+ pub fn deinit(noalias self: *@This()) void {
+ if (!self.finalized) {
+ self.scope.captures.deinit(self.gpa);
+ }
+ self.* = undefined;
+ }
+};
+
pub const Decl = struct {
/// Allocated with Module's allocator; outlives the ZIR code.
name: [*:0]const u8,
@@ -290,7 +340,7 @@ pub const Decl = struct {
linksection_val: Value,
/// Populated when `has_tv`.
@"addrspace": std.builtin.AddressSpace,
- /// The memory for ty, val, align_val, linksection_val.
+ /// The memory for ty, val, align_val, linksection_val, and captures.
/// If this is `null` then there is no memory management needed.
value_arena: ?*std.heap.ArenaAllocator.State = null,
/// The direct parent namespace of the Decl.
@@ -299,6 +349,11 @@ pub const Decl = struct {
/// the namespace of the struct, since there is no parent.
namespace: *Scope.Namespace,
+ /// The scope which lexically contains this decl. A decl must depend
+ /// on its lexical parent, in order to ensure that this pointer is valid.
+ /// This scope is allocated out of the arena of the parent decl.
+ src_scope: ?*CaptureScope,
+
/// An integer that can be checked against the corresponding incrementing
/// generation field of Module. This is used to determine whether `complete` status
/// represents pre- or post- re-analysis.
@@ -959,6 +1014,7 @@ pub const Scope = struct {
return @fieldParentPtr(T, "base", base);
}
+ /// Get the decl that is currently being analyzed
pub fn ownerDecl(scope: *Scope) ?*Decl {
return switch (scope.tag) {
.block => scope.cast(Block).?.sema.owner_decl,
@@ -967,6 +1023,7 @@ pub const Scope = struct {
};
}
+ /// Get the decl which contains this decl, for the purposes of source reporting
pub fn srcDecl(scope: *Scope) ?*Decl {
return switch (scope.tag) {
.block => scope.cast(Block).?.src_decl,
@@ -975,6 +1032,15 @@ pub const Scope = struct {
};
}
+ /// Get the scope which contains this decl, for resolving closure_get instructions.
+ pub fn srcScope(scope: *Scope) ?*CaptureScope {
+ return switch (scope.tag) {
+ .block => scope.cast(Block).?.wip_capture_scope,
+ .file => null,
+ .namespace => scope.cast(Namespace).?.getDecl().src_scope,
+ };
+ }
+
/// Asserts the scope has a parent which is a Namespace and returns it.
pub fn namespace(scope: *Scope) *Namespace {
switch (scope.tag) {
@@ -1311,6 +1377,9 @@ pub const Scope = struct {
instructions: ArrayListUnmanaged(Air.Inst.Index),
// `param` instructions are collected here to be used by the `func` instruction.
params: std.ArrayListUnmanaged(Param) = .{},
+
+ wip_capture_scope: *CaptureScope,
+
label: ?*Label = null,
inlining: ?*Inlining,
/// If runtime_index is not 0 then one of these is guaranteed to be non null.
@@ -1372,6 +1441,7 @@ pub const Scope = struct {
.sema = parent.sema,
.src_decl = parent.src_decl,
.instructions = .{},
+ .wip_capture_scope = parent.wip_capture_scope,
.label = null,
.inlining = parent.inlining,
.is_comptime = parent.is_comptime,
@@ -2901,12 +2971,10 @@ pub fn mapOldZirToNew(
var match_stack: std.ArrayListUnmanaged(MatchedZirDecl) = .{};
defer match_stack.deinit(gpa);
- const old_main_struct_inst = old_zir.getMainStruct();
- const new_main_struct_inst = new_zir.getMainStruct();
-
+ // Main struct inst is always the same
try match_stack.append(gpa, .{
- .old_inst = old_main_struct_inst,
- .new_inst = new_main_struct_inst,
+ .old_inst = Zir.main_struct_inst,
+ .new_inst = Zir.main_struct_inst,
});
var old_decls = std.ArrayList(Zir.Inst.Index).init(gpa);
@@ -3064,6 +3132,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
const struct_obj = try new_decl_arena.allocator.create(Module.Struct);
const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj);
const struct_val = try Value.Tag.ty.create(&new_decl_arena.allocator, struct_ty);
+ const ty_ty = comptime Type.initTag(.type);
struct_obj.* = .{
.owner_decl = undefined, // set below
.fields = .{},
@@ -3078,7 +3147,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
.file_scope = file,
},
};
- const new_decl = try mod.allocateNewDecl(&struct_obj.namespace, 0);
+ const new_decl = try mod.allocateNewDecl(&struct_obj.namespace, 0, null);
file.root_decl = new_decl;
struct_obj.owner_decl = new_decl;
new_decl.src_line = 0;
@@ -3087,7 +3156,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
new_decl.is_exported = false;
new_decl.has_align = false;
new_decl.has_linksection_or_addrspace = false;
- new_decl.ty = struct_ty;
+ new_decl.ty = ty_ty;
new_decl.val = struct_val;
new_decl.has_tv = true;
new_decl.owns_tv = true;
@@ -3097,7 +3166,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
if (file.status == .success_zir) {
assert(file.zir_loaded);
- const main_struct_inst = file.zir.getMainStruct();
+ const main_struct_inst = Zir.main_struct_inst;
struct_obj.zir_index = main_struct_inst;
var sema_arena = std.heap.ArenaAllocator.init(gpa);
@@ -3107,6 +3176,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
.mod = mod,
.gpa = gpa,
.arena = &sema_arena.allocator,
+ .perm_arena = &new_decl_arena.allocator,
.code = file.zir,
.owner_decl = new_decl,
.namespace = &struct_obj.namespace,
@@ -3115,10 +3185,15 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
.owner_func = null,
};
defer sema.deinit();
+
+ var wip_captures = try WipCaptureScope.init(gpa, &new_decl_arena.allocator, null);
+ defer wip_captures.deinit();
+
var block_scope: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = new_decl,
+ .wip_capture_scope = wip_captures.scope,
.instructions = .{},
.inlining = null,
.is_comptime = true,
@@ -3126,6 +3201,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
defer block_scope.instructions.deinit(gpa);
if (sema.analyzeStructDecl(new_decl, main_struct_inst, struct_obj)) |_| {
+ try wip_captures.finalize();
new_decl.analysis = .complete;
} else |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
@@ -3155,6 +3231,10 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
decl.analysis = .in_progress;
+ // We need the memory for the Type to go into the arena for the Decl
+ var decl_arena = std.heap.ArenaAllocator.init(gpa);
+ errdefer decl_arena.deinit();
+
var analysis_arena = std.heap.ArenaAllocator.init(gpa);
defer analysis_arena.deinit();
@@ -3162,6 +3242,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
.mod = mod,
.gpa = gpa,
.arena = &analysis_arena.allocator,
+ .perm_arena = &decl_arena.allocator,
.code = zir,
.owner_decl = decl,
.namespace = decl.namespace,
@@ -3173,7 +3254,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
if (decl.isRoot()) {
log.debug("semaDecl root {*} ({s})", .{ decl, decl.name });
- const main_struct_inst = zir.getMainStruct();
+ const main_struct_inst = Zir.main_struct_inst;
const struct_obj = decl.getStruct().?;
// This might not have gotten set in `semaFile` if the first time had
// a ZIR failure, so we set it here in case.
@@ -3185,10 +3266,14 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
}
log.debug("semaDecl {*} ({s})", .{ decl, decl.name });
+ var wip_captures = try WipCaptureScope.init(gpa, &decl_arena.allocator, decl.src_scope);
+ defer wip_captures.deinit();
+
var block_scope: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = decl,
+ .wip_capture_scope = wip_captures.scope,
.instructions = .{},
.inlining = null,
.is_comptime = true,
@@ -3203,6 +3288,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
const extra = zir.extraData(Zir.Inst.Block, inst_data.payload_index);
const body = zir.extra[extra.end..][0..extra.data.body_len];
const break_index = try sema.analyzeBody(&block_scope, body);
+ try wip_captures.finalize();
const result_ref = zir_datas[break_index].@"break".operand;
const src: LazySrcLoc = .{ .node_offset = 0 };
const decl_tv = try sema.resolveInstValue(&block_scope, src, result_ref);
@@ -3239,9 +3325,6 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
// not the struct itself.
try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty);
- // We need the memory for the Type to go into the arena for the Decl
- var decl_arena = std.heap.ArenaAllocator.init(gpa);
- errdefer decl_arena.deinit();
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
if (decl.is_usingnamespace) {
@@ -3638,7 +3721,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
// We create a Decl for it regardless of analysis status.
const gop = try namespace.decls.getOrPut(gpa, decl_name);
if (!gop.found_existing) {
- const new_decl = try mod.allocateNewDecl(namespace, decl_node);
+ const new_decl = try mod.allocateNewDecl(namespace, decl_node, iter.parent_decl.src_scope);
if (is_usingnamespace) {
namespace.usingnamespace_set.putAssumeCapacity(new_decl, is_pub);
}
@@ -3898,10 +3981,15 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se
const gpa = mod.gpa;
+ // Use the Decl's arena for captured values.
+ var decl_arena = decl.value_arena.?.promote(gpa);
+ defer decl.value_arena.?.* = decl_arena.state;
+
var sema: Sema = .{
.mod = mod,
.gpa = gpa,
.arena = arena,
+ .perm_arena = &decl_arena.allocator,
.code = decl.namespace.file_scope.zir,
.owner_decl = decl,
.namespace = decl.namespace,
@@ -3916,10 +4004,14 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se
try sema.air_extra.ensureTotalCapacity(gpa, reserved_count);
sema.air_extra.items.len += reserved_count;
+ var wip_captures = try WipCaptureScope.init(gpa, &decl_arena.allocator, decl.src_scope);
+ defer wip_captures.deinit();
+
var inner_block: Scope.Block = .{
.parent = null,
.sema = &sema,
.src_decl = decl,
+ .wip_capture_scope = wip_captures.scope,
.instructions = .{},
.inlining = null,
.is_comptime = false,
@@ -3995,6 +4087,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se
else => |e| return e,
};
+ try wip_captures.finalize();
+
// Copy the block into place and mark that as the main block.
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len +
inner_block.instructions.items.len);
@@ -4035,7 +4129,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void {
decl.analysis = .outdated;
}
-pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.Node.Index) !*Decl {
+pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.Node.Index, src_scope: ?*CaptureScope) !*Decl {
// If we have emit-h then we must allocate a bigger structure to store the emit-h state.
const new_decl: *Decl = if (mod.emit_h != null) blk: {
const parent_struct = try mod.gpa.create(DeclPlusEmitH);
@@ -4061,6 +4155,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.
.analysis = .unreferenced,
.deletion_flag = false,
.zir_decl_index = 0,
+ .src_scope = src_scope,
.link = switch (mod.comp.bin_file.tag) {
.coff => .{ .coff = link.File.Coff.TextBlock.empty },
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
@@ -4087,6 +4182,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.
.alive = false,
.is_usingnamespace = false,
};
+
return new_decl;
}
@@ -4191,25 +4287,26 @@ pub fn createAnonymousDeclNamed(
typed_value: TypedValue,
name: [:0]u8,
) !*Decl {
- return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, typed_value, name);
+ return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, scope.srcScope(), typed_value, name);
}
pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl {
- return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, typed_value);
+ return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, scope.srcScope(), typed_value);
}
-pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, tv: TypedValue) !*Decl {
+pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, src_scope: ?*CaptureScope, tv: TypedValue) !*Decl {
const name_index = mod.getNextAnonNameIndex();
const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{
owner_decl.name, name_index,
});
- return mod.createAnonymousDeclFromDeclNamed(owner_decl, tv, name);
+ return mod.createAnonymousDeclFromDeclNamed(owner_decl, src_scope, tv, name);
}
/// Takes ownership of `name` even if it returns an error.
pub fn createAnonymousDeclFromDeclNamed(
mod: *Module,
owner_decl: *Decl,
+ src_scope: ?*CaptureScope,
typed_value: TypedValue,
name: [:0]u8,
) !*Decl {
@@ -4218,7 +4315,7 @@ pub fn createAnonymousDeclFromDeclNamed(
const namespace = owner_decl.namespace;
try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1);
- const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node);
+ const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node, src_scope);
new_decl.name = name;
new_decl.src_line = owner_decl.src_line;
@@ -4783,7 +4880,7 @@ pub fn populateTestFunctions(mod: *Module) !void {
const arena = &new_decl_arena.allocator;
const test_fn_vals = try arena.alloc(Value, mod.test_functions.count());
- const array_decl = try mod.createAnonymousDeclFromDecl(decl, .{
+ const array_decl = try mod.createAnonymousDeclFromDecl(decl, null, .{
.ty = try Type.Tag.array.create(arena, .{
.len = test_fn_vals.len,
.elem_type = try tmp_test_fn_ty.copy(arena),
@@ -4796,7 +4893,7 @@ pub fn populateTestFunctions(mod: *Module) !void {
var name_decl_arena = std.heap.ArenaAllocator.init(gpa);
errdefer name_decl_arena.deinit();
const bytes = try name_decl_arena.allocator.dupe(u8, test_name_slice);
- const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, .{
+ const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, null, .{
.ty = try Type.Tag.array_u8.create(&name_decl_arena.allocator, bytes.len),
.val = try Value.Tag.bytes.create(&name_decl_arena.allocator, bytes),
});
src/print_zir.zig
@@ -26,7 +26,7 @@ pub fn renderAsTextToFile(
.parent_decl_node = 0,
};
- const main_struct_inst = scope_file.zir.getMainStruct();
+ const main_struct_inst = Zir.main_struct_inst;
try fs_file.writer().print("%{d} ", .{main_struct_inst});
try writer.writeInstToStream(fs_file.writer(), main_struct_inst);
try fs_file.writeAll("\n");
@@ -171,6 +171,7 @@ const Writer = struct {
.ref,
.ret_coerce,
.ensure_err_payload_void,
+ .closure_capture,
=> try self.writeUnTok(stream, inst),
.bool_br_and,
@@ -307,10 +308,6 @@ const Writer = struct {
.condbr_inline,
=> try self.writePlNodeCondBr(stream, inst),
- .opaque_decl => try self.writeOpaqueDecl(stream, inst, .parent),
- .opaque_decl_anon => try self.writeOpaqueDecl(stream, inst, .anon),
- .opaque_decl_func => try self.writeOpaqueDecl(stream, inst, .func),
-
.error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent),
.error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon),
.error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func),
@@ -371,6 +368,8 @@ const Writer = struct {
.dbg_stmt => try self.writeDbgStmt(stream, inst),
+ .closure_get => try self.writeInstNode(stream, inst),
+
.extended => try self.writeExtended(stream, inst),
}
}
@@ -412,6 +411,7 @@ const Writer = struct {
.struct_decl => try self.writeStructDecl(stream, extended),
.union_decl => try self.writeUnionDecl(stream, extended),
.enum_decl => try self.writeEnumDecl(stream, extended),
+ .opaque_decl => try self.writeOpaqueDecl(stream, extended),
.c_undef, .c_include => {
const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data;
@@ -745,6 +745,17 @@ const Writer = struct {
try self.writeSrc(stream, src);
}
+ fn writeInstNode(
+ self: *Writer,
+ stream: anytype,
+ inst: Zir.Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].inst_node;
+ try self.writeInstIndex(stream, inst_data.inst);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
fn writeAsm(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
const extra = self.code.extraData(Zir.Inst.Asm, extended.operand);
const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
@@ -1365,26 +1376,36 @@ const Writer = struct {
fn writeOpaqueDecl(
self: *Writer,
stream: anytype,
- inst: Zir.Inst.Index,
- name_strategy: Zir.Inst.NameStrategy,
+ extended: Zir.Inst.Extended.InstData,
) !void {
- const inst_data = self.code.instructions.items(.data)[inst].pl_node;
- const extra = self.code.extraData(Zir.Inst.OpaqueDecl, inst_data.payload_index);
- const decls_len = extra.data.decls_len;
+ const small = @bitCast(Zir.Inst.OpaqueDecl.Small, extended.small);
+ var extra_index: usize = extended.operand;
- try stream.print("{s}, ", .{@tagName(name_strategy)});
+ const src_node: ?i32 = if (small.has_src_node) blk: {
+ const src_node = @bitCast(i32, self.code.extra[extra_index]);
+ extra_index += 1;
+ break :blk src_node;
+ } else null;
+
+ const decls_len = if (small.has_decls_len) blk: {
+ const decls_len = self.code.extra[extra_index];
+ extra_index += 1;
+ break :blk decls_len;
+ } else 0;
+
+ try stream.print("{s}, ", .{@tagName(small.name_strategy)});
if (decls_len == 0) {
- try stream.writeAll("}) ");
+ try stream.writeAll("{})");
} else {
- try stream.writeAll("\n");
+ try stream.writeAll("{\n");
self.indent += 2;
- _ = try self.writeDecls(stream, decls_len, extra.end);
+ _ = try self.writeDecls(stream, decls_len, extra_index);
self.indent -= 2;
try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}) ");
+ try stream.writeAll("})");
}
- try self.writeSrc(stream, inst_data.src());
+ try self.writeSrcNode(stream, src_node);
}
fn writeErrorSetDecl(
src/Sema.zig
@@ -8,8 +8,12 @@
mod: *Module,
/// Alias to `mod.gpa`.
gpa: *Allocator,
-/// Points to the arena allocator of the Decl.
+/// Points to the temporary arena allocator of the Sema.
+/// This arena will be cleared when the sema is destroyed.
arena: *Allocator,
+/// Points to the arena allocator for the owner_decl.
+/// This arena will persist until the decl is invalidated.
+perm_arena: *Allocator,
code: Zir,
air_instructions: std.MultiArrayList(Air.Inst) = .{},
air_extra: std.ArrayListUnmanaged(u32) = .{},
@@ -80,6 +84,8 @@ const Scope = Module.Scope;
const CompileError = Module.CompileError;
const SemaError = Module.SemaError;
const Decl = Module.Decl;
+const CaptureScope = Module.CaptureScope;
+const WipCaptureScope = Module.WipCaptureScope;
const LazySrcLoc = Module.LazySrcLoc;
const RangeSet = @import("RangeSet.zig");
const target_util = @import("target.zig");
@@ -129,15 +135,29 @@ pub fn analyzeBody(
) CompileError!Zir.Inst.Index {
// No tracy calls here, to avoid interfering with the tail call mechanism.
+ const parent_capture_scope = block.wip_capture_scope;
+
+ var wip_captures = WipCaptureScope{
+ .finalized = true,
+ .scope = parent_capture_scope,
+ .perm_arena = sema.perm_arena,
+ .gpa = sema.gpa,
+ };
+ defer if (wip_captures.scope != parent_capture_scope) {
+ wip_captures.deinit();
+ };
+
const map = &block.sema.inst_map;
const tags = block.sema.code.instructions.items(.tag);
const datas = block.sema.code.instructions.items(.data);
+ var orig_captures: usize = parent_capture_scope.captures.count();
+
// We use a while(true) loop here to avoid a redundant way of breaking out of
// the loop. The only way to break out of the loop is with a `noreturn`
// instruction.
var i: usize = 0;
- while (true) {
+ const result = while (true) {
const inst = body[i];
const air_inst: Air.Inst.Ref = switch (tags[inst]) {
// zig fmt: off
@@ -170,6 +190,7 @@ pub fn analyzeBody(
.call_compile_time => try sema.zirCall(block, inst, .compile_time, false),
.call_nosuspend => try sema.zirCall(block, inst, .no_async, false),
.call_async => try sema.zirCall(block, inst, .async_kw, false),
+ .closure_get => try sema.zirClosureGet(block, inst),
.cmp_lt => try sema.zirCmp(block, inst, .lt),
.cmp_lte => try sema.zirCmp(block, inst, .lte),
.cmp_eq => try sema.zirCmpEq(block, inst, .eq, .cmp_eq),
@@ -343,9 +364,6 @@ pub fn analyzeBody(
.trunc => try sema.zirUnaryMath(block, inst),
.round => try sema.zirUnaryMath(block, inst),
- .opaque_decl => try sema.zirOpaqueDecl(block, inst, .parent),
- .opaque_decl_anon => try sema.zirOpaqueDecl(block, inst, .anon),
- .opaque_decl_func => try sema.zirOpaqueDecl(block, inst, .func),
.error_set_decl => try sema.zirErrorSetDecl(block, inst, .parent),
.error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon),
.error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func),
@@ -362,13 +380,13 @@ pub fn analyzeBody(
// Instructions that we know to *always* be noreturn based solely on their tag.
// These functions match the return type of analyzeBody so that we can
// tail call them here.
- .compile_error => return sema.zirCompileError(block, inst),
- .ret_coerce => return sema.zirRetCoerce(block, inst),
- .ret_node => return sema.zirRetNode(block, inst),
- .ret_load => return sema.zirRetLoad(block, inst),
- .ret_err_value => return sema.zirRetErrValue(block, inst),
- .@"unreachable" => return sema.zirUnreachable(block, inst),
- .panic => return sema.zirPanic(block, inst),
+ .compile_error => break sema.zirCompileError(block, inst),
+ .ret_coerce => break sema.zirRetCoerce(block, inst),
+ .ret_node => break sema.zirRetNode(block, inst),
+ .ret_load => break sema.zirRetLoad(block, inst),
+ .ret_err_value => break sema.zirRetErrValue(block, inst),
+ .@"unreachable" => break sema.zirUnreachable(block, inst),
+ .panic => break sema.zirPanic(block, inst),
// zig fmt: on
// Instructions that we know can *never* be noreturn based solely on
@@ -503,34 +521,49 @@ pub fn analyzeBody(
i += 1;
continue;
},
+ .closure_capture => {
+ try sema.zirClosureCapture(block, inst);
+ i += 1;
+ continue;
+ },
// Special case instructions to handle comptime control flow.
.@"break" => {
if (block.is_comptime) {
- return inst; // same as break_inline
+ break inst; // same as break_inline
} else {
- return sema.zirBreak(block, inst);
+ break sema.zirBreak(block, inst);
}
},
- .break_inline => return inst,
+ .break_inline => break inst,
.repeat => {
if (block.is_comptime) {
// Send comptime control flow back to the beginning of this block.
const src: LazySrcLoc = .{ .node_offset = datas[inst].node };
try sema.emitBackwardBranch(block, src);
+ if (wip_captures.scope.captures.count() != orig_captures) {
+ try wip_captures.reset(parent_capture_scope);
+ block.wip_capture_scope = wip_captures.scope;
+ orig_captures = 0;
+ }
i = 0;
continue;
} else {
const src_node = sema.code.instructions.items(.data)[inst].node;
const src: LazySrcLoc = .{ .node_offset = src_node };
try sema.requireRuntimeBlock(block, src);
- return always_noreturn;
+ break always_noreturn;
}
},
.repeat_inline => {
// Send comptime control flow back to the beginning of this block.
const src: LazySrcLoc = .{ .node_offset = datas[inst].node };
try sema.emitBackwardBranch(block, src);
+ if (wip_captures.scope.captures.count() != orig_captures) {
+ try wip_captures.reset(parent_capture_scope);
+ block.wip_capture_scope = wip_captures.scope;
+ orig_captures = 0;
+ }
i = 0;
continue;
},
@@ -545,7 +578,7 @@ pub fn analyzeBody(
if (inst == break_data.block_inst) {
break :blk sema.resolveInst(break_data.operand);
} else {
- return break_inst;
+ break break_inst;
}
},
.block => blk: {
@@ -559,7 +592,7 @@ pub fn analyzeBody(
if (inst == break_data.block_inst) {
break :blk sema.resolveInst(break_data.operand);
} else {
- return break_inst;
+ break break_inst;
}
},
.block_inline => blk: {
@@ -572,11 +605,11 @@ pub fn analyzeBody(
if (inst == break_data.block_inst) {
break :blk sema.resolveInst(break_data.operand);
} else {
- return break_inst;
+ break break_inst;
}
},
.condbr => blk: {
- if (!block.is_comptime) return sema.zirCondbr(block, inst);
+ if (!block.is_comptime) break sema.zirCondbr(block, inst);
// Same as condbr_inline. TODO https://github.com/ziglang/zig/issues/8220
const inst_data = datas[inst].pl_node;
const cond_src: LazySrcLoc = .{ .node_offset_if_cond = inst_data.src_node };
@@ -590,7 +623,7 @@ pub fn analyzeBody(
if (inst == break_data.block_inst) {
break :blk sema.resolveInst(break_data.operand);
} else {
- return break_inst;
+ break break_inst;
}
},
.condbr_inline => blk: {
@@ -606,15 +639,22 @@ pub fn analyzeBody(
if (inst == break_data.block_inst) {
break :blk sema.resolveInst(break_data.operand);
} else {
- return break_inst;
+ break break_inst;
}
},
};
if (sema.typeOf(air_inst).isNoReturn())
- return always_noreturn;
+ break always_noreturn;
try map.put(sema.gpa, inst, air_inst);
i += 1;
+ } else unreachable;
+
+ if (!wip_captures.finalized) {
+ try wip_captures.finalize();
+ block.wip_capture_scope = parent_capture_scope;
}
+
+ return result;
}
fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -626,6 +666,7 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
.struct_decl => return sema.zirStructDecl( block, extended, inst),
.enum_decl => return sema.zirEnumDecl( block, extended),
.union_decl => return sema.zirUnionDecl( block, extended, inst),
+ .opaque_decl => return sema.zirOpaqueDecl( block, extended, inst),
.ret_ptr => return sema.zirRetPtr( block, extended),
.ret_type => return sema.zirRetType( block, extended),
.this => return sema.zirThis( block, extended),
@@ -1011,7 +1052,6 @@ fn zirStructDecl(
}
fn createTypeName(sema: *Sema, block: *Scope.Block, name_strategy: Zir.Inst.NameStrategy) ![:0]u8 {
- _ = block;
switch (name_strategy) {
.anon => {
// It would be neat to have "struct:line:column" but this name has
@@ -1020,14 +1060,14 @@ fn createTypeName(sema: *Sema, block: *Scope.Block, name_strategy: Zir.Inst.Name
// semantically analyzed.
const name_index = sema.mod.getNextAnonNameIndex();
return std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{
- sema.owner_decl.name, name_index,
+ block.src_decl.name, name_index,
});
},
- .parent => return sema.gpa.dupeZ(u8, mem.spanZ(sema.owner_decl.name)),
+ .parent => return sema.gpa.dupeZ(u8, mem.spanZ(block.src_decl.name)),
.func => {
const name_index = sema.mod.getNextAnonNameIndex();
const name = try std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{
- sema.owner_decl.name, name_index,
+ block.src_decl.name, name_index,
});
log.warn("TODO: handle NameStrategy.func correctly instead of using anon name '{s}'", .{
name,
@@ -1083,17 +1123,6 @@ fn zirEnumDecl(
var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
errdefer new_decl_arena.deinit();
- const tag_ty = blk: {
- if (tag_type_ref != .none) {
- // TODO better source location
- // TODO (needs AstGen fix too) move this eval to the block so it gets allocated
- // in the new decl arena.
- break :blk try sema.resolveType(block, src, tag_type_ref);
- }
- const bits = std.math.log2_int_ceil(usize, fields_len);
- break :blk try Type.Tag.int_unsigned.create(&new_decl_arena.allocator, bits);
- };
-
const enum_obj = try new_decl_arena.allocator.create(Module.EnumFull);
const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumFull);
enum_ty_payload.* = .{
@@ -1112,7 +1141,7 @@ fn zirEnumDecl(
enum_obj.* = .{
.owner_decl = new_decl,
- .tag_ty = tag_ty,
+ .tag_ty = Type.initTag(.@"null"),
.fields = .{},
.values = .{},
.node_offset = src.node_offset,
@@ -1140,16 +1169,6 @@ fn zirEnumDecl(
const body_end = extra_index;
extra_index += bit_bags_count;
- try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len);
- const any_values = for (sema.code.extra[body_end..][0..bit_bags_count]) |bag| {
- if (bag != 0) break true;
- } else false;
- if (any_values) {
- try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{
- .ty = tag_ty,
- });
- }
-
{
// We create a block for the field type instructions because they
// may need to reference Decls from inside the enum namespace.
@@ -1172,10 +1191,14 @@ fn zirEnumDecl(
sema.func = null;
defer sema.func = prev_func;
+ var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope);
+ defer wip_captures.deinit();
+
var enum_block: Scope.Block = .{
.parent = null,
.sema = sema,
.src_decl = new_decl,
+ .wip_capture_scope = wip_captures.scope,
.instructions = .{},
.inlining = null,
.is_comptime = true,
@@ -1185,7 +1208,30 @@ fn zirEnumDecl(
if (body.len != 0) {
_ = try sema.analyzeBody(&enum_block, body);
}
+
+ try wip_captures.finalize();
+
+ const tag_ty = blk: {
+ if (tag_type_ref != .none) {
+ // TODO better source location
+ break :blk try sema.resolveType(block, src, tag_type_ref);
+ }
+ const bits = std.math.log2_int_ceil(usize, fields_len);
+ break :blk try Type.Tag.int_unsigned.create(&new_decl_arena.allocator, bits);
+ };
+ enum_obj.tag_ty = tag_ty;
}
+
+ try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len);
+ const any_values = for (sema.code.extra[body_end..][0..bit_bags_count]) |bag| {
+ if (bag != 0) break true;
+ } else false;
+ if (any_values) {
+ try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{
+ .ty = enum_obj.tag_ty,
+ });
+ }
+
var bit_bag_index: usize = body_end;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
@@ -1224,10 +1270,10 @@ fn zirEnumDecl(
// that points to this default value expression rather than the struct.
// But only resolve the source location if we need to emit a compile error.
const tag_val = (try sema.resolveInstConst(block, src, tag_val_ref)).val;
- enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty });
+ enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty });
} else if (any_values) {
const tag_val = try Value.Tag.int_u64.create(&new_decl_arena.allocator, field_i);
- enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty });
+ enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty });
}
}
@@ -1305,20 +1351,14 @@ fn zirUnionDecl(
fn zirOpaqueDecl(
sema: *Sema,
block: *Scope.Block,
+ extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
- name_strategy: Zir.Inst.NameStrategy,
) 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 extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
-
- _ = name_strategy;
- _ = inst_data;
- _ = src;
- _ = extra;
+ _ = extended;
+ _ = inst;
return sema.mod.fail(&block.base, sema.src, "TODO implement zirOpaqueDecl", .{});
}
@@ -2160,6 +2200,7 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com
.parent = parent_block,
.sema = sema,
.src_decl = parent_block.src_decl,
+ .wip_capture_scope = parent_block.wip_capture_scope,
.instructions = .{},
.inlining = parent_block.inlining,
.is_comptime = parent_block.is_comptime,
@@ -2214,7 +2255,7 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com
try sema.mod.semaFile(result.file);
const file_root_decl = result.file.root_decl.?;
try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl);
- return sema.addType(file_root_decl.ty);
+ return sema.addConstant(file_root_decl.ty, file_root_decl.val);
}
fn zirSuspendBlock(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -2259,6 +2300,7 @@ fn zirBlock(
.parent = parent_block,
.sema = sema,
.src_decl = parent_block.src_decl,
+ .wip_capture_scope = parent_block.wip_capture_scope,
.instructions = .{},
.label = &label,
.inlining = parent_block.inlining,
@@ -2866,10 +2908,14 @@ fn analyzeCall(
sema.func = module_fn;
defer sema.func = parent_func;
+ var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, module_fn.owner_decl.src_scope);
+ defer wip_captures.deinit();
+
var child_block: Scope.Block = .{
.parent = null,
.sema = sema,
.src_decl = module_fn.owner_decl,
+ .wip_capture_scope = wip_captures.scope,
.instructions = .{},
.label = null,
.inlining = &inlining,
@@ -3034,6 +3080,9 @@ fn analyzeCall(
break :res2 result;
};
+
+ try wip_captures.finalize();
+
break :res res2;
} else if (func_ty_info.is_generic) res: {
const func_val = try sema.resolveConstValue(block, func_src, func);
@@ -3116,7 +3165,8 @@ fn analyzeCall(
try namespace.anon_decls.ensureUnusedCapacity(gpa, 1);
// Create a Decl for the new function.
- const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node);
+ const src_decl = namespace.getDecl();
+ const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node, src_decl.src_scope);
// TODO better names for generic function instantiations
const name_index = mod.getNextAnonNameIndex();
new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{
@@ -3147,6 +3197,7 @@ fn analyzeCall(
.mod = mod,
.gpa = gpa,
.arena = sema.arena,
+ .perm_arena = &new_decl_arena.allocator,
.code = fn_zir,
.owner_decl = new_decl,
.namespace = namespace,
@@ -3159,10 +3210,14 @@ fn analyzeCall(
};
defer child_sema.deinit();
+ var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope);
+ defer wip_captures.deinit();
+
var child_block: Scope.Block = .{
.parent = null,
.sema = &child_sema,
.src_decl = new_decl,
+ .wip_capture_scope = wip_captures.scope,
.instructions = .{},
.inlining = null,
.is_comptime = true,
@@ -3250,6 +3305,8 @@ fn analyzeCall(
arg_i += 1;
}
+ try wip_captures.finalize();
+
// Populate the Decl ty/val with the function and its type.
new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator);
new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func);
@@ -5164,6 +5221,7 @@ fn analyzeSwitch(
.parent = block,
.sema = sema,
.src_decl = block.src_decl,
+ .wip_capture_scope = block.wip_capture_scope,
.instructions = .{},
.label = &label,
.inlining = block.inlining,
@@ -5268,12 +5326,19 @@ fn analyzeSwitch(
const body = sema.code.extra[extra_index..][0..body_len];
extra_index += body_len;
+ var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope);
+ defer wip_captures.deinit();
+
case_block.instructions.shrinkRetainingCapacity(0);
+ case_block.wip_capture_scope = wip_captures.scope;
+
const item = sema.resolveInst(item_ref);
// `item` is already guaranteed to be constant known.
_ = try sema.analyzeBody(&case_block, body);
+ try wip_captures.finalize();
+
try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
cases_extra.appendAssumeCapacity(1); // items_len
cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
@@ -5301,6 +5366,7 @@ fn analyzeSwitch(
extra_index += items_len;
case_block.instructions.shrinkRetainingCapacity(0);
+ case_block.wip_capture_scope = child_block.wip_capture_scope;
var any_ok: Air.Inst.Ref = .none;
@@ -5379,11 +5445,18 @@ fn analyzeSwitch(
var cond_body = case_block.instructions.toOwnedSlice(gpa);
defer gpa.free(cond_body);
+ var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope);
+ defer wip_captures.deinit();
+
case_block.instructions.shrinkRetainingCapacity(0);
+ case_block.wip_capture_scope = wip_captures.scope;
+
const body = sema.code.extra[extra_index..][0..body_len];
extra_index += body_len;
_ = try sema.analyzeBody(&case_block, body);
+ try wip_captures.finalize();
+
if (is_first) {
is_first = false;
first_else_body = cond_body;
@@ -5409,9 +5482,16 @@ fn analyzeSwitch(
var final_else_body: []const Air.Inst.Index = &.{};
if (special.body.len != 0) {
+ var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope);
+ defer wip_captures.deinit();
+
case_block.instructions.shrinkRetainingCapacity(0);
+ case_block.wip_capture_scope = wip_captures.scope;
+
_ = try sema.analyzeBody(&case_block, special.body);
+ try wip_captures.finalize();
+
if (is_first) {
final_else_body = case_block.instructions.items;
} else {
@@ -5693,7 +5773,7 @@ fn zirImport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
try mod.semaFile(result.file);
const file_root_decl = result.file.root_decl.?;
try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl);
- return sema.addType(file_root_decl.ty);
+ return sema.addConstant(file_root_decl.ty, file_root_decl.val);
}
fn zirRetErrValueCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -6536,8 +6616,45 @@ fn zirThis(
block: *Scope.Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
+ const this_decl = block.base.namespace().getDecl();
const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
- return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirThis", .{});
+ return sema.analyzeDeclVal(block, src, this_decl);
+}
+
+fn zirClosureCapture(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+) CompileError!void {
+ // TODO: Compile error when closed over values are modified
+ const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
+ const tv = try sema.resolveInstConst(block, inst_data.src(), inst_data.operand);
+ try block.wip_capture_scope.captures.putNoClobber(sema.gpa, inst, .{
+ .ty = try tv.ty.copy(sema.perm_arena),
+ .val = try tv.val.copy(sema.perm_arena),
+ });
+}
+
+fn zirClosureGet(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+) CompileError!Air.Inst.Ref {
+ // TODO CLOSURE: Test this with inline functions
+ const inst_data = sema.code.instructions.items(.data)[inst].inst_node;
+ var scope: *CaptureScope = block.src_decl.src_scope.?;
+ // Note: The target closure must be in this scope list.
+ // If it's not here, the zir is invalid, or the list is broken.
+ const tv = while (true) {
+ // Note: We don't need to add a dependency here, because
+ // decls always depend on their lexical parents.
+ if (scope.captures.getPtr(inst_data.inst)) |tv| {
+ break tv;
+ }
+ scope = scope.parent.?;
+ } else unreachable;
+
+ return sema.addConstant(tv.ty, tv.val);
}
fn zirRetAddr(
@@ -8615,6 +8732,7 @@ fn addSafetyCheck(
var fail_block: Scope.Block = .{
.parent = parent_block,
.sema = sema,
+ .wip_capture_scope = parent_block.wip_capture_scope,
.src_decl = parent_block.src_decl,
.instructions = .{},
.inlining = parent_block.inlining,
@@ -8714,7 +8832,7 @@ fn safetyPanic(
block: *Scope.Block,
src: LazySrcLoc,
panic_id: PanicId,
-) !Zir.Inst.Index {
+) CompileError!Zir.Inst.Index {
const msg = switch (panic_id) {
.unreach => "reached unreachable code",
.unwrap_null => "attempt to use null value",
@@ -10666,6 +10784,10 @@ pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty:
sema.namespace = &struct_obj.namespace;
defer sema.namespace = prev_namespace;
+ const old_src = block.src_decl;
+ defer block.src_decl = old_src;
+ block.src_decl = struct_obj.owner_decl;
+
struct_obj.status = .field_types_wip;
try sema.analyzeStructFields(block, struct_obj);
struct_obj.status = .have_field_types;
@@ -10684,6 +10806,10 @@ pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty:
sema.namespace = &union_obj.namespace;
defer sema.namespace = prev_namespace;
+ const old_src = block.src_decl;
+ defer block.src_decl = old_src;
+ block.src_decl = union_obj.owner_decl;
+
union_obj.status = .field_types_wip;
try sema.analyzeUnionFields(block, union_obj);
union_obj.status = .have_field_types;
@@ -10885,9 +11011,11 @@ fn analyzeUnionFields(
const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
extra_index += @boolToInt(small.has_src_node);
- if (small.has_tag_type) {
+ const tag_type_ref: Zir.Inst.Ref = if (small.has_tag_type) blk: {
+ const ty_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
- }
+ break :blk ty_ref;
+ } else .none;
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
@@ -10996,6 +11124,7 @@ fn analyzeUnionFields(
}
// TODO resolve the union tag_type_ref
+ _ = tag_type_ref;
}
fn getBuiltin(
src/Zir.zig
@@ -49,8 +49,6 @@ pub const Header = extern struct {
};
pub const ExtraIndex = enum(u32) {
- /// Ref. The main struct decl for this file.
- main_struct,
/// If this is 0, no compile errors. Otherwise there is a `CompileErrors`
/// payload at this index.
compile_errors,
@@ -61,11 +59,6 @@ pub const ExtraIndex = enum(u32) {
_,
};
-pub fn getMainStruct(zir: Zir) Inst.Index {
- return zir.extra[@enumToInt(ExtraIndex.main_struct)] -
- @intCast(u32, Inst.Ref.typed_value_map.len);
-}
-
/// Returns the requested data, as well as the new index which is at the start of the
/// trailers for the object.
pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, end: usize } {
@@ -112,6 +105,10 @@ pub fn deinit(code: *Zir, gpa: *Allocator) void {
code.* = undefined;
}
+/// ZIR is structured so that the outermost "main" struct of any file
+/// is always at index 0.
+pub const main_struct_inst: Inst.Index = 0;
+
/// These are untyped instructions generated from an Abstract Syntax Tree.
/// The data here is immutable because it is possible to have multiple
/// analyses on the same ZIR happening at the same time.
@@ -267,11 +264,6 @@ pub const Inst = struct {
/// only the taken branch is analyzed. The then block and else block must
/// terminate with an "inline" variant of a noreturn instruction.
condbr_inline,
- /// An opaque type definition. Provides an AST node only.
- /// Uses the `pl_node` union field. Payload is `OpaqueDecl`.
- opaque_decl,
- opaque_decl_anon,
- opaque_decl_func,
/// An error set type definition. Contains a list of field names.
/// Uses the `pl_node` union field. Payload is `ErrorSetDecl`.
error_set_decl,
@@ -941,6 +933,17 @@ pub const Inst = struct {
@"await",
await_nosuspend,
+ /// When a type or function refers to a comptime value from an outer
+ /// scope, that forms a closure over comptime value. The outer scope
+ /// will record a capture of that value, which encodes its current state
+ /// and marks it to persist. Uses `un_tok` field. Operand is the
+ /// instruction value to capture.
+ closure_capture,
+ /// The inner scope of a closure uses closure_get to retrieve the value
+ /// stored by the outer scope. Uses `inst_node` field. Operand is the
+ /// closure_capture instruction ref.
+ closure_get,
+
/// The ZIR instruction tag is one of the `Extended` ones.
/// Uses the `extended` union field.
extended,
@@ -996,9 +999,6 @@ pub const Inst = struct {
.cmp_gt,
.cmp_neq,
.coerce_result_ptr,
- .opaque_decl,
- .opaque_decl_anon,
- .opaque_decl_func,
.error_set_decl,
.error_set_decl_anon,
.error_set_decl_func,
@@ -1191,6 +1191,8 @@ pub const Inst = struct {
.await_nosuspend,
.ret_err_value_code,
.extended,
+ .closure_get,
+ .closure_capture,
=> false,
.@"break",
@@ -1258,9 +1260,6 @@ pub const Inst = struct {
.coerce_result_ptr = .bin,
.condbr = .pl_node,
.condbr_inline = .pl_node,
- .opaque_decl = .pl_node,
- .opaque_decl_anon = .pl_node,
- .opaque_decl_func = .pl_node,
.error_set_decl = .pl_node,
.error_set_decl_anon = .pl_node,
.error_set_decl_func = .pl_node,
@@ -1478,6 +1477,9 @@ pub const Inst = struct {
.@"await" = .un_node,
.await_nosuspend = .un_node,
+ .closure_capture = .un_tok,
+ .closure_get = .inst_node,
+
.extended = .extended,
});
};
@@ -1510,6 +1512,10 @@ pub const Inst = struct {
/// `operand` is payload index to `UnionDecl`.
/// `small` is `UnionDecl.Small`.
union_decl,
+ /// An opaque type definition. Contains references to decls and captures.
+ /// `operand` is payload index to `OpaqueDecl`.
+ /// `small` is `OpaqueDecl.Small`.
+ opaque_decl,
/// Obtains a pointer to the return value.
/// `operand` is `src_node: i32`.
ret_ptr,
@@ -2194,6 +2200,18 @@ pub const Inst = struct {
line: u32,
column: u32,
},
+ /// Used for unary operators which reference an inst,
+ /// with an AST node source location.
+ inst_node: struct {
+ /// Offset from Decl AST node index.
+ src_node: i32,
+ /// The meaning of this operand depends on the corresponding `Tag`.
+ inst: Index,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .node_offset = self.src_node };
+ }
+ },
// Make sure we don't accidentally add a field to make this union
// bigger than expected. Note that in Debug builds, Zig is allowed
@@ -2231,6 +2249,7 @@ pub const Inst = struct {
@"break",
switch_capture,
dbg_stmt,
+ inst_node,
};
};
@@ -2662,13 +2681,15 @@ pub const Inst = struct {
};
/// Trailing:
- /// 0. decl_bits: u32 // for every 8 decls
+ /// 0. src_node: i32, // if has_src_node
+ /// 1. decls_len: u32, // if has_decls_len
+ /// 2. decl_bits: u32 // for every 8 decls
/// - sets of 4 bits:
/// 0b000X: whether corresponding decl is pub
/// 0b00X0: whether corresponding decl is exported
/// 0b0X00: whether corresponding decl has an align expression
/// 0bX000: whether corresponding decl has a linksection or an address space expression
- /// 1. decl: { // for every decls_len
+ /// 3. decl: { // for every decls_len
/// src_hash: [4]u32, // hash of source bytes
/// line: u32, // line number of decl, relative to parent
/// name: u32, // null terminated string index
@@ -2685,7 +2706,12 @@ pub const Inst = struct {
/// }
/// }
pub const OpaqueDecl = struct {
- decls_len: u32,
+ pub const Small = packed struct {
+ has_src_node: bool,
+ has_decls_len: bool,
+ name_strategy: NameStrategy,
+ _: u12 = undefined,
+ };
};
/// Trailing: field_name: u32 // for every field: null terminated string index
@@ -2937,15 +2963,6 @@ pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator {
const tags = zir.instructions.items(.tag);
const datas = zir.instructions.items(.data);
switch (tags[decl_inst]) {
- .opaque_decl,
- .opaque_decl_anon,
- .opaque_decl_func,
- => {
- const inst_data = datas[decl_inst].pl_node;
- const extra = zir.extraData(Inst.OpaqueDecl, inst_data.payload_index);
- return declIteratorInner(zir, extra.end, extra.data.decls_len);
- },
-
// Functions are allowed and yield no iterations.
// There is one case matching this in the extended instruction set below.
.func,
@@ -3000,6 +3017,18 @@ pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator {
return declIteratorInner(zir, extra_index, decls_len);
},
+ .opaque_decl => {
+ const small = @bitCast(Inst.OpaqueDecl.Small, extended.small);
+ var extra_index: usize = extended.operand;
+ extra_index += @boolToInt(small.has_src_node);
+ const decls_len = if (small.has_decls_len) decls_len: {
+ const decls_len = zir.extra[extra_index];
+ extra_index += 1;
+ break :decls_len decls_len;
+ } else 0;
+
+ return declIteratorInner(zir, extra_index, decls_len);
+ },
else => unreachable,
}
},
@@ -3037,13 +3066,6 @@ fn findDeclsInner(
const datas = zir.instructions.items(.data);
switch (tags[inst]) {
- // Decl instructions are interesting but have no body.
- // TODO yes they do have a body actually. recurse over them just like block instructions.
- .opaque_decl,
- .opaque_decl_anon,
- .opaque_decl_func,
- => return list.append(inst),
-
// Functions instructions are interesting and have a body.
.func,
.func_inferred,
@@ -3071,9 +3093,12 @@ fn findDeclsInner(
return zir.findDeclsBody(list, body);
},
+ // Decl instructions are interesting but have no body.
+ // TODO yes they do have a body actually. recurse over them just like block instructions.
.struct_decl,
.union_decl,
.enum_decl,
+ .opaque_decl,
=> return list.append(inst),
else => return,
test/behavior/this.zig
@@ -24,11 +24,10 @@ test "this refer to module call private fn" {
}
test "this refer to container" {
- var pt = Point(i32){
- .x = 12,
- .y = 34,
- };
- pt.addOne();
+ var pt: Point(i32) = undefined;
+ pt.x = 12;
+ pt.y = 34;
+ Point(i32).addOne(&pt);
try expect(pt.x == 13);
try expect(pt.y == 35);
}
test/behavior.zig
@@ -1,20 +1,22 @@
const builtin = @import("builtin");
test {
- _ = @import("behavior/bool.zig");
+ // Tests that pass for both.
+ _ = @import("behavior/array.zig");
+ _ = @import("behavior/atomics.zig");
_ = @import("behavior/basic.zig");
- _ = @import("behavior/generics.zig");
+ _ = @import("behavior/bool.zig");
+ _ = @import("behavior/cast.zig");
_ = @import("behavior/eval.zig");
- _ = @import("behavior/pointers.zig");
+ _ = @import("behavior/generics.zig");
_ = @import("behavior/if.zig");
- _ = @import("behavior/cast.zig");
- _ = @import("behavior/array.zig");
- _ = @import("behavior/usingnamespace.zig");
- _ = @import("behavior/atomics.zig");
+ _ = @import("behavior/pointers.zig");
_ = @import("behavior/sizeof_and_typeof.zig");
- _ = @import("behavior/translate_c_macros.zig");
_ = @import("behavior/struct.zig");
+ _ = @import("behavior/this.zig");
+ _ = @import("behavior/translate_c_macros.zig");
_ = @import("behavior/union.zig");
+ _ = @import("behavior/usingnamespace.zig");
_ = @import("behavior/widening.zig");
if (builtin.zig_is_stage2) {
@@ -142,7 +144,6 @@ test {
_ = @import("behavior/switch.zig");
_ = @import("behavior/switch_prong_err_enum.zig");
_ = @import("behavior/switch_prong_implicit_cast.zig");
- _ = @import("behavior/this.zig");
_ = @import("behavior/truncate.zig");
_ = @import("behavior/try.zig");
_ = @import("behavior/tuple.zig");