From 55eea3b045c86c78eb8d9cc862122d260352a631 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 14 Oct 2021 21:17:30 -0700 Subject: [PATCH] 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. --- lib/std/os.zig | 50 +++--- src/Air.zig | 14 ++ src/Liveness.zig | 2 + src/Sema.zig | 246 ++++++++++++++++++++++++++---- src/Zir.zig | 6 +- src/codegen.zig | 18 +++ src/codegen/c.zig | 28 ++++ src/codegen/llvm.zig | 84 ++++++++-- src/codegen/llvm/bindings.zig | 29 ++++ src/print_air.zig | 2 + src/type.zig | 15 ++ src/value.zig | 91 +++++------ test/behavior.zig | 2 +- test/behavior/maximum_minimum.zig | 8 +- 14 files changed, 470 insertions(+), 125 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index eda246d2a3e9f0fa5d61f5fb72f0f6c69cdda648..7bbc13d2ea06420b46c909c226ee093410161b65 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1,18 +1,18 @@ -// This file contains thin wrappers around OS-specific APIs, with these -// specific goals in mind: -// * Convert "errno"-style error codes into Zig errors. -// * When null-terminated byte buffers are required, provide APIs which accept -// slices as well as APIs which accept null-terminated byte buffers. Same goes -// for UTF-16LE encoding. -// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide -// cross platform abstracting. -// * When there exists a corresponding libc function and linking libc, the libc -// implementation is used. Exceptions are made for known buggy areas of libc. -// On Linux libc can be side-stepped by using `std.os.linux` directly. -// * For Windows, this file represents the API that libc would provide for -// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`. -// Note: The Zig standard library does not support POSIX thread cancellation, and -// in general EINTR is handled by trying again. +//! This file contains thin wrappers around OS-specific APIs, with these +//! specific goals in mind: +//! * Convert "errno"-style error codes into Zig errors. +//! * When null-terminated byte buffers are required, provide APIs which accept +//! slices as well as APIs which accept null-terminated byte buffers. Same goes +//! for UTF-16LE encoding. +//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide +//! cross platform abstracting. +//! * When there exists a corresponding libc function and linking libc, the libc +//! implementation is used. Exceptions are made for known buggy areas of libc. +//! On Linux libc can be side-stepped by using `std.os.linux` directly. +//! * For Windows, this file represents the API that libc would provide for +//! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`. +//! Note: The Zig standard library does not support POSIX thread cancellation, and +//! in general EINTR is handled by trying again. const root = @import("root"); const std = @import("std.zig"); @@ -492,7 +492,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; - const adjusted_len = math.min(max_count, buf.len); + const adjusted_len = @minimum(max_count, buf.len); while (true) { const rc = system.read(fd, buf.ptr, adjusted_len); @@ -621,7 +621,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; - const adjusted_len = math.min(max_count, buf.len); + const adjusted_len = @minimum(max_count, buf.len); const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc) system.pread64 @@ -873,7 +873,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; - const adjusted_len = math.min(max_count, bytes.len); + const adjusted_len = @minimum(max_count, bytes.len); while (true) { const rc = system.write(fd, bytes.ptr, adjusted_len); @@ -1029,7 +1029,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; - const adjusted_len = math.min(max_count, bytes.len); + const adjusted_len = @minimum(max_count, bytes.len); const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc) system.pwrite64 @@ -5439,7 +5439,7 @@ pub fn sendfile( } // Here we match BSD behavior, making a zero count value send as many bytes as possible. - const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count)); + const adjusted_count = if (in_len == 0) max_count else @minimum(in_len, @as(size_t, max_count)); const sendfile_sym = if (builtin.link_libc) system.sendfile64 @@ -5522,7 +5522,7 @@ pub fn sendfile( hdtr = &hdtr_data; } - const adjusted_count = math.min(in_len, max_count); + const adjusted_count = @minimum(in_len, max_count); while (true) { var sbytes: off_t = undefined; @@ -5601,7 +5601,7 @@ pub fn sendfile( hdtr = &hdtr_data; } - const adjusted_count = math.min(in_len, @as(u63, max_count)); + const adjusted_count = @minimum(in_len, @as(u63, max_count)); while (true) { var sbytes: off_t = adjusted_count; @@ -5655,7 +5655,7 @@ pub fn sendfile( rw: { var buf: [8 * 4096]u8 = undefined; // Here we match BSD behavior, making a zero count value send as many bytes as possible. - const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len); + const adjusted_count = if (in_len == 0) buf.len else @minimum(buf.len, in_len); const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); if (amt_read == 0) { if (in_len == 0) { @@ -5756,7 +5756,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len } var buf: [8 * 4096]u8 = undefined; - const adjusted_count = math.min(buf.len, len); + const adjusted_count = @minimum(buf.len, len); const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in); // TODO without @as the line below fails to compile for wasm32-wasi: // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize' @@ -5919,7 +5919,7 @@ pub fn dn_expand( const end = msg.ptr + msg.len; if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket; var dest = exp_dn.ptr; - const dend = dest + std.math.min(exp_dn.len, 254); + const dend = dest + @minimum(exp_dn.len, 254); // detect reference loop using an iteration counter var i: usize = 0; while (i < msg.len) : (i += 2) { diff --git a/src/Air.zig b/src/Air.zig index 9ad8bc9ae486ad94c6f5f505c345ac8a17c20c00..86e16487bb80f28b25d4c0ddc0672d717bfee59d 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -107,6 +107,18 @@ pub const Inst = struct { /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs. /// Uses the `bin_op` field. ptr_sub, + /// Given two operands which can be floats, integers, or vectors, returns the + /// greater of the operands. For vectors it operates element-wise. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + max, + /// Given two operands which can be floats, integers, or vectors, returns the + /// lesser of the operands. For vectors it operates element-wise. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + min, /// Allocates stack local memory. /// Uses the `ty` field. alloc, @@ -640,6 +652,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .shl, .shl_exact, .shl_sat, + .min, + .max, => return air.typeOf(datas[inst].bin_op.lhs), .cmp_lt, diff --git a/src/Liveness.zig b/src/Liveness.zig index b49147606cfc04d47ce4fd4ca0fac78197f524c1..6f7c938f4c0e1f0b58e5d0c49d359d242e6a8f04 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -264,6 +264,8 @@ fn analyzeInst( .atomic_store_release, .atomic_store_seq_cst, .set_union_tag, + .min, + .max, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 6ec102d26d0ec8e215cb63323554d11d77aac551..b6a35d05ef01cf47f607f84f5c257977ad0b1f25 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -614,8 +614,6 @@ pub fn analyzeBody( .builtin_call => try sema.zirBuiltinCall(block, inst), .field_ptr_type => try sema.zirFieldPtrType(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), - .maximum => try sema.zirMaximum(block, inst), - .minimum => try sema.zirMinimum(block, inst), .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), .@"await" => try sema.zirAwait(block, inst, false), @@ -654,6 +652,9 @@ pub fn analyzeBody( .subwrap => try sema.zirArithmetic(block, inst, .subwrap), .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat), + .maximum => try sema.zirMinMax(block, inst, .max), + .minimum => try sema.zirMinMax(block, inst, .min), + .shl => try sema.zirShl(block, inst, .shl), .shl_exact => try sema.zirShl(block, inst, .shl_exact), .shl_sat => try sema.zirShl(block, inst, .shl_sat), @@ -9018,6 +9019,12 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .Void => return Air.Inst.Ref.void_type, .Bool => return Air.Inst.Ref.bool_type, .NoReturn => return Air.Inst.Ref.noreturn_type, + .ComptimeFloat => return Air.Inst.Ref.comptime_float_type, + .ComptimeInt => return Air.Inst.Ref.comptime_int_type, + .Undefined => return Air.Inst.Ref.undefined_type, + .Null => return Air.Inst.Ref.null_type, + .AnyFrame => return Air.Inst.Ref.anyframe_type, + .EnumLiteral => return Air.Inst.Ref.enum_literal_type, .Int => { const struct_val = union_val.val.castTag(.@"struct").?.data; // TODO use reflection instead of magic numbers here @@ -9032,14 +9039,23 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I }; return sema.addType(ty); }, + .Vector => { + const struct_val = union_val.val.castTag(.@"struct").?.data; + // TODO use reflection instead of magic numbers here + const len_val = struct_val[0]; + const child_val = struct_val[1]; + + const len = len_val.toUnsignedInt(); + var buffer: Value.ToTypeBuffer = undefined; + const child_ty = child_val.toType(&buffer); + + const ty = try Type.vector(sema.arena, len, child_ty); + return sema.addType(ty); + }, .Float => return sema.fail(block, src, "TODO: Sema.zirReify for Float", .{}), .Pointer => return sema.fail(block, src, "TODO: Sema.zirReify for Pointer", .{}), .Array => return sema.fail(block, src, "TODO: Sema.zirReify for Array", .{}), .Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}), - .ComptimeFloat => return Air.Inst.Ref.comptime_float_type, - .ComptimeInt => return Air.Inst.Ref.comptime_int_type, - .Undefined => return Air.Inst.Ref.undefined_type, - .Null => return Air.Inst.Ref.null_type, .Optional => return sema.fail(block, src, "TODO: Sema.zirReify for Optional", .{}), .ErrorUnion => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorUnion", .{}), .ErrorSet => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorSet", .{}), @@ -9049,9 +9065,6 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .BoundFn => @panic("TODO delete BoundFn from the language"), .Opaque => return sema.fail(block, src, "TODO: Sema.zirReify for Opaque", .{}), .Frame => return sema.fail(block, src, "TODO: Sema.zirReify for Frame", .{}), - .AnyFrame => return Air.Inst.Ref.anyframe_type, - .Vector => return sema.fail(block, src, "TODO: Sema.zirReify for Vector", .{}), - .EnumLiteral => return Air.Inst.Ref.enum_literal_type, } } @@ -9379,9 +9392,23 @@ fn checkFloatType( ) CompileError!void { switch (ty.zigTypeTag()) { .ComptimeFloat, .Float => {}, - else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ - ty, - }), + else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty}), + } +} + +fn checkNumericType( + sema: *Sema, + block: *Block, + ty_src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .ComptimeFloat, .Float, .ComptimeInt, .Int => {}, + .Vector => switch (ty.childType().zigTypeTag()) { + .ComptimeFloat, .Float, .ComptimeInt, .Int => {}, + else => |t| return sema.fail(block, ty_src, "expected number, found '{}'", .{t}), + }, + else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty}), } } @@ -9474,6 +9501,82 @@ fn checkComptimeVarStore( } } +const SimdBinOp = struct { + len: ?u64, + /// Coerced to `result_ty`. + lhs: Air.Inst.Ref, + /// Coerced to `result_ty`. + rhs: Air.Inst.Ref, + lhs_val: ?Value, + rhs_val: ?Value, + /// Only different than `scalar_ty` when it is a vector operation. + result_ty: Type, + scalar_ty: Type, +}; + +fn checkSimdBinOp( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + uncasted_lhs: Air.Inst.Ref, + uncasted_rhs: Air.Inst.Ref, + lhs_src: LazySrcLoc, + rhs_src: LazySrcLoc, +) CompileError!SimdBinOp { + const lhs_ty = sema.typeOf(uncasted_lhs); + const rhs_ty = sema.typeOf(uncasted_rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + + var vec_len: ?u64 = null; + if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) { + const lhs_len = lhs_ty.arrayLen(); + const rhs_len = rhs_ty.arrayLen(); + if (lhs_len != rhs_len) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "vector length mismatch", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, lhs_src, msg, "length {d} here", .{lhs_len}); + try sema.errNote(block, rhs_src, msg, "length {d} here", .{rhs_len}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + vec_len = lhs_len; + } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "mixed scalar and vector operands: {} and {}", .{ + lhs_ty, rhs_ty, + }); + errdefer msg.destroy(sema.gpa); + if (lhs_zig_ty_tag == .Vector) { + try sema.errNote(block, lhs_src, msg, "vector here", .{}); + try sema.errNote(block, rhs_src, msg, "scalar here", .{}); + } else { + try sema.errNote(block, lhs_src, msg, "scalar here", .{}); + try sema.errNote(block, rhs_src, msg, "vector here", .{}); + } + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + const result_ty = try sema.resolvePeerTypes(block, src, &.{ uncasted_lhs, uncasted_rhs }, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); + const lhs = try sema.coerce(block, result_ty, uncasted_lhs, lhs_src); + const rhs = try sema.coerce(block, result_ty, uncasted_rhs, rhs_src); + + return SimdBinOp{ + .len = vec_len, + .lhs = lhs, + .rhs = rhs, + .lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs), + .rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs), + .result_ty = result_ty, + .scalar_ty = result_ty.scalarType(), + }; +} + fn resolveExportOptions( sema: *Sema, block: *Block, @@ -9744,8 +9847,8 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target), .Or => try stored_val.bitwiseOr (operand_val, sema.arena), .Xor => try stored_val.bitwiseXor (operand_val, sema.arena), - .Max => try stored_val.numberMax (operand_val, sema.arena), - .Min => try stored_val.numberMin (operand_val, sema.arena), + .Max => try stored_val.numberMax (operand_val), + .Min => try stored_val.numberMin (operand_val), // zig fmt: on }; try sema.storePtrVal(block, src, ptr_val, new_val, operand_ty); @@ -9826,10 +9929,62 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr return sema.fail(block, src, "TODO: Sema.zirFieldParentPtr", .{}); } -fn zirMaximum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirMinMax( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + air_tag: Air.Inst.Tag, +) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirMaximum", .{}); + const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const lhs = sema.resolveInst(extra.lhs); + const rhs = sema.resolveInst(extra.rhs); + try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs)); + try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs)); + const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src); + + // TODO @maximum(max_int, undefined) should return max_int + + const runtime_src = if (simd_op.lhs_val) |lhs_val| rs: { + if (lhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty); + + const rhs_val = simd_op.rhs_val orelse break :rs rhs_src; + + if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty); + + const opFunc = switch (air_tag) { + .min => Value.numberMin, + .max => Value.numberMax, + else => unreachable, + }; + const vec_len = simd_op.len orelse { + const result_val = try opFunc(lhs_val, rhs_val); + return sema.addConstant(simd_op.result_ty, result_val); + }; + var lhs_buf: Value.ElemValueBuffer = undefined; + var rhs_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, vec_len); + for (elems) |*elem, i| { + const lhs_elem_val = lhs_val.elemValueBuffer(i, &lhs_buf); + const rhs_elem_val = rhs_val.elemValueBuffer(i, &rhs_buf); + elem.* = try opFunc(lhs_elem_val, rhs_elem_val); + } + return sema.addConstant( + simd_op.result_ty, + try Value.Tag.array.create(sema.arena, elems), + ); + } else rs: { + if (simd_op.rhs_val) |rhs_val| { + if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty); + } + break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs); } fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -9943,12 +10098,6 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void }); } -fn zirMinimum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirMinimum", .{}); -} - fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); @@ -10453,12 +10602,7 @@ fn fieldVal( const result_ty = object_ty.slicePtrFieldType(buf); if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| { if (val.isUndef()) return sema.addConstUndef(result_ty); - return sema.fail( - block, - field_name_src, - "TODO implement comptime slice ptr", - .{}, - ); + return sema.addConstant(result_ty, val.slicePtr()); } try sema.requireRuntimeBlock(block, src); return block.addTyOp(.slice_ptr, result_ty, object); @@ -11464,6 +11608,10 @@ fn coerce( .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src), else => {}, }, + .Array => switch (inst_ty.zigTypeTag()) { + .Vector => return sema.coerceVectorToArray(block, dest_ty, dest_ty_src, inst, inst_src), + else => {}, + }, else => {}, } @@ -12045,6 +12193,48 @@ fn coerceEnumToUnion( return sema.failWithOwnedErrorMsg(msg); } +fn coerceVectorToArray( + sema: *Sema, + block: *Block, + array_ty: Type, + array_ty_src: LazySrcLoc, + vector: Air.Inst.Ref, + vector_src: LazySrcLoc, +) !Air.Inst.Ref { + const vector_ty = sema.typeOf(vector); + const array_len = array_ty.arrayLen(); + const vector_len = vector_ty.arrayLen(); + if (array_len != vector_len) { + const msg = msg: { + const msg = try sema.errMsg(block, vector_src, "expected {}, found {}", .{ + array_ty, vector_ty, + }); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, array_ty_src, msg, "array has length {d}", .{array_len}); + try sema.errNote(block, vector_src, msg, "vector has length {d}", .{vector_len}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + + const target = sema.mod.getTarget(); + const array_elem_ty = array_ty.childType(); + const vector_elem_ty = vector_ty.childType(); + const in_memory_result = coerceInMemoryAllowed(array_elem_ty, vector_elem_ty, false, target); + if (in_memory_result != .ok) { + // TODO recursive error notes for coerceInMemoryAllowed failure + return sema.fail(block, vector_src, "expected {}, found {}", .{ array_ty, vector_ty }); + } + + if (try sema.resolveMaybeUndefVal(block, vector_src, vector)) |vector_val| { + // These types share the same comptime value representation. + return sema.addConstant(array_ty, vector_val); + } + + try sema.requireRuntimeBlock(block, vector_src); + return block.addTyOp(.bitcast, array_ty, vector); +} + fn analyzeDeclVal( sema: *Sema, block: *Block, diff --git a/src/Zir.zig b/src/Zir.zig index e95500cf38f762f81b477c0fbddc52295ffdcc20..e45aac1a6f9327c532dfa2e8ec672477e1477976 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -906,9 +906,6 @@ pub const Inst = struct { /// Implements the `@fieldParentPtr` builtin. /// Uses the `pl_node` union field with payload `FieldParentPtr`. field_parent_ptr, - /// Implements the `@maximum` builtin. - /// Uses the `pl_node` union field with payload `Bin` - maximum, /// Implements the `@memcpy` builtin. /// Uses the `pl_node` union field with payload `Memcpy`. memcpy, @@ -918,6 +915,9 @@ pub const Inst = struct { /// Implements the `@minimum` builtin. /// Uses the `pl_node` union field with payload `Bin` minimum, + /// Implements the `@maximum` builtin. + /// Uses the `pl_node` union field with payload `Bin` + maximum, /// Implements the `@asyncCall` builtin. /// Uses the `pl_node` union field with payload `AsyncCall`. builtin_async_call, diff --git a/src/codegen.zig b/src/codegen.zig index 49628b93f668aab4f4bdcd887302b4a3a7a67912..8745c1aa587223fe3870f5c5d9de8583a3a2b770 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -839,6 +839,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .mod => try self.airMod(inst), .shl, .shl_exact => try self.airShl(inst), .shl_sat => try self.airShlSat(inst), + .min => try self.airMin(inst), + .max => try self.airMax(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -1299,6 +1301,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airMin(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement min for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + + fn airMax(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement max for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airAdd(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 466322c0a3edc64ef15e9a91399dd7a28bce464c..ad98dc87c114daba420616c7ca712f87679c34e8 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 d5b8ee851a3c783984f9c7ae0a432e6a44ea0201..1ebca942d5efd81f1264713c73342825bae055a0 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); - return self.builder.buildBitCast(operand, dest_type, ""); + // 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, 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 7b91d70fbea1b940873ef9ba9677f3a03e1b8a25..5b6824f02ab4819da9b6390cb3ac0e8b22ccdd54 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) { diff --git a/src/print_air.zig b/src/print_air.zig index a4973a97c1970feca38e6daee383631030bd07e0..861483abace62706df592ddc923b9dda9e9cf271 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -138,6 +138,8 @@ const Writer = struct { .shl_sat, .shr, .set_union_tag, + .min, + .max, => try w.writeBinOp(s, inst), .is_null, diff --git a/src/type.zig b/src/type.zig index e0ca0d426fbdb3eeb14ca9fbfa5865d12a05b903..9ddeb507af7c5fa6332cb5e5bf9ca90ab46d9e11 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2517,6 +2517,14 @@ pub const Type = extern union { }; } + /// For vectors, returns the element type. Otherwise returns self. + pub fn scalarType(ty: Type) Type { + return switch (ty.zigTypeTag()) { + .Vector => ty.childType(), + else => ty, + }; + } + /// Asserts that the type is an optional. /// Resulting `Type` will have inner memory referencing `buf`. pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type { @@ -4017,6 +4025,13 @@ pub const Type = extern union { }); } + pub fn vector(arena: *Allocator, len: u64, elem_type: Type) Allocator.Error!Type { + return Tag.vector.create(arena, .{ + .len = len, + .elem_type = elem_type, + }); + } + pub fn smallestUnsignedBits(max: u64) u16 { if (max == 0) return 0; const base = std.math.log2(max); diff --git a/src/value.zig b/src/value.zig index f5477baf21c3b3a4e8efc2ad849967414d1b6c3c..704e667ac077a3bdbe42d713c27b81524d2d759d 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1626,6 +1626,14 @@ pub const Value = extern union { }; } + pub fn slicePtr(val: Value) Value { + return switch (val.tag()) { + .slice => val.castTag(.slice).?.data.ptr, + .decl_ref, .decl_ref_mut => val, + else => unreachable, + }; + } + pub fn sliceLen(val: Value) u64 { return switch (val.tag()) { .slice => val.castTag(.slice).?.data.len.toUnsignedInt(), @@ -2042,63 +2050,27 @@ pub const Value = extern union { } /// Supports both floats and ints; handles undefined. - pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value { - if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); - - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try arena.alloc( - std.math.big.Limb, - std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - - switch (lhs_bigint.order(rhs_bigint)) { - .lt => result_bigint.copy(rhs_bigint), - .gt, .eq => result_bigint.copy(lhs_bigint), - } - - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(arena, result_limbs); - } else { - return Value.Tag.int_big_negative.create(arena, result_limbs); - } + pub fn numberMax(lhs: Value, rhs: Value) !Value { + if (lhs.isUndef() or rhs.isUndef()) return undef; + if (lhs.isNan()) return rhs; + if (rhs.isNan()) return lhs; + + return switch (order(lhs, rhs)) { + .lt => rhs, + .gt, .eq => lhs, + }; } /// Supports both floats and ints; handles undefined. - pub fn numberMin(lhs: Value, rhs: Value, arena: *Allocator) !Value { - if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); - - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try arena.alloc( - std.math.big.Limb, - std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - - switch (lhs_bigint.order(rhs_bigint)) { - .lt => result_bigint.copy(lhs_bigint), - .gt, .eq => result_bigint.copy(rhs_bigint), - } - - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(arena, result_limbs); - } else { - return Value.Tag.int_big_negative.create(arena, result_limbs); - } + pub fn numberMin(lhs: Value, rhs: Value) !Value { + if (lhs.isUndef() or rhs.isUndef()) return undef; + if (lhs.isNan()) return rhs; + if (rhs.isNan()) return lhs; + + return switch (order(lhs, rhs)) { + .lt => lhs, + .gt, .eq => rhs, + }; } /// operands must be integers; handles undefined. @@ -2327,6 +2299,17 @@ pub const Value = extern union { } } + /// Returns true if the value is a floating point type and is NaN. Returns false otherwise. + pub fn isNan(val: Value) bool { + return switch (val.tag()) { + .float_16 => std.math.isNan(val.castTag(.float_16).?.data), + .float_32 => std.math.isNan(val.castTag(.float_32).?.data), + .float_64 => std.math.isNan(val.castTag(.float_64).?.data), + .float_128 => std.math.isNan(val.castTag(.float_128).?.data), + else => false, + }; + } + pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value { _ = lhs; _ = rhs; diff --git a/test/behavior.zig b/test/behavior.zig index dac07e89fb9880a3bf214d776ab4610284f78a63..925d5a65f5a2f60dc47504c396568ca09241e1a4 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -24,6 +24,7 @@ test { _ = @import("behavior/generics.zig"); _ = @import("behavior/if.zig"); _ = @import("behavior/math.zig"); + _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/member_func.zig"); _ = @import("behavior/optional.zig"); _ = @import("behavior/pointers.zig"); @@ -130,7 +131,6 @@ test { _ = @import("behavior/inttoptr.zig"); _ = @import("behavior/ir_block_deps.zig"); _ = @import("behavior/math_stage1.zig"); - _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/merge_error_sets.zig"); _ = @import("behavior/misc.zig"); _ = @import("behavior/muladd.zig"); diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig index 5fef818f2b17fb606c19ce0ec23e16bd51f79173..59a86815aace0e959569322b9004f3c57070eb9b 100644 --- a/test/behavior/maximum_minimum.zig +++ b/test/behavior/maximum_minimum.zig @@ -8,8 +8,8 @@ const Vector = std.meta.Vector; test "@maximum" { const S = struct { fn doTheTest() !void { - try expectEqual(@as(i32, 10), @maximum(@as(i32, -3), @as(i32, 10))); - try expectEqual(@as(f32, 3.2), @maximum(@as(f32, 3.2), @as(f32, 0.68))); + try expect(@as(i32, 10) == @maximum(@as(i32, -3), @as(i32, 10))); + try expect(@as(f32, 3.2) == @maximum(@as(f32, 3.2), @as(f32, 0.68))); var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 }; var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 }; @@ -34,8 +34,8 @@ test "@maximum" { test "@minimum" { const S = struct { fn doTheTest() !void { - try expectEqual(@as(i32, -3), @minimum(@as(i32, -3), @as(i32, 10))); - try expectEqual(@as(f32, 0.68), @minimum(@as(f32, 3.2), @as(f32, 0.68))); + try expect(@as(i32, -3) == @minimum(@as(i32, -3), @as(i32, 10))); + try expect(@as(f32, 0.68) == @minimum(@as(f32, 3.2), @as(f32, 0.68))); var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 }; var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 }; -- 2.54.0