Commit b87105c921

Andrew Kelley <andrew@ziglang.org>
2021-07-25 08:54:20
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.
1 parent 653c851
Changed files (2)
src
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,
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;
     }