From b87105c9217e41cb39c09d9a569ac8bc006c2eef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 24 Jul 2021 23:54:20 -0700 Subject: stage2 llvm backend: implement assembly and ptrtoint These AIR instructions are the next blockers for `zig test` to work for this backend. After this commit, the "hello world" x86_64 test case passes for the LLVM backend as well. --- src/codegen/llvm.zig | 145 ++++++++++++++++++++++++++++++++++++++++-- src/codegen/llvm/bindings.zig | 60 +++++++++++++---- 2 files changed, 188 insertions(+), 17 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 92fa32dacf..ffa55fa034 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -9,6 +9,7 @@ const math = std.math; const Module = @import("../Module.zig"); const TypedValue = @import("../TypedValue.zig"); +const Zir = @import("../Zir.zig"); const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); @@ -488,9 +489,9 @@ pub const DeclGen = struct { llvm_param[i] = try self.getLLVMType(fn_param); } - const fn_type = llvm.Type.functionType( + const fn_type = llvm.functionType( try self.getLLVMType(return_type), - if (fn_param_len == 0) null else llvm_param.ptr, + llvm_param.ptr, @intCast(c_uint, fn_param_len), .False, ); @@ -750,7 +751,7 @@ pub const FuncGen = struct { fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void { const air_tags = self.air.instructions.items(.tag); for (body) |inst| { - const opt_value = switch (air_tags[inst]) { + const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { .add => try self.airAdd(inst), .sub => try self.airSub(inst), @@ -775,6 +776,7 @@ pub const FuncGen = struct { .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), @@ -783,6 +785,7 @@ pub const FuncGen = struct { .unreach => self.airUnreach(inst), .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), + .assembly => try self.airAssembly(inst), .dbg_stmt => blk: { // TODO: implement debug info break :blk null; @@ -821,7 +824,7 @@ pub const FuncGen = struct { // Do we need that? const call = self.builder.buildCall( llvm_fn, - if (args.len == 0) null else llvm_param_vals.ptr, + llvm_param_vals.ptr, @intCast(c_uint, args.len), "", ); @@ -986,6 +989,128 @@ pub const FuncGen = struct { return null; } + fn airAssembly(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + // Eventually, the Zig compiler needs to be reworked to have inline assembly go + // through the same parsing code regardless of backend, and have LLVM-flavored + // inline assembly be *output* from that assembler. + // We don't have such an assembler implemented yet though. For now, this + // implementation feeds the inline assembly code directly to LLVM, same + // as stage1. + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const air_asm = self.air.extraData(Air.Asm, ty_pl.payload); + const zir = self.dg.decl.namespace.file_scope.zir; + const extended = zir.instructions.items(.data)[air_asm.data.zir_index].extended; + const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); + const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); + const outputs_len = @truncate(u5, extended.small); + const args_len = @truncate(u5, extended.small >> 5); + const clobbers_len = @truncate(u5, extended.small >> 10); + const is_volatile = @truncate(u1, extended.small >> 15) != 0; + const outputs = @bitCast([]const Air.Inst.Ref, self.air.extra[air_asm.end..][0..outputs_len]); + const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_asm.end + outputs.len ..][0..args_len]); + if (outputs_len > 1) { + return self.todo("implement llvm codegen for asm with more than 1 output", .{}); + } + + var extra_i: usize = zir_extra.end; + const output_constraint: ?[]const u8 = out: { + var i: usize = 0; + while (i < outputs_len) : (i += 1) { + const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); + extra_i = output.end; + break :out zir.nullTerminatedString(output.data.constraint); + } + break :out null; + }; + + if (!is_volatile and self.liveness.isUnused(inst)) { + return null; + } + + var llvm_constraints: std.ArrayListUnmanaged(u8) = .{}; + defer llvm_constraints.deinit(self.gpa); + + var arena_allocator = std.heap.ArenaAllocator.init(self.gpa); + defer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + const llvm_params_len = args.len + @boolToInt(output_constraint != null); + const llvm_param_types = try arena.alloc(*const llvm.Type, llvm_params_len); + const llvm_param_values = try arena.alloc(*const llvm.Value, llvm_params_len); + + var llvm_param_i: usize = 0; + var total_i: usize = 0; + + if (output_constraint) |constraint| { + try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1); + if (total_i != 0) { + llvm_constraints.appendAssumeCapacity(','); + } + llvm_constraints.appendSliceAssumeCapacity(constraint); + + total_i += 1; + } + + for (args) |arg| { + const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); + extra_i = input.end; + const constraint = zir.nullTerminatedString(input.data.constraint); + const arg_llvm_value = try self.resolveInst(arg); + + llvm_param_values[llvm_param_i] = arg_llvm_value; + llvm_param_types[llvm_param_i] = arg_llvm_value.typeOf(); + + try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1); + if (total_i != 0) { + llvm_constraints.appendAssumeCapacity(','); + } + llvm_constraints.appendSliceAssumeCapacity(constraint); + + llvm_param_i += 1; + total_i += 1; + } + + const clobbers = zir.extra[extra_i..][0..clobbers_len]; + for (clobbers) |clobber_index| { + const clobber = zir.nullTerminatedString(clobber_index); + try llvm_constraints.ensureUnusedCapacity(self.gpa, clobber.len + 4); + if (total_i != 0) { + llvm_constraints.appendAssumeCapacity(','); + } + llvm_constraints.appendSliceAssumeCapacity("~{"); + llvm_constraints.appendSliceAssumeCapacity(clobber); + llvm_constraints.appendSliceAssumeCapacity("}"); + + total_i += 1; + } + + const ret_ty = self.air.typeOfIndex(inst); + const ret_llvm_ty = try self.dg.getLLVMType(ret_ty); + const llvm_fn_ty = llvm.functionType( + ret_llvm_ty, + llvm_param_types.ptr, + @intCast(c_uint, llvm_param_types.len), + .False, + ); + const asm_fn = llvm.getInlineAsm( + llvm_fn_ty, + asm_source.ptr, + asm_source.len, + llvm_constraints.items.ptr, + llvm_constraints.items.len, + llvm.Bool.fromBool(is_volatile), + .False, + .ATT, + ); + return self.builder.buildCall( + asm_fn, + llvm_param_values.ptr, + @intCast(c_uint, llvm_param_values.len), + "", + ); + } + fn airIsNonNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1087,6 +1212,16 @@ pub const FuncGen = struct { return self.builder.buildIntCast2(operand, try self.dg.getLLVMType(inst_ty), llvm.Bool.fromBool(signed), ""); } + fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*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 dest_llvm_ty = try self.dg.getLLVMType(self.air.typeOfIndex(inst)); + return self.builder.buildPtrToInt(operand, dest_llvm_ty, ""); + } + fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1168,7 +1303,7 @@ pub const FuncGen = struct { fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { _ = inst; const llvn_fn = self.getIntrinsic("llvm.debugtrap"); - _ = self.builder.buildCall(llvn_fn, null, 0, ""); + _ = self.builder.buildCall(llvn_fn, undefined, 0, ""); return null; } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index f45ea8f979..59c5b3dfe3 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -68,12 +68,12 @@ pub const Value = opaque { pub const getNextInstruction = LLVMGetNextInstruction; extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value; + + pub const typeOf = LLVMTypeOf; + extern fn LLVMTypeOf(Val: *const Value) *const Type; }; pub const Type = opaque { - pub const functionType = LLVMFunctionType; - extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: Bool) *const Type; - pub const constNull = LLVMConstNull; extern fn LLVMConstNull(Ty: *const Type) *const Value; @@ -152,6 +152,28 @@ extern fn LLVMGetParam(Fn: *const Value, Index: c_uint) *const Value; pub const getEnumAttributeKindForName = LLVMGetEnumAttributeKindForName; extern fn LLVMGetEnumAttributeKindForName(Name: [*]const u8, SLen: usize) c_uint; +pub const getInlineAsm = LLVMGetInlineAsm; +extern fn LLVMGetInlineAsm( + Ty: *const Type, + AsmString: [*]const u8, + AsmStringSize: usize, + Constraints: [*]const u8, + ConstraintsSize: usize, + HasSideEffects: Bool, + IsAlignStack: Bool, + Dialect: InlineAsmDialect, +) *const Value; + +pub const functionType = LLVMFunctionType; +extern fn LLVMFunctionType( + ReturnType: *const Type, + ParamTypes: [*]*const Type, + ParamCount: c_uint, + IsVarArg: Bool, +) *const Type; + +pub const InlineAsmDialect = enum(c_uint) { ATT, Intel }; + pub const Attribute = opaque {}; pub const Builder = opaque { @@ -159,7 +181,11 @@ pub const Builder = opaque { extern fn LLVMDisposeBuilder(Builder: *const Builder) void; pub const positionBuilder = LLVMPositionBuilder; - extern fn LLVMPositionBuilder(Builder: *const Builder, Block: *const BasicBlock, Instr: *const Value) void; + extern fn LLVMPositionBuilder( + Builder: *const Builder, + Block: *const BasicBlock, + Instr: *const Value, + ) void; pub const positionBuilderAtEnd = LLVMPositionBuilderAtEnd; extern fn LLVMPositionBuilderAtEnd(Builder: *const Builder, Block: *const BasicBlock) void; @@ -168,10 +194,23 @@ pub const Builder = opaque { extern fn LLVMGetInsertBlock(Builder: *const Builder) *const BasicBlock; pub const buildCall = LLVMBuildCall; - extern fn LLVMBuildCall(*const Builder, Fn: *const Value, Args: ?[*]*const Value, NumArgs: c_uint, Name: [*:0]const u8) *const Value; + extern fn LLVMBuildCall( + *const Builder, + Fn: *const Value, + Args: [*]*const Value, + NumArgs: c_uint, + Name: [*:0]const u8, + ) *const Value; pub const buildCall2 = LLVMBuildCall2; - extern fn LLVMBuildCall2(*const Builder, *const Type, Fn: *const Value, Args: [*]*const Value, NumArgs: c_uint, Name: [*:0]const u8) *const Value; + extern fn LLVMBuildCall2( + *const Builder, + *const Type, + Fn: *const Value, + Args: [*]*const Value, + NumArgs: c_uint, + Name: [*:0]const u8, + ) *const Value; pub const buildRetVoid = LLVMBuildRetVoid; extern fn LLVMBuildRetVoid(*const Builder) *const Value; @@ -229,6 +268,9 @@ pub const Builder = opaque { pub const buildExtractValue = LLVMBuildExtractValue; extern fn LLVMBuildExtractValue(*const Builder, AggVal: *const Value, Index: c_uint, Name: [*:0]const u8) *const Value; + + pub const buildPtrToInt = LLVMBuildPtrToInt; + extern fn LLVMBuildPtrToInt(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value; }; pub const IntPredicate = enum(c_int) { @@ -534,12 +576,6 @@ pub const ObjectFormatType = enum(c_int) { XCOFF, }; -pub const GetHostCPUName = LLVMGetHostCPUName; -extern fn LLVMGetHostCPUName() ?[*:0]u8; - -pub const GetNativeFeatures = ZigLLVMGetNativeFeatures; -extern fn ZigLLVMGetNativeFeatures() ?[*:0]u8; - pub const WriteArchive = ZigLLVMWriteArchive; extern fn ZigLLVMWriteArchive( archive_name: [*:0]const u8, -- cgit v1.2.3