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.
This commit is contained in:
Andrew Kelley 2021-10-14 21:17:30 -07:00
parent 8b88274781
commit 55eea3b045
14 changed files with 468 additions and 123 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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 });

View File

@ -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,

View File

@ -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,

View File

@ -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) {

View File

@ -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);

View File

@ -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(

View File

@ -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) {

View File

@ -138,6 +138,8 @@ const Writer = struct {
.shl_sat,
.shr,
.set_union_tag,
.min,
.max,
=> try w.writeBinOp(s, inst),
.is_null,

View File

@ -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);

View File

@ -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;

View File

@ -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");

View File

@ -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 };