From 55eea3b045c86c78eb8d9cc862122d260352a631 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 14 Oct 2021 21:17:30 -0700 Subject: stage2: implement `@minimum` and `@maximum`, including vectors * std.os: take advantage of `@minimum`. It's probably time to deprecate `std.min` and `std.max`. * New AIR instructions: min and max * Introduce SIMD vector support to stage2 * Add `@Type` support for vectors * Sema: add `checkSimdBinOp` which can be re-used for other arithmatic operators that want to support vectors. * Implement coercion from vectors to arrays. - In backends this is handled with bitcast for vector to array, however maybe we want to reduce the amount of branching by introducing an explicit AIR instruction for it in the future. * LLVM backend: implement lowering vector types * Sema: Implement `slice.ptr` at comptime * Value: improve `numberMin` and `numberMax` to support floats in addition to integers, and make them behave properly in the presence of NaN. --- src/codegen/c.zig | 28 +++++++++++++++ src/codegen/llvm.zig | 84 +++++++++++++++++++++++++++++++++++++------ src/codegen/llvm/bindings.zig | 29 +++++++++++++++ 3 files changed, 131 insertions(+), 10 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 466322c0a3..ad98dc87c1 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -989,6 +989,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .mul_sat => try airSatOp(f, inst, "muls_"), .shl_sat => try airSatOp(f, inst, "shls_"), + .min => try airMinMax(f, inst, "<"), + .max => try airMinMax(f, inst, ">"), + .cmp_eq => try airBinOp(f, inst, " == "), .cmp_gt => try airBinOp(f, inst, " > "), .cmp_gte => try airBinOp(f, inst, " >= "), @@ -1595,6 +1598,31 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue return local; } +fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const lhs = try f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); + + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + + // (lhs <> rhs) ? lhs : rhs + try writer.writeAll(" = ("); + try f.writeCValue(writer, lhs); + try writer.print("{s}", .{operator}); + try f.writeCValue(writer, rhs); + try writer.writeAll(") "); + try f.writeCValue(writer, lhs); + try writer.writeAll(" : "); + try f.writeCValue(writer, rhs); + try writer.writeAll(";\n"); + + return local; +} + fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Call, pl_op.payload); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d5b8ee851a..1ebca942d5 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -754,7 +754,7 @@ pub const DeclGen = struct { const fields: [2]*const llvm.Type = .{ try dg.llvmType(ptr_type), - try dg.llvmType(Type.initTag(.usize)), + try dg.llvmType(Type.usize), }; return dg.context.structType(&fields, fields.len, .False); } else { @@ -780,10 +780,14 @@ pub const DeclGen = struct { return llvm_struct_ty; }, .Array => { - const elem_type = try dg.llvmType(t.elemType()); + const elem_type = try dg.llvmType(t.childType()); const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null); return elem_type.arrayType(@intCast(c_uint, total_len)); }, + .Vector => { + const elem_type = try dg.llvmType(t.childType()); + return elem_type.vectorType(@intCast(c_uint, t.arrayLen())); + }, .Optional => { var buf: Type.Payload.ElemType = undefined; const child_type = t.optionalChild(&buf); @@ -966,7 +970,6 @@ pub const DeclGen = struct { .Frame, .AnyFrame, - .Vector, => return dg.todo("implement llvmType for type '{}'", .{t}), } } @@ -1062,7 +1065,7 @@ pub const DeclGen = struct { return self.context.constStruct(&fields, fields.len, .False); }, .int_u64 => { - const llvm_usize = try self.llvmType(Type.initTag(.usize)); + const llvm_usize = try self.llvmType(Type.usize); const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False); return llvm_int.constIntToPtr(try self.llvmType(tv.ty)); }, @@ -1295,7 +1298,7 @@ pub const DeclGen = struct { .val = tv.val, }), try self.genTypedValue(.{ - .ty = Type.initTag(.usize), + .ty = Type.usize, .val = Value.initPayload(&slice_len.base), }), }; @@ -1470,6 +1473,8 @@ pub const FuncGen = struct { .shl => try self.airShl(inst), .shl_sat => try self.airShlSat(inst), .shl_exact => try self.airShlExact(inst), + .min => try self.airMin(inst), + .max => try self.airMax(inst), .bit_and, .bool_and => try self.airAnd(inst), .bit_or, .bool_or => try self.airOr(inst), @@ -2356,6 +2361,32 @@ pub const FuncGen = struct { return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{}); } + fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*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 scalar_ty = self.air.typeOfIndex(inst).scalarType(); + + if (scalar_ty.isAnyFloat()) return self.builder.buildMinNum(lhs, rhs, ""); + if (scalar_ty.isSignedInt()) return self.builder.buildSMin(lhs, rhs, ""); + return self.builder.buildUMin(lhs, rhs, ""); + } + + fn airMax(self: *FuncGen, inst: Air.Inst.Index) !?*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 scalar_ty = self.air.typeOfIndex(inst).scalarType(); + + if (scalar_ty.isAnyFloat()) return self.builder.buildMaxNum(lhs, rhs, ""); + if (scalar_ty.isSignedInt()) return self.builder.buildSMax(lhs, rhs, ""); + return self.builder.buildUMax(lhs, rhs, ""); + } + fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -2705,15 +2736,48 @@ pub const FuncGen = struct { } fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + 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 operand_ty = self.air.typeOf(ty_op.operand); const inst_ty = self.air.typeOfIndex(inst); - const dest_type = try self.dg.llvmType(inst_ty); + const llvm_dest_ty = try self.dg.llvmType(inst_ty); + + // TODO look into pulling this logic out into a different AIR instruction than bitcast + if (operand_ty.zigTypeTag() == .Vector and inst_ty.zigTypeTag() == .Array) { + const target = self.dg.module.getTarget(); + const elem_ty = operand_ty.childType(); + if (!isByRef(inst_ty)) { + return self.dg.todo("implement bitcast vector to non-ref array", .{}); + } + const array_ptr = self.buildAlloca(llvm_dest_ty); + const bitcast_ok = elem_ty.bitSize(target) == elem_ty.abiSize(target) * 8; + if (bitcast_ok) { + const llvm_vector_ty = try self.dg.llvmType(operand_ty); + const casted_ptr = self.builder.buildBitCast(array_ptr, llvm_vector_ty.pointerType(0), ""); + _ = self.builder.buildStore(operand, casted_ptr); + } else { + // If the ABI size of the element type is not evenly divisible by size in bits; + // a simple bitcast will not work, and we fall back to extractelement. + const llvm_usize = try self.dg.llvmType(Type.usize); + const llvm_u32 = self.context.intType(32); + const zero = llvm_usize.constNull(); + const vector_len = operand_ty.arrayLen(); + var i: u64 = 0; + while (i < vector_len) : (i += 1) { + const index_usize = llvm_usize.constInt(i, .False); + const index_u32 = llvm_u32.constInt(i, .False); + const indexes: [2]*const llvm.Value = .{ zero, index_usize }; + const elem_ptr = self.builder.buildInBoundsGEP(array_ptr, &indexes, indexes.len, ""); + const elem = self.builder.buildExtractElement(operand, index_u32, ""); + _ = self.builder.buildStore(elem, elem_ptr); + } + } + return array_ptr; + } - return self.builder.buildBitCast(operand, dest_type, ""); + return self.builder.buildBitCast(operand, llvm_dest_ty, ""); } fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -2906,7 +2970,7 @@ pub const FuncGen = struct { } // It's a pointer but we need to treat it as an int. - const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize)); + const usize_llvm_ty = try self.dg.llvmType(Type.usize); const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), ""); const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, ""); const uncasted_result = self.builder.buildAtomicRmw( diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 7b91d70fbe..5b6824f02a 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -212,6 +212,9 @@ pub const Type = opaque { pub const arrayType = LLVMArrayType; extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type; + pub const vectorType = LLVMVectorType; + extern fn LLVMVectorType(ElementType: *const Type, ElementCount: c_uint) *const Type; + pub const structSetBody = LLVMStructSetBody; extern fn LLVMStructSetBody( StructTy: *const Type, @@ -553,6 +556,14 @@ pub const Builder = opaque { Name: [*:0]const u8, ) *const Value; + pub const buildExtractElement = LLVMBuildExtractElement; + extern fn LLVMBuildExtractElement( + *const Builder, + VecVal: *const Value, + Index: *const Value, + Name: [*:0]const u8, + ) *const Value; + pub const buildPtrToInt = LLVMBuildPtrToInt; extern fn LLVMBuildPtrToInt( *const Builder, @@ -700,6 +711,24 @@ pub const Builder = opaque { Size: *const Value, is_volatile: bool, ) *const Value; + + pub const buildMaxNum = ZigLLVMBuildMaxNum; + extern fn ZigLLVMBuildMaxNum(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value; + + pub const buildMinNum = ZigLLVMBuildMinNum; + extern fn ZigLLVMBuildMinNum(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value; + + pub const buildUMax = ZigLLVMBuildUMax; + extern fn ZigLLVMBuildUMax(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value; + + pub const buildUMin = ZigLLVMBuildUMin; + extern fn ZigLLVMBuildUMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value; + + pub const buildSMax = ZigLLVMBuildSMax; + extern fn ZigLLVMBuildSMax(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value; + + pub const buildSMin = ZigLLVMBuildSMin; + extern fn ZigLLVMBuildSMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value; }; pub const IntPredicate = enum(c_uint) { -- cgit v1.2.3