authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-14 21:17:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-14 21:17:30-07:00
log55eea3b045c86c78eb8d9cc862122d260352a631
treea8e234fa3a68c1c62233f047b2b1647be2e091dd
parent8b882747813878a40b63572636a6e86a59a8581e

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.

14 files changed, 470 insertions(+), 125 deletions(-)

lib/std/os.zig+25-25
...@@ -1,18 +1,18 @@...@@ -1,18 +1,18 @@
1// This file contains thin wrappers around OS-specific APIs, with these1//! This file contains thin wrappers around OS-specific APIs, with these
2// specific goals in mind:2//! specific goals in mind:
3// * Convert "errno"-style error codes into Zig errors.3//! * Convert "errno"-style error codes into Zig errors.
4// * When null-terminated byte buffers are required, provide APIs which accept4//! * When null-terminated byte buffers are required, provide APIs which accept
5// slices as well as APIs which accept null-terminated byte buffers. Same goes5//! slices as well as APIs which accept null-terminated byte buffers. Same goes
6// for UTF-16LE encoding.6//! for UTF-16LE encoding.
7// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide7//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
8// cross platform abstracting.8//! cross platform abstracting.
9// * When there exists a corresponding libc function and linking libc, the libc9//! * When there exists a corresponding libc function and linking libc, the libc
10// implementation is used. Exceptions are made for known buggy areas of libc.10//! implementation is used. Exceptions are made for known buggy areas of libc.
11// On Linux libc can be side-stepped by using `std.os.linux` directly.11//! On Linux libc can be side-stepped by using `std.os.linux` directly.
12// * For Windows, this file represents the API that libc would provide for12//! * For Windows, this file represents the API that libc would provide for
13// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.13//! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
14// Note: The Zig standard library does not support POSIX thread cancellation, and14//! Note: The Zig standard library does not support POSIX thread cancellation, and
15// in general EINTR is handled by trying again.15//! in general EINTR is handled by trying again.
1616
17const root = @import("root");17const root = @import("root");
18const std = @import("std.zig");18const std = @import("std.zig");
...@@ -492,7 +492,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -492,7 +492,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
492 .macos, .ios, .watchos, .tvos => math.maxInt(i32),492 .macos, .ios, .watchos, .tvos => math.maxInt(i32),
493 else => math.maxInt(isize),493 else => math.maxInt(isize),
494 };494 };
495 const adjusted_len = math.min(max_count, buf.len);495 const adjusted_len = @minimum(max_count, buf.len);
496496
497 while (true) {497 while (true) {
498 const rc = system.read(fd, buf.ptr, adjusted_len);498 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 {...@@ -621,7 +621,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
621 .macos, .ios, .watchos, .tvos => math.maxInt(i32),621 .macos, .ios, .watchos, .tvos => math.maxInt(i32),
622 else => math.maxInt(isize),622 else => math.maxInt(isize),
623 };623 };
624 const adjusted_len = math.min(max_count, buf.len);624 const adjusted_len = @minimum(max_count, buf.len);
625625
626 const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc)626 const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc)
627 system.pread64627 system.pread64
...@@ -873,7 +873,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {...@@ -873,7 +873,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
873 .macos, .ios, .watchos, .tvos => math.maxInt(i32),873 .macos, .ios, .watchos, .tvos => math.maxInt(i32),
874 else => math.maxInt(isize),874 else => math.maxInt(isize),
875 };875 };
876 const adjusted_len = math.min(max_count, bytes.len);876 const adjusted_len = @minimum(max_count, bytes.len);
877877
878 while (true) {878 while (true) {
879 const rc = system.write(fd, bytes.ptr, adjusted_len);879 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 {...@@ -1029,7 +1029,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
1029 .macos, .ios, .watchos, .tvos => math.maxInt(i32),1029 .macos, .ios, .watchos, .tvos => math.maxInt(i32),
1030 else => math.maxInt(isize),1030 else => math.maxInt(isize),
1031 };1031 };
1032 const adjusted_len = math.min(max_count, bytes.len);1032 const adjusted_len = @minimum(max_count, bytes.len);
10331033
1034 const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc)1034 const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc)
1035 system.pwrite641035 system.pwrite64
...@@ -5439,7 +5439,7 @@ pub fn sendfile(...@@ -5439,7 +5439,7 @@ pub fn sendfile(
5439 }5439 }
54405440
5441 // Here we match BSD behavior, making a zero count value send as many bytes as possible.5441 // Here we match BSD behavior, making a zero count value send as many bytes as possible.
5442 const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count));5442 const adjusted_count = if (in_len == 0) max_count else @minimum(in_len, @as(size_t, max_count));
54435443
5444 const sendfile_sym = if (builtin.link_libc)5444 const sendfile_sym = if (builtin.link_libc)
5445 system.sendfile645445 system.sendfile64
...@@ -5522,7 +5522,7 @@ pub fn sendfile(...@@ -5522,7 +5522,7 @@ pub fn sendfile(
5522 hdtr = &hdtr_data;5522 hdtr = &hdtr_data;
5523 }5523 }
55245524
5525 const adjusted_count = math.min(in_len, max_count);5525 const adjusted_count = @minimum(in_len, max_count);
55265526
5527 while (true) {5527 while (true) {
5528 var sbytes: off_t = undefined;5528 var sbytes: off_t = undefined;
...@@ -5601,7 +5601,7 @@ pub fn sendfile(...@@ -5601,7 +5601,7 @@ pub fn sendfile(
5601 hdtr = &hdtr_data;5601 hdtr = &hdtr_data;
5602 }5602 }
56035603
5604 const adjusted_count = math.min(in_len, @as(u63, max_count));5604 const adjusted_count = @minimum(in_len, @as(u63, max_count));
56055605
5606 while (true) {5606 while (true) {
5607 var sbytes: off_t = adjusted_count;5607 var sbytes: off_t = adjusted_count;
...@@ -5655,7 +5655,7 @@ pub fn sendfile(...@@ -5655,7 +5655,7 @@ pub fn sendfile(
5655 rw: {5655 rw: {
5656 var buf: [8 * 4096]u8 = undefined;5656 var buf: [8 * 4096]u8 = undefined;
5657 // Here we match BSD behavior, making a zero count value send as many bytes as possible.5657 // Here we match BSD behavior, making a zero count value send as many bytes as possible.
5658 const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len);5658 const adjusted_count = if (in_len == 0) buf.len else @minimum(buf.len, in_len);
5659 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);5659 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
5660 if (amt_read == 0) {5660 if (amt_read == 0) {
5661 if (in_len == 0) {5661 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...@@ -5756,7 +5756,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
5756 }5756 }
57575757
5758 var buf: [8 * 4096]u8 = undefined;5758 var buf: [8 * 4096]u8 = undefined;
5759 const adjusted_count = math.min(buf.len, len);5759 const adjusted_count = @minimum(buf.len, len);
5760 const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in);5760 const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in);
5761 // TODO without @as the line below fails to compile for wasm32-wasi:5761 // TODO without @as the line below fails to compile for wasm32-wasi:
5762 // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize'5762 // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize'
...@@ -5919,7 +5919,7 @@ pub fn dn_expand(...@@ -5919,7 +5919,7 @@ pub fn dn_expand(
5919 const end = msg.ptr + msg.len;5919 const end = msg.ptr + msg.len;
5920 if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;5920 if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;
5921 var dest = exp_dn.ptr;5921 var dest = exp_dn.ptr;
5922 const dend = dest + std.math.min(exp_dn.len, 254);5922 const dend = dest + @minimum(exp_dn.len, 254);
5923 // detect reference loop using an iteration counter5923 // detect reference loop using an iteration counter
5924 var i: usize = 0;5924 var i: usize = 0;
5925 while (i < msg.len) : (i += 2) {5925 while (i < msg.len) : (i += 2) {
src/Air.zig+14
...@@ -107,6 +107,18 @@ pub const Inst = struct {...@@ -107,6 +107,18 @@ pub const Inst = struct {
107 /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs.107 /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs.
108 /// Uses the `bin_op` field.108 /// Uses the `bin_op` field.
109 ptr_sub,109 ptr_sub,
110 /// Given two operands which can be floats, integers, or vectors, returns the
111 /// greater of the operands. For vectors it operates element-wise.
112 /// Both operands are guaranteed to be the same type, and the result type
113 /// is the same as both operands.
114 /// Uses the `bin_op` field.
115 max,
116 /// Given two operands which can be floats, integers, or vectors, returns the
117 /// lesser of the operands. For vectors it operates element-wise.
118 /// Both operands are guaranteed to be the same type, and the result type
119 /// is the same as both operands.
120 /// Uses the `bin_op` field.
121 min,
110 /// Allocates stack local memory.122 /// Allocates stack local memory.
111 /// Uses the `ty` field.123 /// Uses the `ty` field.
112 alloc,124 alloc,
...@@ -640,6 +652,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {...@@ -640,6 +652,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
640 .shl,652 .shl,
641 .shl_exact,653 .shl_exact,
642 .shl_sat,654 .shl_sat,
655 .min,
656 .max,
643 => return air.typeOf(datas[inst].bin_op.lhs),657 => return air.typeOf(datas[inst].bin_op.lhs),
644658
645 .cmp_lt,659 .cmp_lt,
src/Liveness.zig+2
...@@ -264,6 +264,8 @@ fn analyzeInst(...@@ -264,6 +264,8 @@ fn analyzeInst(
264 .atomic_store_release,264 .atomic_store_release,
265 .atomic_store_seq_cst,265 .atomic_store_seq_cst,
266 .set_union_tag,266 .set_union_tag,
267 .min,
268 .max,
267 => {269 => {
268 const o = inst_datas[inst].bin_op;270 const o = inst_datas[inst].bin_op;
269 return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });271 return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
src/Sema.zig+218-28
...@@ -614,8 +614,6 @@ pub fn analyzeBody(...@@ -614,8 +614,6 @@ pub fn analyzeBody(
614 .builtin_call => try sema.zirBuiltinCall(block, inst),614 .builtin_call => try sema.zirBuiltinCall(block, inst),
615 .field_ptr_type => try sema.zirFieldPtrType(block, inst),615 .field_ptr_type => try sema.zirFieldPtrType(block, inst),
616 .field_parent_ptr => try sema.zirFieldParentPtr(block, inst),616 .field_parent_ptr => try sema.zirFieldParentPtr(block, inst),
617 .maximum => try sema.zirMaximum(block, inst),
618 .minimum => try sema.zirMinimum(block, inst),
619 .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst),617 .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst),
620 .@"resume" => try sema.zirResume(block, inst),618 .@"resume" => try sema.zirResume(block, inst),
621 .@"await" => try sema.zirAwait(block, inst, false),619 .@"await" => try sema.zirAwait(block, inst, false),
...@@ -654,6 +652,9 @@ pub fn analyzeBody(...@@ -654,6 +652,9 @@ pub fn analyzeBody(
654 .subwrap => try sema.zirArithmetic(block, inst, .subwrap),652 .subwrap => try sema.zirArithmetic(block, inst, .subwrap),
655 .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),653 .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
656654
655 .maximum => try sema.zirMinMax(block, inst, .max),
656 .minimum => try sema.zirMinMax(block, inst, .min),
657
657 .shl => try sema.zirShl(block, inst, .shl),658 .shl => try sema.zirShl(block, inst, .shl),
658 .shl_exact => try sema.zirShl(block, inst, .shl_exact),659 .shl_exact => try sema.zirShl(block, inst, .shl_exact),
659 .shl_sat => try sema.zirShl(block, inst, .shl_sat),660 .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...@@ -9018,6 +9019,12 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
9018 .Void => return Air.Inst.Ref.void_type,9019 .Void => return Air.Inst.Ref.void_type,
9019 .Bool => return Air.Inst.Ref.bool_type,9020 .Bool => return Air.Inst.Ref.bool_type,
9020 .NoReturn => return Air.Inst.Ref.noreturn_type,9021 .NoReturn => return Air.Inst.Ref.noreturn_type,
9022 .ComptimeFloat => return Air.Inst.Ref.comptime_float_type,
9023 .ComptimeInt => return Air.Inst.Ref.comptime_int_type,
9024 .Undefined => return Air.Inst.Ref.undefined_type,
9025 .Null => return Air.Inst.Ref.null_type,
9026 .AnyFrame => return Air.Inst.Ref.anyframe_type,
9027 .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
9021 .Int => {9028 .Int => {
9022 const struct_val = union_val.val.castTag(.@"struct").?.data;9029 const struct_val = union_val.val.castTag(.@"struct").?.data;
9023 // TODO use reflection instead of magic numbers here9030 // 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...@@ -9032,14 +9039,23 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
9032 };9039 };
9033 return sema.addType(ty);9040 return sema.addType(ty);
9034 },9041 },
9042 .Vector => {
9043 const struct_val = union_val.val.castTag(.@"struct").?.data;
9044 // TODO use reflection instead of magic numbers here
9045 const len_val = struct_val[0];
9046 const child_val = struct_val[1];
9047
9048 const len = len_val.toUnsignedInt();
9049 var buffer: Value.ToTypeBuffer = undefined;
9050 const child_ty = child_val.toType(&buffer);
9051
9052 const ty = try Type.vector(sema.arena, len, child_ty);
9053 return sema.addType(ty);
9054 },
9035 .Float => return sema.fail(block, src, "TODO: Sema.zirReify for Float", .{}),9055 .Float => return sema.fail(block, src, "TODO: Sema.zirReify for Float", .{}),
9036 .Pointer => return sema.fail(block, src, "TODO: Sema.zirReify for Pointer", .{}),9056 .Pointer => return sema.fail(block, src, "TODO: Sema.zirReify for Pointer", .{}),
9037 .Array => return sema.fail(block, src, "TODO: Sema.zirReify for Array", .{}),9057 .Array => return sema.fail(block, src, "TODO: Sema.zirReify for Array", .{}),
9038 .Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}),9058 .Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}),
9039 .ComptimeFloat => return Air.Inst.Ref.comptime_float_type,
9040 .ComptimeInt => return Air.Inst.Ref.comptime_int_type,
9041 .Undefined => return Air.Inst.Ref.undefined_type,
9042 .Null => return Air.Inst.Ref.null_type,
9043 .Optional => return sema.fail(block, src, "TODO: Sema.zirReify for Optional", .{}),9059 .Optional => return sema.fail(block, src, "TODO: Sema.zirReify for Optional", .{}),
9044 .ErrorUnion => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorUnion", .{}),9060 .ErrorUnion => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorUnion", .{}),
9045 .ErrorSet => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorSet", .{}),9061 .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...@@ -9049,9 +9065,6 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
9049 .BoundFn => @panic("TODO delete BoundFn from the language"),9065 .BoundFn => @panic("TODO delete BoundFn from the language"),
9050 .Opaque => return sema.fail(block, src, "TODO: Sema.zirReify for Opaque", .{}),9066 .Opaque => return sema.fail(block, src, "TODO: Sema.zirReify for Opaque", .{}),
9051 .Frame => return sema.fail(block, src, "TODO: Sema.zirReify for Frame", .{}),9067 .Frame => return sema.fail(block, src, "TODO: Sema.zirReify for Frame", .{}),
9052 .AnyFrame => return Air.Inst.Ref.anyframe_type,
9053 .Vector => return sema.fail(block, src, "TODO: Sema.zirReify for Vector", .{}),
9054 .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
9055 }9068 }
9056}9069}
90579070
...@@ -9379,9 +9392,23 @@ fn checkFloatType(...@@ -9379,9 +9392,23 @@ fn checkFloatType(
9379) CompileError!void {9392) CompileError!void {
9380 switch (ty.zigTypeTag()) {9393 switch (ty.zigTypeTag()) {
9381 .ComptimeFloat, .Float => {},9394 .ComptimeFloat, .Float => {},
9382 else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{9395 else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty}),
9383 ty,9396 }
9384 }),9397}
9398
9399fn checkNumericType(
9400 sema: *Sema,
9401 block: *Block,
9402 ty_src: LazySrcLoc,
9403 ty: Type,
9404) CompileError!void {
9405 switch (ty.zigTypeTag()) {
9406 .ComptimeFloat, .Float, .ComptimeInt, .Int => {},
9407 .Vector => switch (ty.childType().zigTypeTag()) {
9408 .ComptimeFloat, .Float, .ComptimeInt, .Int => {},
9409 else => |t| return sema.fail(block, ty_src, "expected number, found '{}'", .{t}),
9410 },
9411 else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty}),
9385 }9412 }
9386}9413}
93879414
...@@ -9474,6 +9501,82 @@ fn checkComptimeVarStore(...@@ -9474,6 +9501,82 @@ fn checkComptimeVarStore(
9474 }9501 }
9475}9502}
94769503
9504const SimdBinOp = struct {
9505 len: ?u64,
9506 /// Coerced to `result_ty`.
9507 lhs: Air.Inst.Ref,
9508 /// Coerced to `result_ty`.
9509 rhs: Air.Inst.Ref,
9510 lhs_val: ?Value,
9511 rhs_val: ?Value,
9512 /// Only different than `scalar_ty` when it is a vector operation.
9513 result_ty: Type,
9514 scalar_ty: Type,
9515};
9516
9517fn checkSimdBinOp(
9518 sema: *Sema,
9519 block: *Block,
9520 src: LazySrcLoc,
9521 uncasted_lhs: Air.Inst.Ref,
9522 uncasted_rhs: Air.Inst.Ref,
9523 lhs_src: LazySrcLoc,
9524 rhs_src: LazySrcLoc,
9525) CompileError!SimdBinOp {
9526 const lhs_ty = sema.typeOf(uncasted_lhs);
9527 const rhs_ty = sema.typeOf(uncasted_rhs);
9528 const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
9529 const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
9530
9531 var vec_len: ?u64 = null;
9532 if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
9533 const lhs_len = lhs_ty.arrayLen();
9534 const rhs_len = rhs_ty.arrayLen();
9535 if (lhs_len != rhs_len) {
9536 const msg = msg: {
9537 const msg = try sema.errMsg(block, src, "vector length mismatch", .{});
9538 errdefer msg.destroy(sema.gpa);
9539 try sema.errNote(block, lhs_src, msg, "length {d} here", .{lhs_len});
9540 try sema.errNote(block, rhs_src, msg, "length {d} here", .{rhs_len});
9541 break :msg msg;
9542 };
9543 return sema.failWithOwnedErrorMsg(msg);
9544 }
9545 vec_len = lhs_len;
9546 } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
9547 const msg = msg: {
9548 const msg = try sema.errMsg(block, src, "mixed scalar and vector operands: {} and {}", .{
9549 lhs_ty, rhs_ty,
9550 });
9551 errdefer msg.destroy(sema.gpa);
9552 if (lhs_zig_ty_tag == .Vector) {
9553 try sema.errNote(block, lhs_src, msg, "vector here", .{});
9554 try sema.errNote(block, rhs_src, msg, "scalar here", .{});
9555 } else {
9556 try sema.errNote(block, lhs_src, msg, "scalar here", .{});
9557 try sema.errNote(block, rhs_src, msg, "vector here", .{});
9558 }
9559 break :msg msg;
9560 };
9561 return sema.failWithOwnedErrorMsg(msg);
9562 }
9563 const result_ty = try sema.resolvePeerTypes(block, src, &.{ uncasted_lhs, uncasted_rhs }, .{
9564 .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
9565 });
9566 const lhs = try sema.coerce(block, result_ty, uncasted_lhs, lhs_src);
9567 const rhs = try sema.coerce(block, result_ty, uncasted_rhs, rhs_src);
9568
9569 return SimdBinOp{
9570 .len = vec_len,
9571 .lhs = lhs,
9572 .rhs = rhs,
9573 .lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs),
9574 .rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs),
9575 .result_ty = result_ty,
9576 .scalar_ty = result_ty.scalarType(),
9577 };
9578}
9579
9477fn resolveExportOptions(9580fn resolveExportOptions(
9478 sema: *Sema,9581 sema: *Sema,
9479 block: *Block,9582 block: *Block,
...@@ -9744,8 +9847,8 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A...@@ -9744,8 +9847,8 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
9744 .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target),9847 .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target),
9745 .Or => try stored_val.bitwiseOr (operand_val, sema.arena),9848 .Or => try stored_val.bitwiseOr (operand_val, sema.arena),
9746 .Xor => try stored_val.bitwiseXor (operand_val, sema.arena),9849 .Xor => try stored_val.bitwiseXor (operand_val, sema.arena),
9747 .Max => try stored_val.numberMax (operand_val, sema.arena),9850 .Max => try stored_val.numberMax (operand_val),
9748 .Min => try stored_val.numberMin (operand_val, sema.arena),9851 .Min => try stored_val.numberMin (operand_val),
9749 // zig fmt: on9852 // zig fmt: on
9750 };9853 };
9751 try sema.storePtrVal(block, src, ptr_val, new_val, operand_ty);9854 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...@@ -9826,10 +9929,62 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
9826 return sema.fail(block, src, "TODO: Sema.zirFieldParentPtr", .{});9929 return sema.fail(block, src, "TODO: Sema.zirFieldParentPtr", .{});
9827}9930}
98289931
9829fn zirMaximum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {9932fn zirMinMax(
9933 sema: *Sema,
9934 block: *Block,
9935 inst: Zir.Inst.Index,
9936 air_tag: Air.Inst.Tag,
9937) CompileError!Air.Inst.Ref {
9830 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;9938 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
9939 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
9831 const src = inst_data.src();9940 const src = inst_data.src();
9832 return sema.fail(block, src, "TODO: Sema.zirMaximum", .{});9941 const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
9942 const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
9943 const lhs = sema.resolveInst(extra.lhs);
9944 const rhs = sema.resolveInst(extra.rhs);
9945 try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs));
9946 try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs));
9947 const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src);
9948
9949 // TODO @maximum(max_int, undefined) should return max_int
9950
9951 const runtime_src = if (simd_op.lhs_val) |lhs_val| rs: {
9952 if (lhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
9953
9954 const rhs_val = simd_op.rhs_val orelse break :rs rhs_src;
9955
9956 if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
9957
9958 const opFunc = switch (air_tag) {
9959 .min => Value.numberMin,
9960 .max => Value.numberMax,
9961 else => unreachable,
9962 };
9963 const vec_len = simd_op.len orelse {
9964 const result_val = try opFunc(lhs_val, rhs_val);
9965 return sema.addConstant(simd_op.result_ty, result_val);
9966 };
9967 var lhs_buf: Value.ElemValueBuffer = undefined;
9968 var rhs_buf: Value.ElemValueBuffer = undefined;
9969 const elems = try sema.arena.alloc(Value, vec_len);
9970 for (elems) |*elem, i| {
9971 const lhs_elem_val = lhs_val.elemValueBuffer(i, &lhs_buf);
9972 const rhs_elem_val = rhs_val.elemValueBuffer(i, &rhs_buf);
9973 elem.* = try opFunc(lhs_elem_val, rhs_elem_val);
9974 }
9975 return sema.addConstant(
9976 simd_op.result_ty,
9977 try Value.Tag.array.create(sema.arena, elems),
9978 );
9979 } else rs: {
9980 if (simd_op.rhs_val) |rhs_val| {
9981 if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
9982 }
9983 break :rs lhs_src;
9984 };
9985
9986 try sema.requireRuntimeBlock(block, runtime_src);
9987 return block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs);
9833}9988}
98349989
9835fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {9990fn 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...@@ -9943,12 +10098,6 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
9943 });10098 });
9944}10099}
994510100
9946fn zirMinimum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
9947 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
9948 const src = inst_data.src();
9949 return sema.fail(block, src, "TODO: Sema.zirMinimum", .{});
9950}
9951
9952fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {10101fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
9953 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;10102 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
9954 const src = inst_data.src();10103 const src = inst_data.src();
...@@ -10453,12 +10602,7 @@ fn fieldVal(...@@ -10453,12 +10602,7 @@ fn fieldVal(
10453 const result_ty = object_ty.slicePtrFieldType(buf);10602 const result_ty = object_ty.slicePtrFieldType(buf);
10454 if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| {10603 if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| {
10455 if (val.isUndef()) return sema.addConstUndef(result_ty);10604 if (val.isUndef()) return sema.addConstUndef(result_ty);
10456 return sema.fail(10605 return sema.addConstant(result_ty, val.slicePtr());
10457 block,
10458 field_name_src,
10459 "TODO implement comptime slice ptr",
10460 .{},
10461 );
10462 }10606 }
10463 try sema.requireRuntimeBlock(block, src);10607 try sema.requireRuntimeBlock(block, src);
10464 return block.addTyOp(.slice_ptr, result_ty, object);10608 return block.addTyOp(.slice_ptr, result_ty, object);
...@@ -11464,6 +11608,10 @@ fn coerce(...@@ -11464,6 +11608,10 @@ fn coerce(
11464 .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),11608 .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),
11465 else => {},11609 else => {},
11466 },11610 },
11611 .Array => switch (inst_ty.zigTypeTag()) {
11612 .Vector => return sema.coerceVectorToArray(block, dest_ty, dest_ty_src, inst, inst_src),
11613 else => {},
11614 },
11467 else => {},11615 else => {},
11468 }11616 }
1146911617
...@@ -12045,6 +12193,48 @@ fn coerceEnumToUnion(...@@ -12045,6 +12193,48 @@ fn coerceEnumToUnion(
12045 return sema.failWithOwnedErrorMsg(msg);12193 return sema.failWithOwnedErrorMsg(msg);
12046}12194}
1204712195
12196fn coerceVectorToArray(
12197 sema: *Sema,
12198 block: *Block,
12199 array_ty: Type,
12200 array_ty_src: LazySrcLoc,
12201 vector: Air.Inst.Ref,
12202 vector_src: LazySrcLoc,
12203) !Air.Inst.Ref {
12204 const vector_ty = sema.typeOf(vector);
12205 const array_len = array_ty.arrayLen();
12206 const vector_len = vector_ty.arrayLen();
12207 if (array_len != vector_len) {
12208 const msg = msg: {
12209 const msg = try sema.errMsg(block, vector_src, "expected {}, found {}", .{
12210 array_ty, vector_ty,
12211 });
12212 errdefer msg.destroy(sema.gpa);
12213 try sema.errNote(block, array_ty_src, msg, "array has length {d}", .{array_len});
12214 try sema.errNote(block, vector_src, msg, "vector has length {d}", .{vector_len});
12215 break :msg msg;
12216 };
12217 return sema.failWithOwnedErrorMsg(msg);
12218 }
12219
12220 const target = sema.mod.getTarget();
12221 const array_elem_ty = array_ty.childType();
12222 const vector_elem_ty = vector_ty.childType();
12223 const in_memory_result = coerceInMemoryAllowed(array_elem_ty, vector_elem_ty, false, target);
12224 if (in_memory_result != .ok) {
12225 // TODO recursive error notes for coerceInMemoryAllowed failure
12226 return sema.fail(block, vector_src, "expected {}, found {}", .{ array_ty, vector_ty });
12227 }
12228
12229 if (try sema.resolveMaybeUndefVal(block, vector_src, vector)) |vector_val| {
12230 // These types share the same comptime value representation.
12231 return sema.addConstant(array_ty, vector_val);
12232 }
12233
12234 try sema.requireRuntimeBlock(block, vector_src);
12235 return block.addTyOp(.bitcast, array_ty, vector);
12236}
12237
12048fn analyzeDeclVal(12238fn analyzeDeclVal(
12049 sema: *Sema,12239 sema: *Sema,
12050 block: *Block,12240 block: *Block,
src/Zir.zig+3-3
...@@ -906,9 +906,6 @@ pub const Inst = struct {...@@ -906,9 +906,6 @@ pub const Inst = struct {
906 /// Implements the `@fieldParentPtr` builtin.906 /// Implements the `@fieldParentPtr` builtin.
907 /// Uses the `pl_node` union field with payload `FieldParentPtr`.907 /// Uses the `pl_node` union field with payload `FieldParentPtr`.
908 field_parent_ptr,908 field_parent_ptr,
909 /// Implements the `@maximum` builtin.
910 /// Uses the `pl_node` union field with payload `Bin`
911 maximum,
912 /// Implements the `@memcpy` builtin.909 /// Implements the `@memcpy` builtin.
913 /// Uses the `pl_node` union field with payload `Memcpy`.910 /// Uses the `pl_node` union field with payload `Memcpy`.
914 memcpy,911 memcpy,
...@@ -918,6 +915,9 @@ pub const Inst = struct {...@@ -918,6 +915,9 @@ pub const Inst = struct {
918 /// Implements the `@minimum` builtin.915 /// Implements the `@minimum` builtin.
919 /// Uses the `pl_node` union field with payload `Bin`916 /// Uses the `pl_node` union field with payload `Bin`
920 minimum,917 minimum,
918 /// Implements the `@maximum` builtin.
919 /// Uses the `pl_node` union field with payload `Bin`
920 maximum,
921 /// Implements the `@asyncCall` builtin.921 /// Implements the `@asyncCall` builtin.
922 /// Uses the `pl_node` union field with payload `AsyncCall`.922 /// Uses the `pl_node` union field with payload `AsyncCall`.
923 builtin_async_call,923 builtin_async_call,
src/codegen.zig+18
...@@ -839,6 +839,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -839,6 +839,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
839 .mod => try self.airMod(inst),839 .mod => try self.airMod(inst),
840 .shl, .shl_exact => try self.airShl(inst),840 .shl, .shl_exact => try self.airShl(inst),
841 .shl_sat => try self.airShlSat(inst),841 .shl_sat => try self.airShlSat(inst),
842 .min => try self.airMin(inst),
843 .max => try self.airMax(inst),
842844
843 .cmp_lt => try self.airCmp(inst, .lt),845 .cmp_lt => try self.airCmp(inst, .lt),
844 .cmp_lte => try self.airCmp(inst, .lte),846 .cmp_lte => try self.airCmp(inst, .lte),
...@@ -1299,6 +1301,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1299,6 +1301,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1299 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });1301 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1300 }1302 }
13011303
1304 fn airMin(self: *Self, inst: Air.Inst.Index) !void {
1305 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1306 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1307 else => return self.fail("TODO implement min for {}", .{self.target.cpu.arch}),
1308 };
1309 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1310 }
1311
1312 fn airMax(self: *Self, inst: Air.Inst.Index) !void {
1313 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1314 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1315 else => return self.fail("TODO implement max for {}", .{self.target.cpu.arch}),
1316 };
1317 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1318 }
1319
1302 fn airAdd(self: *Self, inst: Air.Inst.Index) !void {1320 fn airAdd(self: *Self, inst: Air.Inst.Index) !void {
1303 const bin_op = self.air.instructions.items(.data)[inst].bin_op;1321 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
1304 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {1322 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
src/codegen/c.zig+28
...@@ -989,6 +989,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO...@@ -989,6 +989,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
989 .mul_sat => try airSatOp(f, inst, "muls_"),989 .mul_sat => try airSatOp(f, inst, "muls_"),
990 .shl_sat => try airSatOp(f, inst, "shls_"),990 .shl_sat => try airSatOp(f, inst, "shls_"),
991991
992 .min => try airMinMax(f, inst, "<"),
993 .max => try airMinMax(f, inst, ">"),
994
992 .cmp_eq => try airBinOp(f, inst, " == "),995 .cmp_eq => try airBinOp(f, inst, " == "),
993 .cmp_gt => try airBinOp(f, inst, " > "),996 .cmp_gt => try airBinOp(f, inst, " > "),
994 .cmp_gte => try airBinOp(f, inst, " >= "),997 .cmp_gte => try airBinOp(f, inst, " >= "),
...@@ -1595,6 +1598,31 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue...@@ -1595,6 +1598,31 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue
1595 return local;1598 return local;
1596}1599}
15971600
1601fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
1602 if (f.liveness.isUnused(inst)) return CValue.none;
1603
1604 const bin_op = f.air.instructions.items(.data)[inst].bin_op;
1605 const lhs = try f.resolveInst(bin_op.lhs);
1606 const rhs = try f.resolveInst(bin_op.rhs);
1607
1608 const writer = f.object.writer();
1609 const inst_ty = f.air.typeOfIndex(inst);
1610 const local = try f.allocLocal(inst_ty, .Const);
1611
1612 // (lhs <> rhs) ? lhs : rhs
1613 try writer.writeAll(" = (");
1614 try f.writeCValue(writer, lhs);
1615 try writer.print("{s}", .{operator});
1616 try f.writeCValue(writer, rhs);
1617 try writer.writeAll(") ");
1618 try f.writeCValue(writer, lhs);
1619 try writer.writeAll(" : ");
1620 try f.writeCValue(writer, rhs);
1621 try writer.writeAll(";\n");
1622
1623 return local;
1624}
1625
1598fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {1626fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {
1599 const pl_op = f.air.instructions.items(.data)[inst].pl_op;1627 const pl_op = f.air.instructions.items(.data)[inst].pl_op;
1600 const extra = f.air.extraData(Air.Call, pl_op.payload);1628 const extra = f.air.extraData(Air.Call, pl_op.payload);
src/codegen/llvm.zig+74-10
...@@ -754,7 +754,7 @@ pub const DeclGen = struct {...@@ -754,7 +754,7 @@ pub const DeclGen = struct {
754754
755 const fields: [2]*const llvm.Type = .{755 const fields: [2]*const llvm.Type = .{
756 try dg.llvmType(ptr_type),756 try dg.llvmType(ptr_type),
757 try dg.llvmType(Type.initTag(.usize)),757 try dg.llvmType(Type.usize),
758 };758 };
759 return dg.context.structType(&fields, fields.len, .False);759 return dg.context.structType(&fields, fields.len, .False);
760 } else {760 } else {
...@@ -780,10 +780,14 @@ pub const DeclGen = struct {...@@ -780,10 +780,14 @@ pub const DeclGen = struct {
780 return llvm_struct_ty;780 return llvm_struct_ty;
781 },781 },
782 .Array => {782 .Array => {
783 const elem_type = try dg.llvmType(t.elemType());783 const elem_type = try dg.llvmType(t.childType());
784 const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null);784 const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null);
785 return elem_type.arrayType(@intCast(c_uint, total_len));785 return elem_type.arrayType(@intCast(c_uint, total_len));
786 },786 },
787 .Vector => {
788 const elem_type = try dg.llvmType(t.childType());
789 return elem_type.vectorType(@intCast(c_uint, t.arrayLen()));
790 },
787 .Optional => {791 .Optional => {
788 var buf: Type.Payload.ElemType = undefined;792 var buf: Type.Payload.ElemType = undefined;
789 const child_type = t.optionalChild(&buf);793 const child_type = t.optionalChild(&buf);
...@@ -966,7 +970,6 @@ pub const DeclGen = struct {...@@ -966,7 +970,6 @@ pub const DeclGen = struct {
966970
967 .Frame,971 .Frame,
968 .AnyFrame,972 .AnyFrame,
969 .Vector,
970 => return dg.todo("implement llvmType for type '{}'", .{t}),973 => return dg.todo("implement llvmType for type '{}'", .{t}),
971 }974 }
972 }975 }
...@@ -1062,7 +1065,7 @@ pub const DeclGen = struct {...@@ -1062,7 +1065,7 @@ pub const DeclGen = struct {
1062 return self.context.constStruct(&fields, fields.len, .False);1065 return self.context.constStruct(&fields, fields.len, .False);
1063 },1066 },
1064 .int_u64 => {1067 .int_u64 => {
1065 const llvm_usize = try self.llvmType(Type.initTag(.usize));1068 const llvm_usize = try self.llvmType(Type.usize);
1066 const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False);1069 const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False);
1067 return llvm_int.constIntToPtr(try self.llvmType(tv.ty));1070 return llvm_int.constIntToPtr(try self.llvmType(tv.ty));
1068 },1071 },
...@@ -1295,7 +1298,7 @@ pub const DeclGen = struct {...@@ -1295,7 +1298,7 @@ pub const DeclGen = struct {
1295 .val = tv.val,1298 .val = tv.val,
1296 }),1299 }),
1297 try self.genTypedValue(.{1300 try self.genTypedValue(.{
1298 .ty = Type.initTag(.usize),1301 .ty = Type.usize,
1299 .val = Value.initPayload(&slice_len.base),1302 .val = Value.initPayload(&slice_len.base),
1300 }),1303 }),
1301 };1304 };
...@@ -1470,6 +1473,8 @@ pub const FuncGen = struct {...@@ -1470,6 +1473,8 @@ pub const FuncGen = struct {
1470 .shl => try self.airShl(inst),1473 .shl => try self.airShl(inst),
1471 .shl_sat => try self.airShlSat(inst),1474 .shl_sat => try self.airShlSat(inst),
1472 .shl_exact => try self.airShlExact(inst),1475 .shl_exact => try self.airShlExact(inst),
1476 .min => try self.airMin(inst),
1477 .max => try self.airMax(inst),
14731478
1474 .bit_and, .bool_and => try self.airAnd(inst),1479 .bit_and, .bool_and => try self.airAnd(inst),
1475 .bit_or, .bool_or => try self.airOr(inst),1480 .bit_or, .bool_or => try self.airOr(inst),
...@@ -2356,6 +2361,32 @@ pub const FuncGen = struct {...@@ -2356,6 +2361,32 @@ pub const FuncGen = struct {
2356 return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});2361 return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
2357 }2362 }
23582363
2364 fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2365 if (self.liveness.isUnused(inst)) return null;
2366
2367 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2368 const lhs = try self.resolveInst(bin_op.lhs);
2369 const rhs = try self.resolveInst(bin_op.rhs);
2370 const scalar_ty = self.air.typeOfIndex(inst).scalarType();
2371
2372 if (scalar_ty.isAnyFloat()) return self.builder.buildMinNum(lhs, rhs, "");
2373 if (scalar_ty.isSignedInt()) return self.builder.buildSMin(lhs, rhs, "");
2374 return self.builder.buildUMin(lhs, rhs, "");
2375 }
2376
2377 fn airMax(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2378 if (self.liveness.isUnused(inst)) return null;
2379
2380 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
2381 const lhs = try self.resolveInst(bin_op.lhs);
2382 const rhs = try self.resolveInst(bin_op.rhs);
2383 const scalar_ty = self.air.typeOfIndex(inst).scalarType();
2384
2385 if (scalar_ty.isAnyFloat()) return self.builder.buildMaxNum(lhs, rhs, "");
2386 if (scalar_ty.isSignedInt()) return self.builder.buildSMax(lhs, rhs, "");
2387 return self.builder.buildUMax(lhs, rhs, "");
2388 }
2389
2359 fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2390 fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2360 if (self.liveness.isUnused(inst)) return null;2391 if (self.liveness.isUnused(inst)) return null;
23612392
...@@ -2705,15 +2736,48 @@ pub const FuncGen = struct {...@@ -2705,15 +2736,48 @@ pub const FuncGen = struct {
2705 }2736 }
27062737
2707 fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2738 fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2708 if (self.liveness.isUnused(inst))2739 if (self.liveness.isUnused(inst)) return null;
2709 return null;
27102740
2711 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2741 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2712 const operand = try self.resolveInst(ty_op.operand);2742 const operand = try self.resolveInst(ty_op.operand);
2743 const operand_ty = self.air.typeOf(ty_op.operand);
2713 const inst_ty = self.air.typeOfIndex(inst);2744 const inst_ty = self.air.typeOfIndex(inst);
2714 const dest_type = try self.dg.llvmType(inst_ty);2745 const llvm_dest_ty = try self.dg.llvmType(inst_ty);
2746
2747 // TODO look into pulling this logic out into a different AIR instruction than bitcast
2748 if (operand_ty.zigTypeTag() == .Vector and inst_ty.zigTypeTag() == .Array) {
2749 const target = self.dg.module.getTarget();
2750 const elem_ty = operand_ty.childType();
2751 if (!isByRef(inst_ty)) {
2752 return self.dg.todo("implement bitcast vector to non-ref array", .{});
2753 }
2754 const array_ptr = self.buildAlloca(llvm_dest_ty);
2755 const bitcast_ok = elem_ty.bitSize(target) == elem_ty.abiSize(target) * 8;
2756 if (bitcast_ok) {
2757 const llvm_vector_ty = try self.dg.llvmType(operand_ty);
2758 const casted_ptr = self.builder.buildBitCast(array_ptr, llvm_vector_ty.pointerType(0), "");
2759 _ = self.builder.buildStore(operand, casted_ptr);
2760 } else {
2761 // If the ABI size of the element type is not evenly divisible by size in bits;
2762 // a simple bitcast will not work, and we fall back to extractelement.
2763 const llvm_usize = try self.dg.llvmType(Type.usize);
2764 const llvm_u32 = self.context.intType(32);
2765 const zero = llvm_usize.constNull();
2766 const vector_len = operand_ty.arrayLen();
2767 var i: u64 = 0;
2768 while (i < vector_len) : (i += 1) {
2769 const index_usize = llvm_usize.constInt(i, .False);
2770 const index_u32 = llvm_u32.constInt(i, .False);
2771 const indexes: [2]*const llvm.Value = .{ zero, index_usize };
2772 const elem_ptr = self.builder.buildInBoundsGEP(array_ptr, &indexes, indexes.len, "");
2773 const elem = self.builder.buildExtractElement(operand, index_u32, "");
2774 _ = self.builder.buildStore(elem, elem_ptr);
2775 }
2776 }
2777 return array_ptr;
2778 }
27152779
2716 return self.builder.buildBitCast(operand, dest_type, "");2780 return self.builder.buildBitCast(operand, llvm_dest_ty, "");
2717 }2781 }
27182782
2719 fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2783 fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
...@@ -2906,7 +2970,7 @@ pub const FuncGen = struct {...@@ -2906,7 +2970,7 @@ pub const FuncGen = struct {
2906 }2970 }
29072971
2908 // It's a pointer but we need to treat it as an int.2972 // It's a pointer but we need to treat it as an int.
2909 const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize));2973 const usize_llvm_ty = try self.dg.llvmType(Type.usize);
2910 const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), "");2974 const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), "");
2911 const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, "");2975 const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, "");
2912 const uncasted_result = self.builder.buildAtomicRmw(2976 const uncasted_result = self.builder.buildAtomicRmw(
src/codegen/llvm/bindings.zig+29
...@@ -212,6 +212,9 @@ pub const Type = opaque {...@@ -212,6 +212,9 @@ pub const Type = opaque {
212 pub const arrayType = LLVMArrayType;212 pub const arrayType = LLVMArrayType;
213 extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type;213 extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type;
214214
215 pub const vectorType = LLVMVectorType;
216 extern fn LLVMVectorType(ElementType: *const Type, ElementCount: c_uint) *const Type;
217
215 pub const structSetBody = LLVMStructSetBody;218 pub const structSetBody = LLVMStructSetBody;
216 extern fn LLVMStructSetBody(219 extern fn LLVMStructSetBody(
217 StructTy: *const Type,220 StructTy: *const Type,
...@@ -553,6 +556,14 @@ pub const Builder = opaque {...@@ -553,6 +556,14 @@ pub const Builder = opaque {
553 Name: [*:0]const u8,556 Name: [*:0]const u8,
554 ) *const Value;557 ) *const Value;
555558
559 pub const buildExtractElement = LLVMBuildExtractElement;
560 extern fn LLVMBuildExtractElement(
561 *const Builder,
562 VecVal: *const Value,
563 Index: *const Value,
564 Name: [*:0]const u8,
565 ) *const Value;
566
556 pub const buildPtrToInt = LLVMBuildPtrToInt;567 pub const buildPtrToInt = LLVMBuildPtrToInt;
557 extern fn LLVMBuildPtrToInt(568 extern fn LLVMBuildPtrToInt(
558 *const Builder,569 *const Builder,
...@@ -700,6 +711,24 @@ pub const Builder = opaque {...@@ -700,6 +711,24 @@ pub const Builder = opaque {
700 Size: *const Value,711 Size: *const Value,
701 is_volatile: bool,712 is_volatile: bool,
702 ) *const Value;713 ) *const Value;
714
715 pub const buildMaxNum = ZigLLVMBuildMaxNum;
716 extern fn ZigLLVMBuildMaxNum(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
717
718 pub const buildMinNum = ZigLLVMBuildMinNum;
719 extern fn ZigLLVMBuildMinNum(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
720
721 pub const buildUMax = ZigLLVMBuildUMax;
722 extern fn ZigLLVMBuildUMax(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
723
724 pub const buildUMin = ZigLLVMBuildUMin;
725 extern fn ZigLLVMBuildUMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
726
727 pub const buildSMax = ZigLLVMBuildSMax;
728 extern fn ZigLLVMBuildSMax(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
729
730 pub const buildSMin = ZigLLVMBuildSMin;
731 extern fn ZigLLVMBuildSMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
703};732};
704733
705pub const IntPredicate = enum(c_uint) {734pub const IntPredicate = enum(c_uint) {
src/print_air.zig+2
...@@ -138,6 +138,8 @@ const Writer = struct {...@@ -138,6 +138,8 @@ const Writer = struct {
138 .shl_sat,138 .shl_sat,
139 .shr,139 .shr,
140 .set_union_tag,140 .set_union_tag,
141 .min,
142 .max,
141 => try w.writeBinOp(s, inst),143 => try w.writeBinOp(s, inst),
142144
143 .is_null,145 .is_null,
src/type.zig+15
...@@ -2517,6 +2517,14 @@ pub const Type = extern union {...@@ -2517,6 +2517,14 @@ pub const Type = extern union {
2517 };2517 };
2518 }2518 }
25192519
2520 /// For vectors, returns the element type. Otherwise returns self.
2521 pub fn scalarType(ty: Type) Type {
2522 return switch (ty.zigTypeTag()) {
2523 .Vector => ty.childType(),
2524 else => ty,
2525 };
2526 }
2527
2520 /// Asserts that the type is an optional.2528 /// Asserts that the type is an optional.
2521 /// Resulting `Type` will have inner memory referencing `buf`.2529 /// Resulting `Type` will have inner memory referencing `buf`.
2522 pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type {2530 pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type {
...@@ -4017,6 +4025,13 @@ pub const Type = extern union {...@@ -4017,6 +4025,13 @@ pub const Type = extern union {
4017 });4025 });
4018 }4026 }
40194027
4028 pub fn vector(arena: *Allocator, len: u64, elem_type: Type) Allocator.Error!Type {
4029 return Tag.vector.create(arena, .{
4030 .len = len,
4031 .elem_type = elem_type,
4032 });
4033 }
4034
4020 pub fn smallestUnsignedBits(max: u64) u16 {4035 pub fn smallestUnsignedBits(max: u64) u16 {
4021 if (max == 0) return 0;4036 if (max == 0) return 0;
4022 const base = std.math.log2(max);4037 const base = std.math.log2(max);
src/value.zig+37-54
...@@ -1626,6 +1626,14 @@ pub const Value = extern union {...@@ -1626,6 +1626,14 @@ pub const Value = extern union {
1626 };1626 };
1627 }1627 }
16281628
1629 pub fn slicePtr(val: Value) Value {
1630 return switch (val.tag()) {
1631 .slice => val.castTag(.slice).?.data.ptr,
1632 .decl_ref, .decl_ref_mut => val,
1633 else => unreachable,
1634 };
1635 }
1636
1629 pub fn sliceLen(val: Value) u64 {1637 pub fn sliceLen(val: Value) u64 {
1630 return switch (val.tag()) {1638 return switch (val.tag()) {
1631 .slice => val.castTag(.slice).?.data.len.toUnsignedInt(),1639 .slice => val.castTag(.slice).?.data.len.toUnsignedInt(),
...@@ -2042,63 +2050,27 @@ pub const Value = extern union {...@@ -2042,63 +2050,27 @@ pub const Value = extern union {
2042 }2050 }
20432051
2044 /// Supports both floats and ints; handles undefined.2052 /// Supports both floats and ints; handles undefined.
2045 pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {2053 pub fn numberMax(lhs: Value, rhs: Value) !Value {
2046 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);2054 if (lhs.isUndef() or rhs.isUndef()) return undef;
20472055 if (lhs.isNan()) return rhs;
2048 // TODO is this a performance issue? maybe we should try the operation without2056 if (rhs.isNan()) return lhs;
2049 // resorting to BigInt first.2057
2050 var lhs_space: Value.BigIntSpace = undefined;2058 return switch (order(lhs, rhs)) {
2051 var rhs_space: Value.BigIntSpace = undefined;2059 .lt => rhs,
2052 const lhs_bigint = lhs.toBigInt(&lhs_space);2060 .gt, .eq => lhs,
2053 const rhs_bigint = rhs.toBigInt(&rhs_space);2061 };
2054 const limbs = try arena.alloc(
2055 std.math.big.Limb,
2056 std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
2057 );
2058 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
2059
2060 switch (lhs_bigint.order(rhs_bigint)) {
2061 .lt => result_bigint.copy(rhs_bigint),
2062 .gt, .eq => result_bigint.copy(lhs_bigint),
2063 }
2064
2065 const result_limbs = result_bigint.limbs[0..result_bigint.len];
2066
2067 if (result_bigint.positive) {
2068 return Value.Tag.int_big_positive.create(arena, result_limbs);
2069 } else {
2070 return Value.Tag.int_big_negative.create(arena, result_limbs);
2071 }
2072 }2062 }
20732063
2074 /// Supports both floats and ints; handles undefined.2064 /// Supports both floats and ints; handles undefined.
2075 pub fn numberMin(lhs: Value, rhs: Value, arena: *Allocator) !Value {2065 pub fn numberMin(lhs: Value, rhs: Value) !Value {
2076 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);2066 if (lhs.isUndef() or rhs.isUndef()) return undef;
20772067 if (lhs.isNan()) return rhs;
2078 // TODO is this a performance issue? maybe we should try the operation without2068 if (rhs.isNan()) return lhs;
2079 // resorting to BigInt first.2069
2080 var lhs_space: Value.BigIntSpace = undefined;2070 return switch (order(lhs, rhs)) {
2081 var rhs_space: Value.BigIntSpace = undefined;2071 .lt => lhs,
2082 const lhs_bigint = lhs.toBigInt(&lhs_space);2072 .gt, .eq => rhs,
2083 const rhs_bigint = rhs.toBigInt(&rhs_space);2073 };
2084 const limbs = try arena.alloc(
2085 std.math.big.Limb,
2086 std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
2087 );
2088 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
2089
2090 switch (lhs_bigint.order(rhs_bigint)) {
2091 .lt => result_bigint.copy(lhs_bigint),
2092 .gt, .eq => result_bigint.copy(rhs_bigint),
2093 }
2094
2095 const result_limbs = result_bigint.limbs[0..result_bigint.len];
2096
2097 if (result_bigint.positive) {
2098 return Value.Tag.int_big_positive.create(arena, result_limbs);
2099 } else {
2100 return Value.Tag.int_big_negative.create(arena, result_limbs);
2101 }
2102 }2074 }
21032075
2104 /// operands must be integers; handles undefined. 2076 /// operands must be integers; handles undefined.
...@@ -2327,6 +2299,17 @@ pub const Value = extern union {...@@ -2327,6 +2299,17 @@ pub const Value = extern union {
2327 }2299 }
2328 }2300 }
23292301
2302 /// Returns true if the value is a floating point type and is NaN. Returns false otherwise.
2303 pub fn isNan(val: Value) bool {
2304 return switch (val.tag()) {
2305 .float_16 => std.math.isNan(val.castTag(.float_16).?.data),
2306 .float_32 => std.math.isNan(val.castTag(.float_32).?.data),
2307 .float_64 => std.math.isNan(val.castTag(.float_64).?.data),
2308 .float_128 => std.math.isNan(val.castTag(.float_128).?.data),
2309 else => false,
2310 };
2311 }
2312
2330 pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {2313 pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
2331 _ = lhs;2314 _ = lhs;
2332 _ = rhs;2315 _ = rhs;
test/behavior.zig+1-1
...@@ -24,6 +24,7 @@ test {...@@ -24,6 +24,7 @@ test {
24 _ = @import("behavior/generics.zig");24 _ = @import("behavior/generics.zig");
25 _ = @import("behavior/if.zig");25 _ = @import("behavior/if.zig");
26 _ = @import("behavior/math.zig");26 _ = @import("behavior/math.zig");
27 _ = @import("behavior/maximum_minimum.zig");
27 _ = @import("behavior/member_func.zig");28 _ = @import("behavior/member_func.zig");
28 _ = @import("behavior/optional.zig");29 _ = @import("behavior/optional.zig");
29 _ = @import("behavior/pointers.zig");30 _ = @import("behavior/pointers.zig");
...@@ -130,7 +131,6 @@ test {...@@ -130,7 +131,6 @@ test {
130 _ = @import("behavior/inttoptr.zig");131 _ = @import("behavior/inttoptr.zig");
131 _ = @import("behavior/ir_block_deps.zig");132 _ = @import("behavior/ir_block_deps.zig");
132 _ = @import("behavior/math_stage1.zig");133 _ = @import("behavior/math_stage1.zig");
133 _ = @import("behavior/maximum_minimum.zig");
134 _ = @import("behavior/merge_error_sets.zig");134 _ = @import("behavior/merge_error_sets.zig");
135 _ = @import("behavior/misc.zig");135 _ = @import("behavior/misc.zig");
136 _ = @import("behavior/muladd.zig");136 _ = @import("behavior/muladd.zig");
test/behavior/maximum_minimum.zig+4-4
...@@ -8,8 +8,8 @@ const Vector = std.meta.Vector;...@@ -8,8 +8,8 @@ const Vector = std.meta.Vector;
8test "@maximum" {8test "@maximum" {
9 const S = struct {9 const S = struct {
10 fn doTheTest() !void {10 fn doTheTest() !void {
11 try expectEqual(@as(i32, 10), @maximum(@as(i32, -3), @as(i32, 10)));11 try expect(@as(i32, 10) == @maximum(@as(i32, -3), @as(i32, 10)));
12 try expectEqual(@as(f32, 3.2), @maximum(@as(f32, 3.2), @as(f32, 0.68)));12 try expect(@as(f32, 3.2) == @maximum(@as(f32, 3.2), @as(f32, 0.68)));
1313
14 var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };14 var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
15 var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };15 var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
...@@ -34,8 +34,8 @@ test "@maximum" {...@@ -34,8 +34,8 @@ test "@maximum" {
34test "@minimum" {34test "@minimum" {
35 const S = struct {35 const S = struct {
36 fn doTheTest() !void {36 fn doTheTest() !void {
37 try expectEqual(@as(i32, -3), @minimum(@as(i32, -3), @as(i32, 10)));37 try expect(@as(i32, -3) == @minimum(@as(i32, -3), @as(i32, 10)));
38 try expectEqual(@as(f32, 0.68), @minimum(@as(f32, 3.2), @as(f32, 0.68)));38 try expect(@as(f32, 0.68) == @minimum(@as(f32, 3.2), @as(f32, 0.68)));
3939
40 var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };40 var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
41 var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };41 var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };