stage2: field type expressions support referencing locals
The big change in this commit is making `semaDecl` resolve the fields if
the Decl ends up being a struct or union. It needs to do this while
the `Sema` is still in scope, because it will have the resolved AIR
instructions that the field type expressions possibly reference. We do
this after the decl is populated and set to `complete` so that a `Decl`
may reference itself.
Everything else is fixes and improvements to make the test suite pass
again after making this change.
* New AIR instruction: `ptr_elem_ptr`
- Implemented for LLVM backend
* New Type tag: `type_info` which represents `std.builtin.TypeInfo`. It
is used by AstGen for the operand type of `@Type`.
* ZIR instruction `set_float_mode` uses `coerced_ty` to avoid
superfluous `as` instruction on operand.
* ZIR instruction `Type` uses `coerced_ty` to properly handle result
location type of operand.
* Fix two instances of `enum_nonexhaustive` Value Tag not handled
properly - it should generally be handled the same as `enum_full`.
* Fix struct and union field resolution not copying Type and Value
objects into its Decl arena.
* Fix enum tag value resolution discarding the ZIR=>AIR instruction map
for the child Sema, when they still needed to be accessed.
* Fix `zirResolveInferredAlloc` use-after-free in the AIR instructions
data array.
* Fix `elemPtrArray` not respecting const/mutable attribute of pointer
in the result type.
* Fix LLVM backend crashing when `updateDeclExports` is called before
`updateDecl`/`updateFunc` (which is, according to the API, perfectly
legal for the frontend to do).
* Fix LLVM backend handling element pointer of pointer-to-array. It
needed another index in the GEP otherwise LLVM saw the wrong type.
* Fix LLVM test cases not returning 0 from main, causing test failures.
Fixes a regression introduced in
6a5094872f.
* Implement comptime shift-right.
* Implement `@Type` for integers and `@TypeInfo` for integers.
* Implement union initialization syntax.
* Implement `zirFieldType` for unions.
* Implement `elemPtrArray` for a runtime-known operand.
* Make `zirLog2IntType` support RHS of shift being `comptime_int`. In
this case it returns `comptime_int`.
The motivating test case for this commit was originally:
```zig
test "example" {
var l: List(10) = undefined;
l.array[1] = 1;
}
fn List(comptime L: usize) type {
var T = u8;
return struct {
array: [L]T,
};
}
```
However I changed it to:
```zig
test "example" {
var l: List = undefined;
l.array[1] = 1;
}
const List = blk: {
const T = [10]u8;
break :blk struct {
array: T,
};
};
```
Which ended up being a similar, smaller problem. The former test case
will require a similar solution in the implementation of comptime
function calls - checking if the result of the function call is a struct
or union, and using the child `Sema` before it is destroyed to resolve
the fields.
This commit is contained in:
320
src/Module.zig
320
src/Module.zig
@@ -554,8 +554,8 @@ pub const Decl = struct {
|
||||
assert(struct_obj.owner_decl == decl);
|
||||
return &struct_obj.namespace;
|
||||
},
|
||||
.enum_full => {
|
||||
const enum_obj = ty.castTag(.enum_full).?.data;
|
||||
.enum_full, .enum_nonexhaustive => {
|
||||
const enum_obj = ty.cast(Type.Payload.EnumFull).?.data;
|
||||
assert(enum_obj.owner_decl == decl);
|
||||
return &enum_obj.namespace;
|
||||
},
|
||||
@@ -660,6 +660,7 @@ pub const Struct = struct {
|
||||
/// is necessary to determine whether it has bits at runtime.
|
||||
known_has_bits: bool,
|
||||
|
||||
/// The `Type` and `Value` memory is owned by the arena of the Struct's owner_decl.
|
||||
pub const Field = struct {
|
||||
/// Uses `noreturn` to indicate `anytype`.
|
||||
/// undefined until `status` is `have_field_types` or `have_layout`.
|
||||
@@ -3091,6 +3092,9 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
||||
if (linksection_ref == .none) break :blk Value.initTag(.null_value);
|
||||
break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val;
|
||||
};
|
||||
// Note this resolves the type of the Decl, not the value; if this Decl
|
||||
// is a struct, for example, this resolves `type` (which needs no resolution),
|
||||
// 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
|
||||
@@ -3193,6 +3197,15 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
||||
if (type_changed and mod.emit_h != null) {
|
||||
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
}
|
||||
} else if (decl_tv.ty.zigTypeTag() == .Type) {
|
||||
// In case this Decl is a struct or union, we need to resolve the fields
|
||||
// while we still have the `Sema` in scope, so that the field type expressions
|
||||
// can use the resolved AIR instructions that they possibly reference.
|
||||
// We do this after the decl is populated and set to `complete` so that a `Decl`
|
||||
// may reference itself.
|
||||
var buffer: Value.ToTypeBuffer = undefined;
|
||||
const ty = decl.val.toType(&buffer);
|
||||
try sema.resolveDeclFields(&block_scope, src, ty);
|
||||
}
|
||||
|
||||
if (decl.is_exported) {
|
||||
@@ -4450,309 +4463,6 @@ pub const PeerTypeCandidateSrc = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const gpa = mod.gpa;
|
||||
const zir = struct_obj.owner_decl.namespace.file_scope.zir;
|
||||
const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
|
||||
assert(extended.opcode == .struct_decl);
|
||||
const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
|
||||
var extra_index: usize = extended.operand;
|
||||
|
||||
const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset };
|
||||
extra_index += @boolToInt(small.has_src_node);
|
||||
|
||||
const body_len = if (small.has_body_len) blk: {
|
||||
const body_len = zir.extra[extra_index];
|
||||
extra_index += 1;
|
||||
break :blk body_len;
|
||||
} else 0;
|
||||
|
||||
const fields_len = if (small.has_fields_len) blk: {
|
||||
const fields_len = zir.extra[extra_index];
|
||||
extra_index += 1;
|
||||
break :blk fields_len;
|
||||
} else 0;
|
||||
|
||||
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;
|
||||
|
||||
// Skip over decls.
|
||||
var decls_it = zir.declIteratorInner(extra_index, decls_len);
|
||||
while (decls_it.next()) |_| {}
|
||||
extra_index = decls_it.extra_index;
|
||||
|
||||
const body = zir.extra[extra_index..][0..body_len];
|
||||
if (fields_len == 0) {
|
||||
assert(body.len == 0);
|
||||
return;
|
||||
}
|
||||
extra_index += body.len;
|
||||
|
||||
var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa);
|
||||
defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state;
|
||||
|
||||
try struct_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
|
||||
|
||||
// We create a block for the field type instructions because they
|
||||
// may need to reference Decls from inside the struct namespace.
|
||||
// Within the field type, default value, and alignment expressions, the "owner decl"
|
||||
// should be the struct itself. Thus we need a new Sema.
|
||||
var sema: Sema = .{
|
||||
.mod = mod,
|
||||
.gpa = gpa,
|
||||
.arena = &decl_arena.allocator,
|
||||
.code = zir,
|
||||
.owner_decl = struct_obj.owner_decl,
|
||||
.namespace = &struct_obj.namespace,
|
||||
.owner_func = null,
|
||||
.func = null,
|
||||
.fn_ret_ty = Type.initTag(.void),
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
var block: Scope.Block = .{
|
||||
.parent = null,
|
||||
.sema = &sema,
|
||||
.src_decl = struct_obj.owner_decl,
|
||||
.instructions = .{},
|
||||
.inlining = null,
|
||||
.is_comptime = true,
|
||||
};
|
||||
defer assert(block.instructions.items.len == 0); // should all be comptime instructions
|
||||
|
||||
if (body.len != 0) {
|
||||
_ = try sema.analyzeBody(&block, body);
|
||||
}
|
||||
|
||||
const bits_per_field = 4;
|
||||
const fields_per_u32 = 32 / bits_per_field;
|
||||
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
|
||||
var bit_bag_index: usize = extra_index;
|
||||
extra_index += bit_bags_count;
|
||||
var cur_bit_bag: u32 = undefined;
|
||||
var field_i: u32 = 0;
|
||||
while (field_i < fields_len) : (field_i += 1) {
|
||||
if (field_i % fields_per_u32 == 0) {
|
||||
cur_bit_bag = zir.extra[bit_bag_index];
|
||||
bit_bag_index += 1;
|
||||
}
|
||||
const has_align = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
const has_default = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
const is_comptime = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
const unused = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
|
||||
_ = unused;
|
||||
|
||||
const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
|
||||
// This string needs to outlive the ZIR code.
|
||||
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
|
||||
if (field_type_ref == .none) {
|
||||
return mod.fail(&block.base, src, "TODO: implement anytype struct field", .{});
|
||||
}
|
||||
const field_ty: Type = if (field_type_ref == .none)
|
||||
Type.initTag(.noreturn)
|
||||
else
|
||||
// TODO: if we need to report an error here, use a source location
|
||||
// that points to this type expression rather than the struct.
|
||||
// But only resolve the source location if we need to emit a compile error.
|
||||
try sema.resolveType(&block, src, field_type_ref);
|
||||
|
||||
const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
|
||||
assert(!gop.found_existing);
|
||||
gop.value_ptr.* = .{
|
||||
.ty = field_ty,
|
||||
.abi_align = Value.initTag(.abi_align_default),
|
||||
.default_val = Value.initTag(.unreachable_value),
|
||||
.is_comptime = is_comptime,
|
||||
.offset = undefined,
|
||||
};
|
||||
|
||||
if (has_align) {
|
||||
const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
// TODO: if we need to report an error here, use a source location
|
||||
// that points to this alignment expression rather than the struct.
|
||||
// But only resolve the source location if we need to emit a compile error.
|
||||
gop.value_ptr.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val;
|
||||
}
|
||||
if (has_default) {
|
||||
const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
// TODO: if we need to report an error here, use a source location
|
||||
// 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.
|
||||
gop.value_ptr.default_val = (try sema.resolveInstConst(&block, src, default_ref)).val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const gpa = mod.gpa;
|
||||
const zir = union_obj.owner_decl.namespace.file_scope.zir;
|
||||
const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
|
||||
assert(extended.opcode == .union_decl);
|
||||
const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
|
||||
var extra_index: usize = extended.operand;
|
||||
|
||||
const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
|
||||
extra_index += @boolToInt(small.has_src_node);
|
||||
|
||||
if (small.has_tag_type) {
|
||||
extra_index += 1;
|
||||
}
|
||||
|
||||
const body_len = if (small.has_body_len) blk: {
|
||||
const body_len = zir.extra[extra_index];
|
||||
extra_index += 1;
|
||||
break :blk body_len;
|
||||
} else 0;
|
||||
|
||||
const fields_len = if (small.has_fields_len) blk: {
|
||||
const fields_len = zir.extra[extra_index];
|
||||
extra_index += 1;
|
||||
break :blk fields_len;
|
||||
} else 0;
|
||||
|
||||
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;
|
||||
|
||||
// Skip over decls.
|
||||
var decls_it = zir.declIteratorInner(extra_index, decls_len);
|
||||
while (decls_it.next()) |_| {}
|
||||
extra_index = decls_it.extra_index;
|
||||
|
||||
const body = zir.extra[extra_index..][0..body_len];
|
||||
if (fields_len == 0) {
|
||||
assert(body.len == 0);
|
||||
return;
|
||||
}
|
||||
extra_index += body.len;
|
||||
|
||||
var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa);
|
||||
defer union_obj.owner_decl.value_arena.?.* = decl_arena.state;
|
||||
|
||||
try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
|
||||
|
||||
// We create a block for the field type instructions because they
|
||||
// may need to reference Decls from inside the struct namespace.
|
||||
// Within the field type, default value, and alignment expressions, the "owner decl"
|
||||
// should be the struct itself. Thus we need a new Sema.
|
||||
var sema: Sema = .{
|
||||
.mod = mod,
|
||||
.gpa = gpa,
|
||||
.arena = &decl_arena.allocator,
|
||||
.code = zir,
|
||||
.owner_decl = union_obj.owner_decl,
|
||||
.namespace = &union_obj.namespace,
|
||||
.owner_func = null,
|
||||
.func = null,
|
||||
.fn_ret_ty = Type.initTag(.void),
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
var block: Scope.Block = .{
|
||||
.parent = null,
|
||||
.sema = &sema,
|
||||
.src_decl = union_obj.owner_decl,
|
||||
.instructions = .{},
|
||||
.inlining = null,
|
||||
.is_comptime = true,
|
||||
};
|
||||
defer assert(block.instructions.items.len == 0); // should all be comptime instructions
|
||||
|
||||
if (body.len != 0) {
|
||||
_ = try sema.analyzeBody(&block, body);
|
||||
}
|
||||
|
||||
const bits_per_field = 4;
|
||||
const fields_per_u32 = 32 / bits_per_field;
|
||||
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
|
||||
var bit_bag_index: usize = extra_index;
|
||||
extra_index += bit_bags_count;
|
||||
var cur_bit_bag: u32 = undefined;
|
||||
var field_i: u32 = 0;
|
||||
while (field_i < fields_len) : (field_i += 1) {
|
||||
if (field_i % fields_per_u32 == 0) {
|
||||
cur_bit_bag = zir.extra[bit_bag_index];
|
||||
bit_bag_index += 1;
|
||||
}
|
||||
const has_type = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
const has_align = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
const has_tag = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
const unused = @truncate(u1, cur_bit_bag) != 0;
|
||||
cur_bit_bag >>= 1;
|
||||
_ = unused;
|
||||
|
||||
const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
|
||||
const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
|
||||
const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
break :blk field_type_ref;
|
||||
} else .none;
|
||||
|
||||
const align_ref: Zir.Inst.Ref = if (has_align) blk: {
|
||||
const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
|
||||
extra_index += 1;
|
||||
break :blk align_ref;
|
||||
} else .none;
|
||||
|
||||
if (has_tag) {
|
||||
extra_index += 1;
|
||||
}
|
||||
|
||||
// This string needs to outlive the ZIR code.
|
||||
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
|
||||
const field_ty: Type = if (field_type_ref == .none)
|
||||
Type.initTag(.void)
|
||||
else
|
||||
// TODO: if we need to report an error here, use a source location
|
||||
// that points to this type expression rather than the union.
|
||||
// But only resolve the source location if we need to emit a compile error.
|
||||
try sema.resolveType(&block, src, field_type_ref);
|
||||
|
||||
const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
|
||||
assert(!gop.found_existing);
|
||||
gop.value_ptr.* = .{
|
||||
.ty = field_ty,
|
||||
.abi_align = Value.initTag(.abi_align_default),
|
||||
};
|
||||
|
||||
if (align_ref != .none) {
|
||||
// TODO: if we need to report an error here, use a source location
|
||||
// that points to this alignment expression rather than the struct.
|
||||
// But only resolve the source location if we need to emit a compile error.
|
||||
gop.value_ptr.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO resolve the union tag_type_ref
|
||||
}
|
||||
|
||||
/// Called from `performAllTheWork`, after all AstGen workers have finished,
|
||||
/// and before the main semantic analysis loop begins.
|
||||
pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
|
||||
|
||||
Reference in New Issue
Block a user