Commit 55eea3b045
Changed files (14)
lib
std
src
test
behavior
lib/std/os.zig
@@ -1,18 +1,18 @@
-// This file contains thin wrappers around OS-specific APIs, with these
-// specific goals in mind:
-// * Convert "errno"-style error codes into Zig errors.
-// * When null-terminated byte buffers are required, provide APIs which accept
-// slices as well as APIs which accept null-terminated byte buffers. Same goes
-// for UTF-16LE encoding.
-// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
-// cross platform abstracting.
-// * When there exists a corresponding libc function and linking libc, the libc
-// implementation is used. Exceptions are made for known buggy areas of libc.
-// On Linux libc can be side-stepped by using `std.os.linux` directly.
-// * For Windows, this file represents the API that libc would provide for
-// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
-// Note: The Zig standard library does not support POSIX thread cancellation, and
-// in general EINTR is handled by trying again.
+//! This file contains thin wrappers around OS-specific APIs, with these
+//! specific goals in mind:
+//! * Convert "errno"-style error codes into Zig errors.
+//! * When null-terminated byte buffers are required, provide APIs which accept
+//! slices as well as APIs which accept null-terminated byte buffers. Same goes
+//! for UTF-16LE encoding.
+//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
+//! cross platform abstracting.
+//! * When there exists a corresponding libc function and linking libc, the libc
+//! implementation is used. Exceptions are made for known buggy areas of libc.
+//! On Linux libc can be side-stepped by using `std.os.linux` directly.
+//! * For Windows, this file represents the API that libc would provide for
+//! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
+//! Note: The Zig standard library does not support POSIX thread cancellation, and
+//! in general EINTR is handled by trying again.
const root = @import("root");
const std = @import("std.zig");
@@ -492,7 +492,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
.macos, .ios, .watchos, .tvos => math.maxInt(i32),
else => math.maxInt(isize),
};
- const adjusted_len = math.min(max_count, buf.len);
+ const adjusted_len = @minimum(max_count, buf.len);
while (true) {
const rc = system.read(fd, buf.ptr, adjusted_len);
@@ -621,7 +621,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
.macos, .ios, .watchos, .tvos => math.maxInt(i32),
else => math.maxInt(isize),
};
- const adjusted_len = math.min(max_count, buf.len);
+ const adjusted_len = @minimum(max_count, buf.len);
const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc)
system.pread64
@@ -873,7 +873,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
.macos, .ios, .watchos, .tvos => math.maxInt(i32),
else => math.maxInt(isize),
};
- const adjusted_len = math.min(max_count, bytes.len);
+ const adjusted_len = @minimum(max_count, bytes.len);
while (true) {
const rc = system.write(fd, bytes.ptr, adjusted_len);
@@ -1029,7 +1029,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
.macos, .ios, .watchos, .tvos => math.maxInt(i32),
else => math.maxInt(isize),
};
- const adjusted_len = math.min(max_count, bytes.len);
+ const adjusted_len = @minimum(max_count, bytes.len);
const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc)
system.pwrite64
@@ -5439,7 +5439,7 @@ pub fn sendfile(
}
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
- const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count));
+ const adjusted_count = if (in_len == 0) max_count else @minimum(in_len, @as(size_t, max_count));
const sendfile_sym = if (builtin.link_libc)
system.sendfile64
@@ -5522,7 +5522,7 @@ pub fn sendfile(
hdtr = &hdtr_data;
}
- const adjusted_count = math.min(in_len, max_count);
+ const adjusted_count = @minimum(in_len, max_count);
while (true) {
var sbytes: off_t = undefined;
@@ -5601,7 +5601,7 @@ pub fn sendfile(
hdtr = &hdtr_data;
}
- const adjusted_count = math.min(in_len, @as(u63, max_count));
+ const adjusted_count = @minimum(in_len, @as(u63, max_count));
while (true) {
var sbytes: off_t = adjusted_count;
@@ -5655,7 +5655,7 @@ pub fn sendfile(
rw: {
var buf: [8 * 4096]u8 = undefined;
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
- const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len);
+ const adjusted_count = if (in_len == 0) buf.len else @minimum(buf.len, in_len);
const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
if (amt_read == 0) {
if (in_len == 0) {
@@ -5756,7 +5756,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
}
var buf: [8 * 4096]u8 = undefined;
- const adjusted_count = math.min(buf.len, len);
+ const adjusted_count = @minimum(buf.len, len);
const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in);
// TODO without @as the line below fails to compile for wasm32-wasi:
// error: integer value 0 cannot be coerced to type 'os.PWriteError!usize'
@@ -5919,7 +5919,7 @@ pub fn dn_expand(
const end = msg.ptr + msg.len;
if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;
var dest = exp_dn.ptr;
- const dend = dest + std.math.min(exp_dn.len, 254);
+ const dend = dest + @minimum(exp_dn.len, 254);
// detect reference loop using an iteration counter
var i: usize = 0;
while (i < msg.len) : (i += 2) {
src/codegen/llvm/bindings.zig
@@ -212,6 +212,9 @@ pub const Type = opaque {
pub const arrayType = LLVMArrayType;
extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type;
+ pub const vectorType = LLVMVectorType;
+ extern fn LLVMVectorType(ElementType: *const Type, ElementCount: c_uint) *const Type;
+
pub const structSetBody = LLVMStructSetBody;
extern fn LLVMStructSetBody(
StructTy: *const Type,
@@ -553,6 +556,14 @@ pub const Builder = opaque {
Name: [*:0]const u8,
) *const Value;
+ pub const buildExtractElement = LLVMBuildExtractElement;
+ extern fn LLVMBuildExtractElement(
+ *const Builder,
+ VecVal: *const Value,
+ Index: *const Value,
+ Name: [*:0]const u8,
+ ) *const Value;
+
pub const buildPtrToInt = LLVMBuildPtrToInt;
extern fn LLVMBuildPtrToInt(
*const Builder,
@@ -700,6 +711,24 @@ pub const Builder = opaque {
Size: *const Value,
is_volatile: bool,
) *const Value;
+
+ pub const buildMaxNum = ZigLLVMBuildMaxNum;
+ extern fn ZigLLVMBuildMaxNum(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
+
+ pub const buildMinNum = ZigLLVMBuildMinNum;
+ extern fn ZigLLVMBuildMinNum(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
+
+ pub const buildUMax = ZigLLVMBuildUMax;
+ extern fn ZigLLVMBuildUMax(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
+
+ pub const buildUMin = ZigLLVMBuildUMin;
+ extern fn ZigLLVMBuildUMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
+
+ pub const buildSMax = ZigLLVMBuildSMax;
+ extern fn ZigLLVMBuildSMax(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
+
+ pub const buildSMin = ZigLLVMBuildSMin;
+ extern fn ZigLLVMBuildSMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
};
pub const IntPredicate = enum(c_uint) {
src/codegen/c.zig
@@ -989,6 +989,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.mul_sat => try airSatOp(f, inst, "muls_"),
.shl_sat => try airSatOp(f, inst, "shls_"),
+ .min => try airMinMax(f, inst, "<"),
+ .max => try airMinMax(f, inst, ">"),
+
.cmp_eq => try airBinOp(f, inst, " == "),
.cmp_gt => try airBinOp(f, inst, " > "),
.cmp_gte => try airBinOp(f, inst, " >= "),
@@ -1595,6 +1598,31 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue
return local;
}
+fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
+ if (f.liveness.isUnused(inst)) return CValue.none;
+
+ const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try f.resolveInst(bin_op.lhs);
+ const rhs = try f.resolveInst(bin_op.rhs);
+
+ const writer = f.object.writer();
+ const inst_ty = f.air.typeOfIndex(inst);
+ const local = try f.allocLocal(inst_ty, .Const);
+
+ // (lhs <> rhs) ? lhs : rhs
+ try writer.writeAll(" = (");
+ try f.writeCValue(writer, lhs);
+ try writer.print("{s}", .{operator});
+ try f.writeCValue(writer, rhs);
+ try writer.writeAll(") ");
+ try f.writeCValue(writer, lhs);
+ try writer.writeAll(" : ");
+ try f.writeCValue(writer, rhs);
+ try writer.writeAll(";\n");
+
+ return local;
+}
+
fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {
const pl_op = f.air.instructions.items(.data)[inst].pl_op;
const extra = f.air.extraData(Air.Call, pl_op.payload);
src/codegen/llvm.zig
@@ -754,7 +754,7 @@ pub const DeclGen = struct {
const fields: [2]*const llvm.Type = .{
try dg.llvmType(ptr_type),
- try dg.llvmType(Type.initTag(.usize)),
+ try dg.llvmType(Type.usize),
};
return dg.context.structType(&fields, fields.len, .False);
} else {
@@ -780,10 +780,14 @@ pub const DeclGen = struct {
return llvm_struct_ty;
},
.Array => {
- const elem_type = try dg.llvmType(t.elemType());
+ const elem_type = try dg.llvmType(t.childType());
const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null);
return elem_type.arrayType(@intCast(c_uint, total_len));
},
+ .Vector => {
+ const elem_type = try dg.llvmType(t.childType());
+ return elem_type.vectorType(@intCast(c_uint, t.arrayLen()));
+ },
.Optional => {
var buf: Type.Payload.ElemType = undefined;
const child_type = t.optionalChild(&buf);
@@ -966,7 +970,6 @@ pub const DeclGen = struct {
.Frame,
.AnyFrame,
- .Vector,
=> return dg.todo("implement llvmType for type '{}'", .{t}),
}
}
@@ -1062,7 +1065,7 @@ pub const DeclGen = struct {
return self.context.constStruct(&fields, fields.len, .False);
},
.int_u64 => {
- const llvm_usize = try self.llvmType(Type.initTag(.usize));
+ const llvm_usize = try self.llvmType(Type.usize);
const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False);
return llvm_int.constIntToPtr(try self.llvmType(tv.ty));
},
@@ -1295,7 +1298,7 @@ pub const DeclGen = struct {
.val = tv.val,
}),
try self.genTypedValue(.{
- .ty = Type.initTag(.usize),
+ .ty = Type.usize,
.val = Value.initPayload(&slice_len.base),
}),
};
@@ -1470,6 +1473,8 @@ pub const FuncGen = struct {
.shl => try self.airShl(inst),
.shl_sat => try self.airShlSat(inst),
.shl_exact => try self.airShlExact(inst),
+ .min => try self.airMin(inst),
+ .max => try self.airMax(inst),
.bit_and, .bool_and => try self.airAnd(inst),
.bit_or, .bool_or => try self.airOr(inst),
@@ -2356,6 +2361,32 @@ pub const FuncGen = struct {
return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
}
+ fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const scalar_ty = self.air.typeOfIndex(inst).scalarType();
+
+ if (scalar_ty.isAnyFloat()) return self.builder.buildMinNum(lhs, rhs, "");
+ if (scalar_ty.isSignedInt()) return self.builder.buildSMin(lhs, rhs, "");
+ return self.builder.buildUMin(lhs, rhs, "");
+ }
+
+ fn airMax(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const scalar_ty = self.air.typeOfIndex(inst).scalarType();
+
+ if (scalar_ty.isAnyFloat()) return self.builder.buildMaxNum(lhs, rhs, "");
+ if (scalar_ty.isSignedInt()) return self.builder.buildSMax(lhs, rhs, "");
+ return self.builder.buildUMax(lhs, rhs, "");
+ }
+
fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
@@ -2705,15 +2736,48 @@ pub const FuncGen = struct {
}
fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
+ const operand_ty = self.air.typeOf(ty_op.operand);
const inst_ty = self.air.typeOfIndex(inst);
- const dest_type = try self.dg.llvmType(inst_ty);
+ const llvm_dest_ty = try self.dg.llvmType(inst_ty);
+
+ // TODO look into pulling this logic out into a different AIR instruction than bitcast
+ if (operand_ty.zigTypeTag() == .Vector and inst_ty.zigTypeTag() == .Array) {
+ const target = self.dg.module.getTarget();
+ const elem_ty = operand_ty.childType();
+ if (!isByRef(inst_ty)) {
+ return self.dg.todo("implement bitcast vector to non-ref array", .{});
+ }
+ const array_ptr = self.buildAlloca(llvm_dest_ty);
+ const bitcast_ok = elem_ty.bitSize(target) == elem_ty.abiSize(target) * 8;
+ if (bitcast_ok) {
+ const llvm_vector_ty = try self.dg.llvmType(operand_ty);
+ const casted_ptr = self.builder.buildBitCast(array_ptr, llvm_vector_ty.pointerType(0), "");
+ _ = self.builder.buildStore(operand, casted_ptr);
+ } else {
+ // If the ABI size of the element type is not evenly divisible by size in bits;
+ // a simple bitcast will not work, and we fall back to extractelement.
+ const llvm_usize = try self.dg.llvmType(Type.usize);
+ const llvm_u32 = self.context.intType(32);
+ const zero = llvm_usize.constNull();
+ const vector_len = operand_ty.arrayLen();
+ var i: u64 = 0;
+ while (i < vector_len) : (i += 1) {
+ const index_usize = llvm_usize.constInt(i, .False);
+ const index_u32 = llvm_u32.constInt(i, .False);
+ const indexes: [2]*const llvm.Value = .{ zero, index_usize };
+ const elem_ptr = self.builder.buildInBoundsGEP(array_ptr, &indexes, indexes.len, "");
+ const elem = self.builder.buildExtractElement(operand, index_u32, "");
+ _ = self.builder.buildStore(elem, elem_ptr);
+ }
+ }
+ return array_ptr;
+ }
- return self.builder.buildBitCast(operand, dest_type, "");
+ return self.builder.buildBitCast(operand, llvm_dest_ty, "");
}
fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
@@ -2906,7 +2970,7 @@ pub const FuncGen = struct {
}
// It's a pointer but we need to treat it as an int.
- const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize));
+ const usize_llvm_ty = try self.dg.llvmType(Type.usize);
const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), "");
const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, "");
const uncasted_result = self.builder.buildAtomicRmw(
src/Air.zig
@@ -107,6 +107,18 @@ pub const Inst = struct {
/// The lhs is the pointer, rhs is the offset. Result type is the same as lhs.
/// Uses the `bin_op` field.
ptr_sub,
+ /// Given two operands which can be floats, integers, or vectors, returns the
+ /// greater of the operands. For vectors it operates element-wise.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ max,
+ /// Given two operands which can be floats, integers, or vectors, returns the
+ /// lesser of the operands. For vectors it operates element-wise.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ min,
/// Allocates stack local memory.
/// Uses the `ty` field.
alloc,
@@ -640,6 +652,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.shl,
.shl_exact,
.shl_sat,
+ .min,
+ .max,
=> return air.typeOf(datas[inst].bin_op.lhs),
.cmp_lt,
src/codegen.zig
@@ -839,6 +839,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.mod => try self.airMod(inst),
.shl, .shl_exact => try self.airShl(inst),
.shl_sat => try self.airShlSat(inst),
+ .min => try self.airMin(inst),
+ .max => try self.airMax(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -1299,6 +1301,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
+ fn airMin(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement min for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
+ fn airMax(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement max for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airAdd(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
src/Liveness.zig
@@ -264,6 +264,8 @@ fn analyzeInst(
.atomic_store_release,
.atomic_store_seq_cst,
.set_union_tag,
+ .min,
+ .max,
=> {
const o = inst_datas[inst].bin_op;
return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
src/print_air.zig
@@ -138,6 +138,8 @@ const Writer = struct {
.shl_sat,
.shr,
.set_union_tag,
+ .min,
+ .max,
=> try w.writeBinOp(s, inst),
.is_null,
src/Sema.zig
@@ -614,8 +614,6 @@ pub fn analyzeBody(
.builtin_call => try sema.zirBuiltinCall(block, inst),
.field_ptr_type => try sema.zirFieldPtrType(block, inst),
.field_parent_ptr => try sema.zirFieldParentPtr(block, inst),
- .maximum => try sema.zirMaximum(block, inst),
- .minimum => try sema.zirMinimum(block, inst),
.builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst),
.@"resume" => try sema.zirResume(block, inst),
.@"await" => try sema.zirAwait(block, inst, false),
@@ -654,6 +652,9 @@ pub fn analyzeBody(
.subwrap => try sema.zirArithmetic(block, inst, .subwrap),
.sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
+ .maximum => try sema.zirMinMax(block, inst, .max),
+ .minimum => try sema.zirMinMax(block, inst, .min),
+
.shl => try sema.zirShl(block, inst, .shl),
.shl_exact => try sema.zirShl(block, inst, .shl_exact),
.shl_sat => try sema.zirShl(block, inst, .shl_sat),
@@ -9018,6 +9019,12 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
.Void => return Air.Inst.Ref.void_type,
.Bool => return Air.Inst.Ref.bool_type,
.NoReturn => return Air.Inst.Ref.noreturn_type,
+ .ComptimeFloat => return Air.Inst.Ref.comptime_float_type,
+ .ComptimeInt => return Air.Inst.Ref.comptime_int_type,
+ .Undefined => return Air.Inst.Ref.undefined_type,
+ .Null => return Air.Inst.Ref.null_type,
+ .AnyFrame => return Air.Inst.Ref.anyframe_type,
+ .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
.Int => {
const struct_val = union_val.val.castTag(.@"struct").?.data;
// TODO use reflection instead of magic numbers here
@@ -9032,14 +9039,23 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
};
return sema.addType(ty);
},
+ .Vector => {
+ const struct_val = union_val.val.castTag(.@"struct").?.data;
+ // TODO use reflection instead of magic numbers here
+ const len_val = struct_val[0];
+ const child_val = struct_val[1];
+
+ const len = len_val.toUnsignedInt();
+ var buffer: Value.ToTypeBuffer = undefined;
+ const child_ty = child_val.toType(&buffer);
+
+ const ty = try Type.vector(sema.arena, len, child_ty);
+ return sema.addType(ty);
+ },
.Float => return sema.fail(block, src, "TODO: Sema.zirReify for Float", .{}),
.Pointer => return sema.fail(block, src, "TODO: Sema.zirReify for Pointer", .{}),
.Array => return sema.fail(block, src, "TODO: Sema.zirReify for Array", .{}),
.Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}),
- .ComptimeFloat => return Air.Inst.Ref.comptime_float_type,
- .ComptimeInt => return Air.Inst.Ref.comptime_int_type,
- .Undefined => return Air.Inst.Ref.undefined_type,
- .Null => return Air.Inst.Ref.null_type,
.Optional => return sema.fail(block, src, "TODO: Sema.zirReify for Optional", .{}),
.ErrorUnion => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorUnion", .{}),
.ErrorSet => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorSet", .{}),
@@ -9049,9 +9065,6 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
.BoundFn => @panic("TODO delete BoundFn from the language"),
.Opaque => return sema.fail(block, src, "TODO: Sema.zirReify for Opaque", .{}),
.Frame => return sema.fail(block, src, "TODO: Sema.zirReify for Frame", .{}),
- .AnyFrame => return Air.Inst.Ref.anyframe_type,
- .Vector => return sema.fail(block, src, "TODO: Sema.zirReify for Vector", .{}),
- .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
}
}
@@ -9379,9 +9392,23 @@ fn checkFloatType(
) CompileError!void {
switch (ty.zigTypeTag()) {
.ComptimeFloat, .Float => {},
- else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{
- ty,
- }),
+ else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty}),
+ }
+}
+
+fn checkNumericType(
+ sema: *Sema,
+ block: *Block,
+ ty_src: LazySrcLoc,
+ ty: Type,
+) CompileError!void {
+ switch (ty.zigTypeTag()) {
+ .ComptimeFloat, .Float, .ComptimeInt, .Int => {},
+ .Vector => switch (ty.childType().zigTypeTag()) {
+ .ComptimeFloat, .Float, .ComptimeInt, .Int => {},
+ else => |t| return sema.fail(block, ty_src, "expected number, found '{}'", .{t}),
+ },
+ else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty}),
}
}
@@ -9474,6 +9501,82 @@ fn checkComptimeVarStore(
}
}
+const SimdBinOp = struct {
+ len: ?u64,
+ /// Coerced to `result_ty`.
+ lhs: Air.Inst.Ref,
+ /// Coerced to `result_ty`.
+ rhs: Air.Inst.Ref,
+ lhs_val: ?Value,
+ rhs_val: ?Value,
+ /// Only different than `scalar_ty` when it is a vector operation.
+ result_ty: Type,
+ scalar_ty: Type,
+};
+
+fn checkSimdBinOp(
+ sema: *Sema,
+ block: *Block,
+ src: LazySrcLoc,
+ uncasted_lhs: Air.Inst.Ref,
+ uncasted_rhs: Air.Inst.Ref,
+ lhs_src: LazySrcLoc,
+ rhs_src: LazySrcLoc,
+) CompileError!SimdBinOp {
+ const lhs_ty = sema.typeOf(uncasted_lhs);
+ const rhs_ty = sema.typeOf(uncasted_rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+
+ var vec_len: ?u64 = null;
+ if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
+ const lhs_len = lhs_ty.arrayLen();
+ const rhs_len = rhs_ty.arrayLen();
+ if (lhs_len != rhs_len) {
+ const msg = msg: {
+ const msg = try sema.errMsg(block, src, "vector length mismatch", .{});
+ errdefer msg.destroy(sema.gpa);
+ try sema.errNote(block, lhs_src, msg, "length {d} here", .{lhs_len});
+ try sema.errNote(block, rhs_src, msg, "length {d} here", .{rhs_len});
+ break :msg msg;
+ };
+ return sema.failWithOwnedErrorMsg(msg);
+ }
+ vec_len = lhs_len;
+ } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
+ const msg = msg: {
+ const msg = try sema.errMsg(block, src, "mixed scalar and vector operands: {} and {}", .{
+ lhs_ty, rhs_ty,
+ });
+ errdefer msg.destroy(sema.gpa);
+ if (lhs_zig_ty_tag == .Vector) {
+ try sema.errNote(block, lhs_src, msg, "vector here", .{});
+ try sema.errNote(block, rhs_src, msg, "scalar here", .{});
+ } else {
+ try sema.errNote(block, lhs_src, msg, "scalar here", .{});
+ try sema.errNote(block, rhs_src, msg, "vector here", .{});
+ }
+ break :msg msg;
+ };
+ return sema.failWithOwnedErrorMsg(msg);
+ }
+ const result_ty = try sema.resolvePeerTypes(block, src, &.{ uncasted_lhs, uncasted_rhs }, .{
+ .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+ });
+ const lhs = try sema.coerce(block, result_ty, uncasted_lhs, lhs_src);
+ const rhs = try sema.coerce(block, result_ty, uncasted_rhs, rhs_src);
+
+ return SimdBinOp{
+ .len = vec_len,
+ .lhs = lhs,
+ .rhs = rhs,
+ .lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs),
+ .rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs),
+ .result_ty = result_ty,
+ .scalar_ty = result_ty.scalarType(),
+ };
+}
+
fn resolveExportOptions(
sema: *Sema,
block: *Block,
@@ -9744,8 +9847,8 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
.Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target),
.Or => try stored_val.bitwiseOr (operand_val, sema.arena),
.Xor => try stored_val.bitwiseXor (operand_val, sema.arena),
- .Max => try stored_val.numberMax (operand_val, sema.arena),
- .Min => try stored_val.numberMin (operand_val, sema.arena),
+ .Max => try stored_val.numberMax (operand_val),
+ .Min => try stored_val.numberMin (operand_val),
// zig fmt: on
};
try sema.storePtrVal(block, src, ptr_val, new_val, operand_ty);
@@ -9826,10 +9929,62 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
return sema.fail(block, src, "TODO: Sema.zirFieldParentPtr", .{});
}
-fn zirMaximum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirMinMax(
+ sema: *Sema,
+ block: *Block,
+ inst: Zir.Inst.Index,
+ air_tag: Air.Inst.Tag,
+) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = inst_data.src();
- return sema.fail(block, src, "TODO: Sema.zirMaximum", .{});
+ const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const lhs = sema.resolveInst(extra.lhs);
+ const rhs = sema.resolveInst(extra.rhs);
+ try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs));
+ try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs));
+ const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src);
+
+ // TODO @maximum(max_int, undefined) should return max_int
+
+ const runtime_src = if (simd_op.lhs_val) |lhs_val| rs: {
+ if (lhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
+
+ const rhs_val = simd_op.rhs_val orelse break :rs rhs_src;
+
+ if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
+
+ const opFunc = switch (air_tag) {
+ .min => Value.numberMin,
+ .max => Value.numberMax,
+ else => unreachable,
+ };
+ const vec_len = simd_op.len orelse {
+ const result_val = try opFunc(lhs_val, rhs_val);
+ return sema.addConstant(simd_op.result_ty, result_val);
+ };
+ var lhs_buf: Value.ElemValueBuffer = undefined;
+ var rhs_buf: Value.ElemValueBuffer = undefined;
+ const elems = try sema.arena.alloc(Value, vec_len);
+ for (elems) |*elem, i| {
+ const lhs_elem_val = lhs_val.elemValueBuffer(i, &lhs_buf);
+ const rhs_elem_val = rhs_val.elemValueBuffer(i, &rhs_buf);
+ elem.* = try opFunc(lhs_elem_val, rhs_elem_val);
+ }
+ return sema.addConstant(
+ simd_op.result_ty,
+ try Value.Tag.array.create(sema.arena, elems),
+ );
+ } else rs: {
+ if (simd_op.rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) return sema.addConstUndef(simd_op.result_ty);
+ }
+ break :rs lhs_src;
+ };
+
+ try sema.requireRuntimeBlock(block, runtime_src);
+ return block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs);
}
fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
@@ -9943,12 +10098,6 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
});
}
-fn zirMinimum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
- const src = inst_data.src();
- return sema.fail(block, src, "TODO: Sema.zirMinimum", .{});
-}
-
fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
@@ -10453,12 +10602,7 @@ fn fieldVal(
const result_ty = object_ty.slicePtrFieldType(buf);
if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| {
if (val.isUndef()) return sema.addConstUndef(result_ty);
- return sema.fail(
- block,
- field_name_src,
- "TODO implement comptime slice ptr",
- .{},
- );
+ return sema.addConstant(result_ty, val.slicePtr());
}
try sema.requireRuntimeBlock(block, src);
return block.addTyOp(.slice_ptr, result_ty, object);
@@ -11464,6 +11608,10 @@ fn coerce(
.Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),
else => {},
},
+ .Array => switch (inst_ty.zigTypeTag()) {
+ .Vector => return sema.coerceVectorToArray(block, dest_ty, dest_ty_src, inst, inst_src),
+ else => {},
+ },
else => {},
}
@@ -12045,6 +12193,48 @@ fn coerceEnumToUnion(
return sema.failWithOwnedErrorMsg(msg);
}
+fn coerceVectorToArray(
+ sema: *Sema,
+ block: *Block,
+ array_ty: Type,
+ array_ty_src: LazySrcLoc,
+ vector: Air.Inst.Ref,
+ vector_src: LazySrcLoc,
+) !Air.Inst.Ref {
+ const vector_ty = sema.typeOf(vector);
+ const array_len = array_ty.arrayLen();
+ const vector_len = vector_ty.arrayLen();
+ if (array_len != vector_len) {
+ const msg = msg: {
+ const msg = try sema.errMsg(block, vector_src, "expected {}, found {}", .{
+ array_ty, vector_ty,
+ });
+ errdefer msg.destroy(sema.gpa);
+ try sema.errNote(block, array_ty_src, msg, "array has length {d}", .{array_len});
+ try sema.errNote(block, vector_src, msg, "vector has length {d}", .{vector_len});
+ break :msg msg;
+ };
+ return sema.failWithOwnedErrorMsg(msg);
+ }
+
+ const target = sema.mod.getTarget();
+ const array_elem_ty = array_ty.childType();
+ const vector_elem_ty = vector_ty.childType();
+ const in_memory_result = coerceInMemoryAllowed(array_elem_ty, vector_elem_ty, false, target);
+ if (in_memory_result != .ok) {
+ // TODO recursive error notes for coerceInMemoryAllowed failure
+ return sema.fail(block, vector_src, "expected {}, found {}", .{ array_ty, vector_ty });
+ }
+
+ if (try sema.resolveMaybeUndefVal(block, vector_src, vector)) |vector_val| {
+ // These types share the same comptime value representation.
+ return sema.addConstant(array_ty, vector_val);
+ }
+
+ try sema.requireRuntimeBlock(block, vector_src);
+ return block.addTyOp(.bitcast, array_ty, vector);
+}
+
fn analyzeDeclVal(
sema: *Sema,
block: *Block,
src/type.zig
@@ -2517,6 +2517,14 @@ pub const Type = extern union {
};
}
+ /// For vectors, returns the element type. Otherwise returns self.
+ pub fn scalarType(ty: Type) Type {
+ return switch (ty.zigTypeTag()) {
+ .Vector => ty.childType(),
+ else => ty,
+ };
+ }
+
/// Asserts that the type is an optional.
/// Resulting `Type` will have inner memory referencing `buf`.
pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type {
@@ -4017,6 +4025,13 @@ pub const Type = extern union {
});
}
+ pub fn vector(arena: *Allocator, len: u64, elem_type: Type) Allocator.Error!Type {
+ return Tag.vector.create(arena, .{
+ .len = len,
+ .elem_type = elem_type,
+ });
+ }
+
pub fn smallestUnsignedBits(max: u64) u16 {
if (max == 0) return 0;
const base = std.math.log2(max);
src/value.zig
@@ -1626,6 +1626,14 @@ pub const Value = extern union {
};
}
+ pub fn slicePtr(val: Value) Value {
+ return switch (val.tag()) {
+ .slice => val.castTag(.slice).?.data.ptr,
+ .decl_ref, .decl_ref_mut => val,
+ else => unreachable,
+ };
+ }
+
pub fn sliceLen(val: Value) u64 {
return switch (val.tag()) {
.slice => val.castTag(.slice).?.data.len.toUnsignedInt(),
@@ -2042,63 +2050,27 @@ pub const Value = extern union {
}
/// Supports both floats and ints; handles undefined.
- pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {
- if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
-
- // TODO is this a performance issue? maybe we should try the operation without
- // resorting to BigInt first.
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = lhs.toBigInt(&lhs_space);
- const rhs_bigint = rhs.toBigInt(&rhs_space);
- const limbs = try arena.alloc(
- std.math.big.Limb,
- std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
- );
- var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
-
- switch (lhs_bigint.order(rhs_bigint)) {
- .lt => result_bigint.copy(rhs_bigint),
- .gt, .eq => result_bigint.copy(lhs_bigint),
- }
-
- const result_limbs = result_bigint.limbs[0..result_bigint.len];
-
- if (result_bigint.positive) {
- return Value.Tag.int_big_positive.create(arena, result_limbs);
- } else {
- return Value.Tag.int_big_negative.create(arena, result_limbs);
- }
+ pub fn numberMax(lhs: Value, rhs: Value) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return undef;
+ if (lhs.isNan()) return rhs;
+ if (rhs.isNan()) return lhs;
+
+ return switch (order(lhs, rhs)) {
+ .lt => rhs,
+ .gt, .eq => lhs,
+ };
}
/// Supports both floats and ints; handles undefined.
- pub fn numberMin(lhs: Value, rhs: Value, arena: *Allocator) !Value {
- if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
-
- // TODO is this a performance issue? maybe we should try the operation without
- // resorting to BigInt first.
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = lhs.toBigInt(&lhs_space);
- const rhs_bigint = rhs.toBigInt(&rhs_space);
- const limbs = try arena.alloc(
- std.math.big.Limb,
- std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
- );
- var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
-
- switch (lhs_bigint.order(rhs_bigint)) {
- .lt => result_bigint.copy(lhs_bigint),
- .gt, .eq => result_bigint.copy(rhs_bigint),
- }
-
- const result_limbs = result_bigint.limbs[0..result_bigint.len];
-
- if (result_bigint.positive) {
- return Value.Tag.int_big_positive.create(arena, result_limbs);
- } else {
- return Value.Tag.int_big_negative.create(arena, result_limbs);
- }
+ pub fn numberMin(lhs: Value, rhs: Value) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return undef;
+ if (lhs.isNan()) return rhs;
+ if (rhs.isNan()) return lhs;
+
+ return switch (order(lhs, rhs)) {
+ .lt => lhs,
+ .gt, .eq => rhs,
+ };
}
/// operands must be integers; handles undefined.
@@ -2327,6 +2299,17 @@ pub const Value = extern union {
}
}
+ /// Returns true if the value is a floating point type and is NaN. Returns false otherwise.
+ pub fn isNan(val: Value) bool {
+ return switch (val.tag()) {
+ .float_16 => std.math.isNan(val.castTag(.float_16).?.data),
+ .float_32 => std.math.isNan(val.castTag(.float_32).?.data),
+ .float_64 => std.math.isNan(val.castTag(.float_64).?.data),
+ .float_128 => std.math.isNan(val.castTag(.float_128).?.data),
+ else => false,
+ };
+ }
+
pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
_ = lhs;
_ = rhs;
src/Zir.zig
@@ -906,9 +906,6 @@ pub const Inst = struct {
/// Implements the `@fieldParentPtr` builtin.
/// Uses the `pl_node` union field with payload `FieldParentPtr`.
field_parent_ptr,
- /// Implements the `@maximum` builtin.
- /// Uses the `pl_node` union field with payload `Bin`
- maximum,
/// Implements the `@memcpy` builtin.
/// Uses the `pl_node` union field with payload `Memcpy`.
memcpy,
@@ -918,6 +915,9 @@ pub const Inst = struct {
/// Implements the `@minimum` builtin.
/// Uses the `pl_node` union field with payload `Bin`
minimum,
+ /// Implements the `@maximum` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`
+ maximum,
/// Implements the `@asyncCall` builtin.
/// Uses the `pl_node` union field with payload `AsyncCall`.
builtin_async_call,
test/behavior/maximum_minimum.zig
@@ -8,8 +8,8 @@ const Vector = std.meta.Vector;
test "@maximum" {
const S = struct {
fn doTheTest() !void {
- try expectEqual(@as(i32, 10), @maximum(@as(i32, -3), @as(i32, 10)));
- try expectEqual(@as(f32, 3.2), @maximum(@as(f32, 3.2), @as(f32, 0.68)));
+ try expect(@as(i32, 10) == @maximum(@as(i32, -3), @as(i32, 10)));
+ try expect(@as(f32, 3.2) == @maximum(@as(f32, 3.2), @as(f32, 0.68)));
var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
@@ -34,8 +34,8 @@ test "@maximum" {
test "@minimum" {
const S = struct {
fn doTheTest() !void {
- try expectEqual(@as(i32, -3), @minimum(@as(i32, -3), @as(i32, 10)));
- try expectEqual(@as(f32, 0.68), @minimum(@as(f32, 3.2), @as(f32, 0.68)));
+ try expect(@as(i32, -3) == @minimum(@as(i32, -3), @as(i32, 10)));
+ try expect(@as(f32, 0.68) == @minimum(@as(f32, 3.2), @as(f32, 0.68)));
var a: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
var b: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
test/behavior.zig
@@ -24,6 +24,7 @@ test {
_ = @import("behavior/generics.zig");
_ = @import("behavior/if.zig");
_ = @import("behavior/math.zig");
+ _ = @import("behavior/maximum_minimum.zig");
_ = @import("behavior/member_func.zig");
_ = @import("behavior/optional.zig");
_ = @import("behavior/pointers.zig");
@@ -130,7 +131,6 @@ test {
_ = @import("behavior/inttoptr.zig");
_ = @import("behavior/ir_block_deps.zig");
_ = @import("behavior/math_stage1.zig");
- _ = @import("behavior/maximum_minimum.zig");
_ = @import("behavior/merge_error_sets.zig");
_ = @import("behavior/misc.zig");
_ = @import("behavior/muladd.zig");