From 14d8a1c10da7e98ccfdbfd50473582043e48ca10 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 25 Jul 2021 22:38:50 -0700 Subject: [PATCH] stage2 llvm backend improvements working towards `zig test` * properly set global variables to const if they are not a global variable. * implement global variable initializations. * initial implementation of llvmType() for structs and functions. * implement genTypedValue for variable tags * implement more AIR instructions: varptr, slice_ptr, slice_len, slice_elem_val, ptr_slice_elem_val, unwrap_errunion_payload, unwrap_errunion_payload_ptr, unwrap_errunion_err, unwrap_errunion_err_ptr. --- src/codegen/llvm.zig | 219 +++++++++++++++++++++++++++++----- src/codegen/llvm/bindings.zig | 22 +++- src/type.zig | 2 +- 3 files changed, 211 insertions(+), 32 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d6a4449616..4224591b0b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -512,12 +512,15 @@ pub const DeclGen = struct { // TODO: remove this redundant `llvmType`, it is also called in `genTypedValue`. const llvm_type = try self.llvmType(decl.ty); - const val = try self.genTypedValue(.{ .ty = decl.ty, .val = decl.val }, null); const global = self.llvmModule().addGlobal(llvm_type, decl.name); - llvm.setInitializer(global, val); + const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { + const variable = payload.data; + global.setGlobalConstant(.False); + break :init_val variable.init; + } else decl.val; - // TODO ask the Decl if it is const - // https://github.com/ziglang/zig/issues/7582 + const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }, null); + llvm.setInitializer(global, llvm_init); return global; } @@ -576,6 +579,36 @@ pub const DeclGen = struct { .ErrorSet => { return self.context.intType(16); }, + .Struct => { + const struct_obj = t.castTag(.@"struct").?.data; + assert(struct_obj.haveFieldTypes()); + const llvm_fields = try self.gpa.alloc(*const llvm.Type, struct_obj.fields.count()); + defer self.gpa.free(llvm_fields); + for (struct_obj.fields.values()) |field, i| { + llvm_fields[i] = try self.llvmType(field.ty); + } + return self.context.structType( + llvm_fields.ptr, + @intCast(c_uint, llvm_fields.len), + .False, + ); + }, + .Fn => { + const ret_ty = try self.llvmType(t.fnReturnType()); + const params_len = t.fnParamLen(); + const llvm_params = try self.gpa.alloc(*const llvm.Type, params_len); + defer self.gpa.free(llvm_params); + for (llvm_params) |*llvm_param, i| { + llvm_param.* = try self.llvmType(t.fnParamType(i)); + } + const is_var_args = t.fnIsVarArgs(); + return llvm.functionType( + ret_ty, + llvm_params.ptr, + @intCast(c_uint, llvm_params.len), + llvm.Bool.fromBool(is_var_args), + ); + }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, .Type => unreachable, @@ -586,10 +619,8 @@ pub const DeclGen = struct { .BoundFn => @panic("TODO remove BoundFn from the language"), .Float, - .Struct, .Enum, .Union, - .Fn, .Opaque, .Frame, .AnyFrame, @@ -645,7 +676,11 @@ pub const DeclGen = struct { _ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); return alloca; }, - else => return self.todo("implement const of pointer type '{}'", .{tv.ty}), + .variable => { + const variable = tv.val.castTag(.variable).?.data; + return self.resolveGlobalDecl(variable.owner_decl); + }, + else => |tag| return self.todo("implement const of pointer type '{}' ({})", .{ tv.ty, tag }), }, .Array => { if (tv.val.castTag(.bytes)) |payload| { @@ -765,45 +800,64 @@ pub const FuncGen = struct { const air_tags = self.air.instructions.items(.tag); for (body) |inst| { const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { + // zig fmt: off .add => try self.airAdd(inst), .sub => try self.airSub(inst), - .cmp_eq => try self.airCmp(inst, .eq), - .cmp_gt => try self.airCmp(inst, .gt), + .cmp_eq => try self.airCmp(inst, .eq), + .cmp_gt => try self.airCmp(inst, .gt), .cmp_gte => try self.airCmp(inst, .gte), - .cmp_lt => try self.airCmp(inst, .lt), + .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), .cmp_neq => try self.airCmp(inst, .neq), - .is_non_null => try self.airIsNonNull(inst, false), + .is_non_null => try self.airIsNonNull(inst, false), .is_non_null_ptr => try self.airIsNonNull(inst, true), - .is_null => try self.airIsNull(inst, false), - .is_null_ptr => try self.airIsNull(inst, true), + .is_null => try self.airIsNull(inst, false), + .is_null_ptr => try self.airIsNull(inst, true), + .is_non_err => try self.airIsErr(inst, true, false), + .is_non_err_ptr => try self.airIsErr(inst, true, true), + .is_err => try self.airIsErr(inst, false, false), + .is_err_ptr => try self.airIsErr(inst, false, true), - .alloc => try self.airAlloc(inst), - .arg => try self.airArg(inst), - .bitcast => try self.airBitCast(inst), - .block => try self.airBlock(inst), - .br => try self.airBr(inst), + .alloc => try self.airAlloc(inst), + .arg => try self.airArg(inst), + .bitcast => try self.airBitCast(inst), + .block => try self.airBlock(inst), + .br => try self.airBr(inst), .breakpoint => try self.airBreakpoint(inst), - .call => try self.airCall(inst), - .cond_br => try self.airCondBr(inst), - .intcast => try self.airIntCast(inst), - .ptrtoint => try self.airPtrToInt(inst), - .load => try self.airLoad(inst), - .loop => try self.airLoop(inst), - .not => try self.airNot(inst), - .ret => try self.airRet(inst), - .store => try self.airStore(inst), - .unreach => self.airUnreach(inst), - .optional_payload => try self.airOptionalPayload(inst, false), + .call => try self.airCall(inst), + .cond_br => try self.airCondBr(inst), + .intcast => try self.airIntCast(inst), + .ptrtoint => try self.airPtrToInt(inst), + .load => try self.airLoad(inst), + .loop => try self.airLoop(inst), + .not => try self.airNot(inst), + .ret => try self.airRet(inst), + .store => try self.airStore(inst), + .assembly => try self.airAssembly(inst), + .varptr => try self.airVarPtr(inst), + .slice_ptr => try self.airSliceField(inst, 0), + .slice_len => try self.airSliceField(inst, 1), + + .slice_elem_val => try self.airSliceElemVal(inst, false), + .ptr_slice_elem_val => try self.airSliceElemVal(inst, true), + + .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), - .assembly => try self.airAssembly(inst), + + .unwrap_errunion_payload => try self.airErrUnionPayload(inst, false), + .unwrap_errunion_payload_ptr => try self.airErrUnionPayload(inst, true), + .unwrap_errunion_err => try self.airErrUnionErr(inst, false), + .unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true), + + .unreach => self.airUnreach(inst), .dbg_stmt => blk: { // TODO: implement debug info break :blk null; }, else => |tag| return self.todo("implement AIR instruction: {}", .{tag}), + // zig fmt: on }; if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val); } @@ -986,6 +1040,52 @@ pub const FuncGen = struct { return null; } + fn airVarPtr(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 variable = self.air.variables[ty_pl.payload]; + const decl_llvm_value = self.dg.resolveGlobalDecl(variable.owner_decl); + return decl_llvm_value; + } + + fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + return self.builder.buildExtractValue(operand, index, ""); + } + + fn airSliceElemVal( + self: *FuncGen, + inst: Air.Inst.Index, + operand_is_ptr: bool, + ) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + + const base_ptr = if (!operand_is_ptr) lhs else ptr: { + const index_type = self.context.intType(32); + const indices: [2]*const llvm.Value = .{ + index_type.constNull(), + index_type.constInt(0, .False), + }; + const ptr_field_ptr = self.builder.buildInBoundsGEP(lhs, &indices, 2, ""); + break :ptr self.builder.buildLoad(ptr_field_ptr, ""); + }; + + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, 1, ""); + return self.builder.buildLoad(ptr, ""); + } + fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1152,6 +1252,31 @@ pub const FuncGen = struct { return self.builder.buildNot((try self.airIsNonNull(inst, operand_is_ptr)).?, ""); } + fn airIsErr( + self: *FuncGen, + inst: Air.Inst.Index, + invert_logic: bool, + operand_is_ptr: bool, + ) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const err_union_ty = self.air.typeOf(un_op); + const payload_ty = err_union_ty.errorUnionPayload(); + + if (!payload_ty.hasCodeGenBits()) { + const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand; + const op: llvm.IntPredicate = if (invert_logic) .EQ else .NE; + const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror)); + const zero = err_set_ty.constNull(); + return self.builder.buildICmp(op, loaded, zero, ""); + } + + return self.todo("implement 'airIsErr' for error unions with nonzero payload", .{}); + } + fn airOptionalPayload( self: *FuncGen, inst: Air.Inst.Index, @@ -1177,6 +1302,40 @@ pub const FuncGen = struct { } } + fn airErrUnionPayload( + self: *FuncGen, + inst: Air.Inst.Index, + operand_is_ptr: bool, + ) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const err_union_ty = self.air.typeOf(ty_op.operand); + const payload_ty = err_union_ty.errorUnionPayload(); + + if (!payload_ty.hasCodeGenBits()) { + return null; + } + + _ = operand; + _ = operand_is_ptr; + return self.todo("implement 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)}); + } + + fn airErrUnionErr( + self: *FuncGen, + inst: Air.Inst.Index, + operand_is_ptr: bool, + ) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + _ = operand_is_ptr; + return self.todo("implement 'airErrUnionErr'", .{}); + } + fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 3d17172cfd..3b76361030 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -42,6 +42,9 @@ pub const Context = opaque { Packed: Bool, ) *const Type; + const structCreateNamed = LLVMStructCreateNamed; + extern fn LLVMStructCreateNamed(C: *const Context, Name: [*:0]const u8) *const Type; + pub const constString = LLVMConstStringInContext; extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) *const Value; @@ -76,6 +79,9 @@ pub const Value = opaque { pub const typeOf = LLVMTypeOf; extern fn LLVMTypeOf(Val: *const Value) *const Type; + + pub const setGlobalConstant = LLVMSetGlobalConstant; + extern fn LLVMSetGlobalConstant(GlobalVar: *const Value, IsConstant: Bool) void; }; pub const Type = opaque { @@ -99,6 +105,14 @@ pub const Type = opaque { pub const arrayType = LLVMArrayType; extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type; + + pub const structSetBody = LLVMStructSetBody; + extern fn LLVMStructSetBody( + StructTy: *const Type, + ElementTypes: [*]*const Type, + ElementCount: c_uint, + Packed: Bool, + ) void; }; pub const Module = opaque { @@ -257,7 +271,13 @@ pub const Builder = opaque { extern fn LLVMBuildBitCast(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value; pub const buildInBoundsGEP = LLVMBuildInBoundsGEP; - extern fn LLVMBuildInBoundsGEP(B: *const Builder, Pointer: *const Value, Indices: [*]*const Value, NumIndices: c_uint, Name: [*:0]const u8) *const Value; + extern fn LLVMBuildInBoundsGEP( + B: *const Builder, + Pointer: *const Value, + Indices: [*]const *const Value, + NumIndices: c_uint, + Name: [*:0]const u8, + ) *const Value; pub const buildICmp = LLVMBuildICmp; extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; diff --git a/src/type.zig b/src/type.zig index bbb3ee667a..731c69b806 100644 --- a/src/type.zig +++ b/src/type.zig @@ -915,7 +915,7 @@ pub const Type = extern union { } try writer.writeAll(") callconv(."); try writer.writeAll(@tagName(payload.cc)); - try writer.writeAll(")"); + try writer.writeAll(") "); ty = payload.return_type; continue; },