diff --git a/lib/std/os.zig b/lib/std/os.zig index eda246d2a3..7bbc13d2ea 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 9ad8bc9ae4..86e16487bb 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 b49147606c..6f7c938f4c 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 6ec102d26d..b6a35d05ef 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 e95500cf38..e45aac1a6f 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 49628b93f6..8745c1aa58 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 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); - 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 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) { diff --git a/src/print_air.zig b/src/print_air.zig index a4973a97c1..861483abac 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 e0ca0d426f..9ddeb507af 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 f5477baf21..704e667ac0 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); + 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; - // 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); - } + 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); + 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; - // 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); - } + 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 dac07e89fb..925d5a65f5 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 5fef818f2b..59a86815aa 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 };