From 0cd361219c107bce48f2d7b44c6f3dd05ea6ccf4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 20 Aug 2021 15:23:55 -0700 Subject: 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 6a5094872f10acc629543cc7f10533b438d0283a. * 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. --- src/codegen/c.zig | 8 ++++++++ src/codegen/llvm.zig | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index d3417d1567..e7994ffd06 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -913,6 +913,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .ptr_elem_val => try airPtrElemVal(o, inst, "["), .ptr_ptr_elem_val => try airPtrElemVal(o, inst, "[0]["), + .ptr_elem_ptr => try airPtrElemPtr(o, inst), .slice_elem_val => try airSliceElemVal(o, inst, "["), .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["), @@ -960,6 +961,13 @@ fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { return o.dg.fail("TODO: C backend: airPtrElemVal", .{}); } +fn airPtrElemPtr(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + return o.dg.fail("TODO: C backend: airPtrElemPtr", .{}); +} + fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { const is_volatile = false; // TODO if (!is_volatile and o.liveness.isUnused(inst)) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 632b275704..745fb036db 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -432,6 +432,8 @@ pub const Object = struct { }, else => |e| return e, }; + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + try self.updateDeclExports(module, decl, decl_exports); } pub fn updateDeclExports( @@ -440,7 +442,9 @@ pub const Object = struct { decl: *const Module.Decl, exports: []const *Module.Export, ) !void { - const llvm_fn = self.llvm_module.getNamedFunction(decl.name).?; + // If the module does not already have the function, we ignore this function call + // because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`. + const llvm_fn = self.llvm_module.getNamedFunction(decl.name) orelse return; const is_extern = decl.val.tag() == .extern_fn; if (is_extern or exports.len != 0) { llvm_fn.setLinkage(.External); @@ -1041,6 +1045,7 @@ pub const FuncGen = struct { .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_elem_ptr => try self.airPtrElemPtr(inst), .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), @@ -1296,11 +1301,35 @@ pub const FuncGen = struct { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const base_ptr = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const indices: [1]*const llvm.Value = .{rhs}; - const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + const ptr = if (self.air.typeOf(bin_op.lhs).isSinglePointer()) ptr: { + // If this is a single-item pointer to an array, we need another index in the GEP. + const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs }; + break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } else ptr: { + const indices: [1]*const llvm.Value = .{rhs}; + break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + }; return self.builder.buildLoad(ptr, ""); } + fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; + const base_ptr = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + if (self.air.typeOf(bin_op.lhs).isSinglePointer()) { + // If this is a single-item pointer to an array, we need another index in the GEP. + const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs }; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } else { + const indices: [1]*const llvm.Value = .{rhs}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } + } + fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const is_volatile = false; // TODO if (!is_volatile and self.liveness.isUnused(inst)) -- cgit v1.2.3