Commit 93b854eb74
Changed files (19)
lib
std
src
arch
codegen
test
lib/std/testing.zig
@@ -103,11 +103,13 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) !void {
.Array => |array| try expectEqualSlices(array.child, &expected, &actual),
- .Vector => |vectorType| {
+ .Vector => |info| {
var i: usize = 0;
- while (i < vectorType.len) : (i += 1) {
+ while (i < info.len) : (i += 1) {
if (!std.meta.eql(expected[i], actual[i])) {
- std.debug.print("index {} incorrect. expected {}, found {}\n", .{ i, expected[i], actual[i] });
+ std.debug.print("index {} incorrect. expected {}, found {}\n", .{
+ i, expected[i], actual[i],
+ });
return error.TestExpectedEqual;
}
}
src/arch/aarch64/CodeGen.zig
@@ -593,6 +593,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.popcount => try self.airPopcount(inst),
.tag_name => try self.airTagName(inst),
.error_name => try self.airErrorName(inst),
+ .splat => try self.airSplat(inst),
+ .vector_init => try self.airVectorInit(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1648,7 +1650,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void {
break :result info.return_value;
};
- if (args.len <= Liveness.bpi - 2) {
+ if (args.len + 1 <= Liveness.bpi - 1) {
var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
buf[0] = callee;
std.mem.copy(Air.Inst.Ref, buf[1..], args);
@@ -2567,6 +2569,34 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
+fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for {}", .{self.target.cpu.arch});
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+}
+
+fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
+ const result: MCValue = res: {
+ if (self.liveness.isUnused(inst)) break :res MCValue.dead;
+ return self.fail("TODO implement airVectorInit for {}", .{self.target.cpu.arch});
+ };
+
+ if (elements.len <= Liveness.bpi - 1) {
+ var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
+ std.mem.copy(Air.Inst.Ref, &buf, elements);
+ return self.finishAir(inst, result, buf);
+ }
+ var bt = try self.iterateBigTomb(inst, elements.len);
+ for (elements) |elem| {
+ bt.feed(elem);
+ }
+ return bt.finishAir(result);
+}
+
fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
// First section of indexes correspond to a set number of constant values.
const ref_int = @enumToInt(inst);
src/arch/arm/CodeGen.zig
@@ -584,6 +584,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.popcount => try self.airPopcount(inst),
.tag_name => try self.airTagName(inst),
.error_name => try self.airErrorName(inst),
+ .splat => try self.airSplat(inst),
+ .vector_init => try self.airVectorInit(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -3665,6 +3667,34 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
+fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for arm", .{});
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+}
+
+fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
+ const result: MCValue = res: {
+ if (self.liveness.isUnused(inst)) break :res MCValue.dead;
+ return self.fail("TODO implement airVectorInit for arm", .{});
+ };
+
+ if (elements.len <= Liveness.bpi - 1) {
+ var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
+ std.mem.copy(Air.Inst.Ref, &buf, elements);
+ return self.finishAir(inst, result, buf);
+ }
+ var bt = try self.iterateBigTomb(inst, elements.len);
+ for (elements) |elem| {
+ bt.feed(elem);
+ }
+ return bt.finishAir(result);
+}
+
fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
// First section of indexes correspond to a set number of constant values.
const ref_int = @enumToInt(inst);
src/arch/riscv64/CodeGen.zig
@@ -572,6 +572,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.popcount => try self.airPopcount(inst),
.tag_name => try self.airTagName(inst),
.error_name => try self.airErrorName(inst),
+ .splat => try self.airSplat(inst),
+ .vector_init => try self.airVectorInit(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2066,6 +2068,34 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
+fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for riscv64", .{});
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+}
+
+fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
+ const result: MCValue = res: {
+ if (self.liveness.isUnused(inst)) break :res MCValue.dead;
+ return self.fail("TODO implement airVectorInit for riscv64", .{});
+ };
+
+ if (elements.len <= Liveness.bpi - 1) {
+ var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
+ std.mem.copy(Air.Inst.Ref, &buf, elements);
+ return self.finishAir(inst, result, buf);
+ }
+ var bt = try self.iterateBigTomb(inst, elements.len);
+ for (elements) |elem| {
+ bt.feed(elem);
+ }
+ return bt.finishAir(result);
+}
+
fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
// First section of indexes correspond to a set number of constant values.
const ref_int = @enumToInt(inst);
src/arch/wasm/CodeGen.zig
@@ -3224,6 +3224,29 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
return result;
}
+fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+ if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
+
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const operand = self.resolveInst(ty_op.operand);
+
+ _ = ty_op;
+ _ = operand;
+ return self.fail("TODO: Implement wasm airSplat", .{});
+}
+
+fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
+ if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
+
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
+
+ _ = elements;
+ return self.fail("TODO: Wasm backend: implement airVectorInit", .{});
+}
+
fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std.math.CompareOperator) InnerError!WValue {
assert(operand_ty.hasCodeGenBits());
assert(op == .eq or op == .neq);
src/arch/x86_64/CodeGen.zig
@@ -635,7 +635,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.ctz => try self.airCtz(inst),
.popcount => try self.airPopcount(inst),
.tag_name => try self.airTagName(inst),
- .error_name, => try self.airErrorName(inst),
+ .error_name => try self.airErrorName(inst),
+ .splat => try self.airSplat(inst),
+ .vector_init => try self.airVectorInit(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -3659,6 +3661,34 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ un_op, .none, .none });
}
+fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for x86_64", .{});
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+}
+
+fn airVectorInit(self: *Self, inst: Air.Inst.Index) !void {
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
+ const result: MCValue = res: {
+ if (self.liveness.isUnused(inst)) break :res MCValue.dead;
+ return self.fail("TODO implement airVectorInit for x86_64", .{});
+ };
+
+ if (elements.len <= Liveness.bpi - 1) {
+ var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1);
+ std.mem.copy(Air.Inst.Ref, &buf, elements);
+ return self.finishAir(inst, result, buf);
+ }
+ var bt = try self.iterateBigTomb(inst, elements.len);
+ for (elements) |elem| {
+ bt.feed(elem);
+ }
+ return bt.finishAir(result);
+}
+
fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
// First section of indexes correspond to a set number of constant values.
const ref_int = @enumToInt(inst);
src/codegen/llvm/bindings.zig
@@ -820,6 +820,9 @@ pub const Builder = opaque {
pub const setCurrentDebugLocation2 = LLVMSetCurrentDebugLocation2;
extern fn LLVMSetCurrentDebugLocation2(Builder: *const Builder, Loc: *Metadata) void;
+
+ pub const buildShuffleVector = LLVMBuildShuffleVector;
+ extern fn LLVMBuildShuffleVector(*const Builder, V1: *const Value, V2: *const Value, Mask: *const Value, Name: [*:0]const u8) *const Value;
};
pub const DIScope = opaque {};
src/codegen/c.zig
@@ -1245,6 +1245,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.popcount => try airBuiltinCall(f, inst, "popcount"),
.tag_name => try airTagName(f, inst),
.error_name => try airErrorName(f, inst),
+ .splat => try airSplat(f, inst),
+ .vector_init => try airVectorInit(f, inst),
.int_to_float,
.float_to_int,
@@ -3015,6 +3017,39 @@ fn airErrorName(f: *Function, inst: Air.Inst.Index) !CValue {
return f.fail("TODO: C backend: implement airErrorName", .{});
}
+fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue {
+ if (f.liveness.isUnused(inst)) return CValue.none;
+
+ const inst_ty = f.air.typeOfIndex(inst);
+ const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+ const operand = try f.resolveInst(ty_op.operand);
+ const writer = f.object.writer();
+ const local = try f.allocLocal(inst_ty, .Const);
+ try writer.writeAll(" = ");
+
+ _ = operand;
+ _ = local;
+ return f.fail("TODO: C backend: implement airSplat", .{});
+}
+
+fn airVectorInit(f: *Function, inst: Air.Inst.Index) !CValue {
+ if (f.liveness.isUnused(inst)) return CValue.none;
+
+ const inst_ty = f.air.typeOfIndex(inst);
+ const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+ const vector_ty = f.air.getRefType(ty_pl.ty);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const elements = @bitCast([]const Air.Inst.Ref, f.air.extra[ty_pl.payload..][0..len]);
+
+ const writer = f.object.writer();
+ const local = try f.allocLocal(inst_ty, .Const);
+ try writer.writeAll(" = ");
+
+ _ = elements;
+ _ = local;
+ return f.fail("TODO: C backend: implement airVectorInit", .{});
+}
+
fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
return switch (order) {
.Unordered => "memory_order_relaxed",
src/codegen/llvm.zig
@@ -2089,6 +2089,8 @@ pub const FuncGen = struct {
.popcount => try self.airPopCount(inst, "ctpop"),
.tag_name => try self.airTagName(inst),
.error_name => try self.airErrorName(inst),
+ .splat => try self.airSplat(inst),
+ .vector_init => try self.airVectorInit(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2612,15 +2614,19 @@ pub const FuncGen = struct {
const array_ty = self.air.typeOf(bin_op.lhs);
const array_llvm_val = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
- assert(isByRef(array_ty));
- const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
- const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_val, &indices, indices.len, "");
- const elem_ty = array_ty.childType();
- if (isByRef(elem_ty)) {
- return elem_ptr;
- } else {
- return self.builder.buildLoad(elem_ptr, "");
+ if (isByRef(array_ty)) {
+ const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
+ const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_val, &indices, indices.len, "");
+ const elem_ty = array_ty.childType();
+ if (isByRef(elem_ty)) {
+ return elem_ptr;
+ } else {
+ return self.builder.buildLoad(elem_ptr, "");
+ }
}
+
+ // This branch can be reached for vectors, which are always by-value.
+ return self.builder.buildExtractElement(array_llvm_val, rhs, "");
}
fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
@@ -4163,11 +4169,20 @@ pub const FuncGen = struct {
const operand = try self.resolveInst(ty_op.operand);
const target = self.dg.module.getTarget();
const bits = operand_ty.intInfo(target).bits;
+ const vec_len: ?u32 = switch (operand_ty.zigTypeTag()) {
+ .Vector => @intCast(u32, operand_ty.arrayLen()),
+ else => null,
+ };
var fn_name_buf: [100]u8 = undefined;
- const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
- prefix, bits,
- }) catch unreachable;
+ const llvm_fn_name = if (vec_len) |len|
+ std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.v{d}i{d}", .{
+ prefix, len, bits,
+ }) catch unreachable
+ else
+ std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
+ prefix, bits,
+ }) catch unreachable;
const llvm_i1 = self.context.intType(1);
const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
@@ -4350,6 +4365,43 @@ pub const FuncGen = struct {
return self.builder.buildLoad(error_name_ptr, "");
}
+ fn airSplat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const scalar = try self.resolveInst(ty_op.operand);
+ const scalar_ty = self.air.typeOf(ty_op.operand);
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const scalar_llvm_ty = try self.dg.llvmType(scalar_ty);
+ const op_llvm_ty = scalar_llvm_ty.vectorType(1);
+ const u32_llvm_ty = self.context.intType(32);
+ const mask_llvm_ty = u32_llvm_ty.vectorType(len);
+ const undef_vector = op_llvm_ty.getUndef();
+ const u32_zero = u32_llvm_ty.constNull();
+ const op_vector = self.builder.buildInsertElement(undef_vector, scalar, u32_zero, "");
+ return self.builder.buildShuffleVector(op_vector, undef_vector, mask_llvm_ty.constNull(), "");
+ }
+
+ fn airVectorInit(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+ const vector_ty = self.air.typeOfIndex(inst);
+ const len = vector_ty.arrayLen();
+ const elements = @bitCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
+ const llvm_vector_ty = try self.dg.llvmType(vector_ty);
+ const llvm_u32 = self.context.intType(32);
+
+ var vector = llvm_vector_ty.getUndef();
+ for (elements) |elem, i| {
+ const index_u32 = llvm_u32.constInt(i, .False);
+ const llvm_elem = try self.resolveInst(elem);
+ vector = self.builder.buildInsertElement(vector, llvm_elem, index_u32, "");
+ }
+ return vector;
+ }
+
fn getErrorNameTable(self: *FuncGen) !*const llvm.Value {
if (self.dg.object.error_name_table) |table| {
return table;
src/Air.zig
@@ -426,7 +426,8 @@ pub const Inst = struct {
/// Given a pointer to a slice, return a pointer to the pointer of the slice.
/// Uses the `ty_op` field.
ptr_slice_ptr_ptr,
- /// Given an array value and element index, return the element value at that index.
+ /// Given an (array value or vector value) and element index,
+ /// return the element value at that index.
/// Result type is the element type of the array operand.
/// Uses the `bin_op` field.
array_elem_val,
@@ -455,6 +456,10 @@ pub const Inst = struct {
/// Given an integer operand, return the float with the closest mathematical meaning.
/// Uses the `ty_op` field.
int_to_float,
+ /// Given an integer, bool, float, or pointer operand, return a vector with all elements
+ /// equal to the scalar value.
+ /// Uses the `ty_op` field.
+ splat,
/// Given dest ptr, value, and len, set all elements at dest to value.
/// Result type is always void.
@@ -505,6 +510,11 @@ pub const Inst = struct {
/// Uses the `un_op` field.
error_name,
+ /// Constructs a vector value out of runtime-known elements.
+ /// Uses the `ty_pl` field, payload is index of an array of elements, each of which
+ /// is a `Ref`. Length of the array is given by the vector type.
+ vector_init,
+
pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
return switch (op) {
.lt => .cmp_lt,
@@ -756,6 +766,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.cmpxchg_weak,
.cmpxchg_strong,
.slice,
+ .vector_init,
=> return air.getRefType(datas[inst].ty_pl.ty),
.not,
@@ -785,6 +796,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.array_to_slice,
.float_to_int,
.int_to_float,
+ .splat,
.get_union_tag,
.clz,
.ctz,
src/AstGen.zig
@@ -7060,7 +7060,7 @@ fn builtinCall(
},
.splat => {
- const len = try expr(gz, scope, .{ .ty = .u32_type }, params[0]);
+ const len = try expr(gz, scope, .{ .coerced_ty = .u32_type }, params[0]);
const scalar = try expr(gz, scope, .none, params[1]);
const result = try gz.addPlNode(.splat, node, Zir.Inst.Bin{
.lhs = len,
@@ -7395,8 +7395,14 @@ fn bitBuiltin(
operand_node: Ast.Node.Index,
tag: Zir.Inst.Tag,
) InnerError!Zir.Inst.Ref {
- const int_type = try typeExpr(gz, scope, int_type_node);
- const operand = try expr(gz, scope, .{ .ty = int_type }, operand_node);
+ // The accepted proposal https://github.com/ziglang/zig/issues/6835
+ // tells us to remove the type parameter from these builtins. To stay
+ // source-compatible with stage1, we still observe the parameter here,
+ // but we do not encode it into the ZIR. To implement this proposal in
+ // stage2, only AstGen code will need to be changed.
+ _ = try typeExpr(gz, scope, int_type_node);
+
+ const operand = try expr(gz, scope, .none, operand_node);
const result = try gz.addUnNode(tag, operand, node);
return rvalue(gz, rl, result, node);
}
src/Liveness.zig
@@ -26,7 +26,8 @@ tomb_bits: []usize,
/// array. The meaning of the data depends on the AIR tag.
/// * `cond_br` - points to a `CondBr` in `extra` at this index.
/// * `switch_br` - points to a `SwitchBr` in `extra` at this index.
-/// * `asm`, `call` - the value is a set of bits which are the extra tomb bits of operands.
+/// * `asm`, `call`, `vector_init` - the value is a set of bits which are the extra tomb
+/// bits of operands.
/// The main tomb bits are still used and the extra ones are starting with the lsb of the
/// value here.
special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
@@ -316,6 +317,7 @@ fn analyzeInst(
.clz,
.ctz,
.popcount,
+ .splat,
=> {
const o = inst_datas[inst].ty_op;
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
@@ -345,7 +347,7 @@ fn analyzeInst(
const callee = inst_data.operand;
const extra = a.air.extraData(Air.Call, inst_data.payload);
const args = @bitCast([]const Air.Inst.Ref, a.air.extra[extra.end..][0..extra.data.args_len]);
- if (args.len <= bpi - 2) {
+ if (args.len + 1 <= bpi - 1) {
var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
buf[0] = callee;
std.mem.copy(Air.Inst.Ref, buf[1..], args);
@@ -363,6 +365,28 @@ fn analyzeInst(
}
return extra_tombs.finish();
},
+ .vector_init => {
+ const ty_pl = inst_datas[inst].ty_pl;
+ const vector_ty = a.air.getRefType(ty_pl.ty);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const elements = @bitCast([]const Air.Inst.Ref, a.air.extra[ty_pl.payload..][0..len]);
+
+ if (elements.len <= bpi - 1) {
+ var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
+ std.mem.copy(Air.Inst.Ref, &buf, elements);
+ return trackOperands(a, new_set, inst, main_tomb, buf);
+ }
+ var extra_tombs: ExtraTombs = .{
+ .analysis = a,
+ .new_set = new_set,
+ .inst = inst,
+ .main_tomb = main_tomb,
+ };
+ for (elements) |elem| {
+ try extra_tombs.feed(elem);
+ }
+ return extra_tombs.finish();
+ },
.struct_field_ptr, .struct_field_val => {
const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none });
src/print_air.zig
@@ -196,6 +196,7 @@ const Writer = struct {
.struct_field_ptr_index_3,
.array_to_slice,
.int_to_float,
+ .splat,
.float_to_int,
.get_union_tag,
.clz,
@@ -218,6 +219,7 @@ const Writer = struct {
.assembly => try w.writeAssembly(s, inst),
.dbg_stmt => try w.writeDbgStmt(s, inst),
.call => try w.writeCall(s, inst),
+ .vector_init => try w.writeVectorInit(s, inst),
.br => try w.writeBr(s, inst),
.cond_br => try w.writeCondBr(s, inst),
.switch_br => try w.writeSwitchBr(s, inst),
@@ -290,6 +292,20 @@ const Writer = struct {
try s.writeAll("}");
}
+ fn writeVectorInit(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+ const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
+ const vector_ty = w.air.getRefType(ty_pl.ty);
+ const len = @intCast(u32, vector_ty.arrayLen());
+ const elements = @bitCast([]const Air.Inst.Ref, w.air.extra[ty_pl.payload..][0..len]);
+
+ try s.print("{}, [", .{vector_ty});
+ for (elements) |elem, i| {
+ if (i != 0) try s.writeAll(", ");
+ try w.writeOperand(s, inst, i, elem);
+ }
+ try s.writeAll("]");
+ }
+
fn writeStructField(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
const extra = w.air.extraData(Air.StructField, ty_pl.payload).data;
src/Sema.zig
@@ -379,6 +379,26 @@ pub const Block = struct {
});
}
+ pub fn addVectorInit(
+ block: *Block,
+ vector_ty: Type,
+ elements: []const Air.Inst.Ref,
+ ) !Air.Inst.Ref {
+ const sema = block.sema;
+ const ty_ref = try sema.addType(vector_ty);
+ try sema.air_extra.ensureUnusedCapacity(sema.gpa, elements.len);
+ const extra_index = @intCast(u32, sema.air_extra.items.len);
+ sema.appendRefsAssumeCapacity(elements);
+
+ return block.addInst(.{
+ .tag = .vector_init,
+ .data = .{ .ty_pl = .{
+ .ty = ty_ref,
+ .payload = extra_index,
+ } },
+ });
+ }
+
pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
return Air.indexToRef(try block.addInstAsIndex(inst));
}
@@ -652,8 +672,6 @@ pub fn analyzeBody(
.align_cast => try sema.zirAlignCast(block, inst),
.has_decl => try sema.zirHasDecl(block, inst),
.has_field => try sema.zirHasField(block, inst),
- .clz => try sema.zirClz(block, inst),
- .ctz => try sema.zirCtz(block, inst),
.pop_count => try sema.zirPopCount(block, inst),
.byte_swap => try sema.zirByteSwap(block, inst),
.bit_reverse => try sema.zirBitReverse(block, inst),
@@ -678,6 +696,9 @@ pub fn analyzeBody(
.await_nosuspend => try sema.zirAwait(block, inst, true),
.extended => try sema.zirExtended(block, inst),
+ .clz => try sema.zirClzCtz(block, inst, .clz, Value.clz),
+ .ctz => try sema.zirClzCtz(block, inst, .ctz, Value.ctz),
+
.sqrt => try sema.zirUnaryMath(block, inst),
.sin => try sema.zirUnaryMath(block, inst),
.cos => try sema.zirUnaryMath(block, inst),
@@ -4643,6 +4664,7 @@ fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const len = try sema.resolveAlreadyCoercedInt(block, len_src, extra.lhs, u32);
const elem_type = try sema.resolveType(block, elem_type_src, extra.rhs);
+ try sema.checkVectorElemType(block, elem_type_src, elem_type);
const vector_type = try Type.Tag.vector.create(sema.arena, .{
.len = len,
.elem_type = elem_type,
@@ -9401,6 +9423,22 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
}),
);
},
+ .Vector => {
+ const info = ty.arrayInfo();
+ const field_values = try sema.arena.alloc(Value, 2);
+ // len: comptime_int,
+ field_values[0] = try Value.Tag.int_u64.create(sema.arena, info.len);
+ // child: type,
+ field_values[1] = try Value.Tag.ty.create(sema.arena, info.elem_type);
+
+ return sema.addConstant(
+ type_info_ty,
+ try Value.Tag.@"union".create(sema.arena, .{
+ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Vector)),
+ .val = try Value.Tag.@"struct".create(sema.arena, field_values),
+ }),
+ );
+ },
.Optional => {
const field_values = try sema.arena.alloc(Value, 1);
// child: type,
@@ -9639,7 +9677,6 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
.Opaque => return sema.fail(block, src, "TODO: implement zirTypeInfo for Opaque", .{}),
.Frame => return sema.fail(block, src, "TODO: implement zirTypeInfo for Frame", .{}),
.AnyFrame => return sema.fail(block, src, "TODO: implement zirTypeInfo for AnyFrame", .{}),
- .Vector => return sema.fail(block, src, "TODO: implement zirTypeInfo for Vector", .{}),
}
}
@@ -10945,58 +10982,67 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
return sema.coerceCompatiblePtrs(block, dest_ty, ptr, ptr_src);
}
-fn zirClz(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirClzCtz(
+ sema: *Sema,
+ block: *Block,
+ inst: Zir.Inst.Index,
+ air_tag: Air.Inst.Tag,
+ comptimeOp: fn (val: Value, ty: Type, target: std.Target) u64,
+) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
- const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
const operand = sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
- // TODO implement support for vectors
- if (operand_ty.zigTypeTag() != .Int) {
- return sema.fail(block, ty_src, "expected integer type, found '{}'", .{
- operand_ty,
- });
- }
+ try checkIntOrVector(sema, block, operand, operand_src);
const target = sema.mod.getTarget();
const bits = operand_ty.intInfo(target).bits;
- if (bits == 0) return Air.Inst.Ref.zero;
-
- const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
-
- const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
- if (val.isUndef()) return sema.addConstUndef(result_ty);
- return sema.addIntUnsigned(result_ty, val.clz(operand_ty, target));
- } else operand_src;
-
- try sema.requireRuntimeBlock(block, runtime_src);
- return block.addTyOp(.clz, result_ty, operand);
-}
-
-fn zirCtz(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const inst_data = sema.code.instructions.items(.data)[inst].un_node;
- const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
- const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
- const operand = sema.resolveInst(inst_data.operand);
- const operand_ty = sema.typeOf(operand);
- // TODO implement support for vectors
- if (operand_ty.zigTypeTag() != .Int) {
- return sema.fail(block, ty_src, "expected integer type, found '{}'", .{
- operand_ty,
- });
+ if (bits == 0) {
+ switch (operand_ty.zigTypeTag()) {
+ .Vector => return sema.addConstant(
+ try Type.vector(sema.arena, operand_ty.arrayLen(), Type.comptime_int),
+ try Value.Tag.repeated.create(sema.arena, Value.zero),
+ ),
+ .Int => return Air.Inst.Ref.zero,
+ else => unreachable,
+ }
}
- const target = sema.mod.getTarget();
- const bits = operand_ty.intInfo(target).bits;
- if (bits == 0) return Air.Inst.Ref.zero;
- const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
-
- const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
- if (val.isUndef()) return sema.addConstUndef(result_ty);
- return sema.fail(block, operand_src, "TODO: implement comptime @ctz", .{});
- } else operand_src;
-
- try sema.requireRuntimeBlock(block, runtime_src);
- return block.addTyOp(.ctz, result_ty, operand);
+ const result_scalar_ty = try Type.smallestUnsignedInt(sema.arena, bits);
+ switch (operand_ty.zigTypeTag()) {
+ .Vector => {
+ const vec_len = operand_ty.arrayLen();
+ const result_ty = try Type.vector(sema.arena, vec_len, result_scalar_ty);
+ if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
+ if (val.isUndef()) return sema.addConstUndef(result_ty);
+
+ var elem_buf: Value.ElemValueBuffer = undefined;
+ const elems = try sema.arena.alloc(Value, vec_len);
+ const scalar_ty = operand_ty.scalarType();
+ for (elems) |*elem, i| {
+ const elem_val = val.elemValueBuffer(i, &elem_buf);
+ const count = comptimeOp(elem_val, scalar_ty, target);
+ elem.* = try Value.Tag.int_u64.create(sema.arena, count);
+ }
+ return sema.addConstant(
+ result_ty,
+ try Value.Tag.array.create(sema.arena, elems),
+ );
+ } else {
+ try sema.requireRuntimeBlock(block, operand_src);
+ return block.addTyOp(air_tag, result_ty, operand);
+ }
+ },
+ .Int => {
+ if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
+ if (val.isUndef()) return sema.addConstUndef(result_scalar_ty);
+ return sema.addIntUnsigned(result_scalar_ty, comptimeOp(val, operand_ty, target));
+ } else {
+ try sema.requireRuntimeBlock(block, operand_src);
+ return block.addTyOp(air_tag, result_scalar_ty, operand);
+ }
+ },
+ else => unreachable,
+ }
}
fn zirPopCount(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -11126,6 +11172,19 @@ fn checkPtrType(
}
}
+fn checkVectorElemType(
+ sema: *Sema,
+ block: *Block,
+ ty_src: LazySrcLoc,
+ ty: Type,
+) CompileError!void {
+ switch (ty.zigTypeTag()) {
+ .Int, .Float, .Bool => return,
+ else => if (ty.isPtrAtRuntime()) return,
+ }
+ return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{}'", .{ty});
+}
+
fn checkFloatType(
sema: *Sema,
block: *Block,
@@ -11243,6 +11302,22 @@ fn checkComptimeVarStore(
}
}
+fn checkIntOrVector(
+ sema: *Sema,
+ block: *Block,
+ operand: Air.Inst.Ref,
+ operand_src: LazySrcLoc,
+) CompileError!void {
+ const operand_ty = sema.typeOf(operand);
+ const operand_zig_ty_tag = try operand_ty.zigTypeTagOrPoison();
+ switch (operand_zig_ty_tag) {
+ .Vector, .Int => return,
+ else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{
+ operand_ty,
+ }),
+ }
+}
+
const SimdBinOp = struct {
len: ?usize,
/// Coerced to `result_ty`.
@@ -11464,8 +11539,28 @@ fn zirCmpxchg(
fn zirSplat(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.zirSplat", .{});
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const len_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const scalar_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const len = @intCast(u32, try sema.resolveInt(block, len_src, extra.lhs, Type.u32));
+ const scalar = sema.resolveInst(extra.rhs);
+ const scalar_ty = sema.typeOf(scalar);
+ try sema.checkVectorElemType(block, scalar_src, scalar_ty);
+ const vector_ty = try Type.Tag.vector.create(sema.arena, .{
+ .len = len,
+ .elem_type = scalar_ty,
+ });
+ if (try sema.resolveMaybeUndefVal(block, scalar_src, scalar)) |scalar_val| {
+ if (scalar_val.isUndef()) return sema.addConstUndef(vector_ty);
+
+ return sema.addConstant(
+ vector_ty,
+ try Value.Tag.repeated.create(sema.arena, scalar_val),
+ );
+ }
+
+ try sema.requireRuntimeBlock(block, scalar_src);
+ return block.addTyOp(.splat, vector_ty, scalar);
}
fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -13138,6 +13233,8 @@ fn elemVal(
return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty});
}
+ // TODO in case of a vector of pointers, we need to detect whether the element
+ // index is a scalar or vector instead of unconditionally casting to usize.
const elem_index = try sema.coerce(block, Type.usize, elem_index_uncasted, elem_index_src);
switch (array_ty.zigTypeTag()) {
@@ -13178,25 +13275,38 @@ fn elemVal(
return sema.analyzeLoad(block, array_src, elem_ptr, elem_index_src);
},
},
- .Array => {
- if (try sema.resolveMaybeUndefVal(block, array_src, array)) |array_val| {
- const elem_ty = array_ty.childType();
- if (array_val.isUndef()) return sema.addConstUndef(elem_ty);
- const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
- if (maybe_index_val) |index_val| {
- const index = @intCast(usize, index_val.toUnsignedInt());
- const elem_val = try array_val.elemValue(sema.arena, index);
- return sema.addConstant(elem_ty, elem_val);
- }
- }
- try sema.requireRuntimeBlock(block, array_src);
- return block.addBinOp(.array_elem_val, array, elem_index);
+ .Array => return elemValArray(sema, block, array, elem_index, array_src, elem_index_src),
+ .Vector => {
+ // TODO: If the index is a vector, the result should be a vector.
+ return elemValArray(sema, block, array, elem_index, array_src, elem_index_src);
},
- .Vector => return sema.fail(block, array_src, "TODO implement Sema for elemVal for vector", .{}),
else => unreachable,
}
}
+fn elemValArray(
+ sema: *Sema,
+ block: *Block,
+ array: Air.Inst.Ref,
+ elem_index: Air.Inst.Ref,
+ array_src: LazySrcLoc,
+ elem_index_src: LazySrcLoc,
+) CompileError!Air.Inst.Ref {
+ const array_ty = sema.typeOf(array);
+ if (try sema.resolveMaybeUndefVal(block, array_src, array)) |array_val| {
+ const elem_ty = array_ty.childType();
+ if (array_val.isUndef()) return sema.addConstUndef(elem_ty);
+ const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
+ if (maybe_index_val) |index_val| {
+ const index = @intCast(usize, index_val.toUnsignedInt());
+ const elem_val = try array_val.elemValue(sema.arena, index);
+ return sema.addConstant(elem_ty, elem_val);
+ }
+ }
+ try sema.requireRuntimeBlock(block, array_src);
+ return block.addBinOp(.array_elem_val, array, elem_index);
+}
+
fn elemPtrArray(
sema: *Sema,
block: *Block,
@@ -13530,6 +13640,7 @@ fn coerce(
},
.Vector => switch (inst_ty.zigTypeTag()) {
.Array => return sema.coerceVectorInMemory(block, dest_ty, dest_ty_src, inst, inst_src),
+ .Vector => return sema.coerceVectors(block, dest_ty, dest_ty_src, inst, inst_src),
else => {},
},
else => {},
@@ -14410,8 +14521,9 @@ fn coerceEnumToUnion(
return sema.failWithOwnedErrorMsg(msg);
}
-// Coerces vectors/arrays which have the same in-memory layout. This can be used for
-// both coercing from and to vectors.
+/// Coerces vectors/arrays which have the same in-memory layout. This can be used for
+/// both coercing from and to vectors.
+/// TODO (affects the lang spec) delete this in favor of always using `coerceVectors`.
fn coerceVectorInMemory(
sema: *Sema,
block: *Block,
@@ -14455,6 +14567,78 @@ fn coerceVectorInMemory(
return block.addBitCast(dest_ty, inst);
}
+/// If the lengths match, coerces element-wise.
+fn coerceVectors(
+ sema: *Sema,
+ block: *Block,
+ dest_ty: Type,
+ dest_ty_src: LazySrcLoc,
+ inst: Air.Inst.Ref,
+ inst_src: LazySrcLoc,
+) !Air.Inst.Ref {
+ const inst_ty = sema.typeOf(inst);
+ const inst_len = inst_ty.arrayLen();
+ const dest_len = dest_ty.arrayLen();
+
+ if (dest_len != inst_len) {
+ const msg = msg: {
+ const msg = try sema.errMsg(block, inst_src, "expected {}, found {}", .{
+ dest_ty, inst_ty,
+ });
+ errdefer msg.destroy(sema.gpa);
+ try sema.errNote(block, dest_ty_src, msg, "destination has length {d}", .{dest_len});
+ try sema.errNote(block, inst_src, msg, "source has length {d}", .{inst_len});
+ break :msg msg;
+ };
+ return sema.failWithOwnedErrorMsg(msg);
+ }
+
+ const target = sema.mod.getTarget();
+ const dest_elem_ty = dest_ty.childType();
+ const inst_elem_ty = inst_ty.childType();
+ const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, false, target, dest_ty_src, inst_src);
+ if (in_memory_result == .ok) {
+ if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |inst_val| {
+ // These types share the same comptime value representation.
+ return sema.addConstant(dest_ty, inst_val);
+ }
+ try sema.requireRuntimeBlock(block, inst_src);
+ return block.addBitCast(dest_ty, inst);
+ }
+
+ const element_vals = try sema.arena.alloc(Value, dest_len);
+ const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len);
+ var runtime_src: ?LazySrcLoc = null;
+
+ for (element_vals) |*elem, i| {
+ const index_ref = try sema.addConstant(
+ Type.usize,
+ try Value.Tag.int_u64.create(sema.arena, i),
+ );
+ const elem_src = inst_src; // TODO better source location
+ const elem_ref = try elemValArray(sema, block, inst, index_ref, inst_src, elem_src);
+ const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
+ element_refs[i] = coerced;
+ if (runtime_src == null) {
+ if (try sema.resolveMaybeUndefVal(block, elem_src, coerced)) |elem_val| {
+ elem.* = elem_val;
+ } else {
+ runtime_src = elem_src;
+ }
+ }
+ }
+
+ if (runtime_src) |rs| {
+ try sema.requireRuntimeBlock(block, rs);
+ return block.addVectorInit(dest_ty, element_refs);
+ }
+
+ return sema.addConstant(
+ dest_ty,
+ try Value.Tag.array.create(sema.arena, element_vals),
+ );
+}
+
fn analyzeDeclVal(
sema: *Sema,
block: *Block,
src/type.zig
@@ -3080,7 +3080,7 @@ pub const Type = extern union {
};
}
- /// Asserts the type is an integer, enum, or error set.
+ /// Asserts the type is an integer, enum, error set, or vector of one of them.
pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } {
var ty = self;
while (true) switch (ty.tag()) {
@@ -3128,6 +3128,8 @@ pub const Type = extern union {
return .{ .signedness = .unsigned, .bits = 16 };
},
+ .vector => ty = ty.castTag(.vector).?.data.elem_type,
+
else => unreachable,
};
}
@@ -4501,6 +4503,7 @@ pub const Type = extern union {
};
pub const @"u8" = initTag(.u8);
+ pub const @"u32" = initTag(.u32);
pub const @"bool" = initTag(.bool);
pub const @"usize" = initTag(.usize);
pub const @"isize" = initTag(.isize);
src/value.zig
@@ -1172,6 +1172,44 @@ pub const Value = extern union {
}
}
+ pub fn ctz(val: Value, ty: Type, target: Target) u64 {
+ const ty_bits = ty.intInfo(target).bits;
+ switch (val.tag()) {
+ .zero, .bool_false => return ty_bits,
+ .one, .bool_true => return 0,
+
+ .int_u64 => {
+ const big = @ctz(u64, val.castTag(.int_u64).?.data);
+ return if (big == 64) ty_bits else big;
+ },
+ .int_i64 => {
+ @panic("TODO implement i64 Value ctz");
+ },
+ .int_big_positive => {
+ // TODO: move this code into std lib big ints
+ const bigint = val.castTag(.int_big_positive).?.asBigInt();
+ // Limbs are stored in little-endian order.
+ var result: u64 = 0;
+ for (bigint.limbs) |limb| {
+ const limb_tz = @ctz(std.math.big.Limb, limb);
+ result += limb_tz;
+ if (limb_tz != @sizeOf(std.math.big.Limb) * 8) break;
+ }
+ return result;
+ },
+ .int_big_negative => {
+ @panic("TODO implement int_big_negative Value ctz");
+ },
+
+ .the_only_possible_value => {
+ assert(ty_bits == 0);
+ return ty_bits;
+ },
+
+ else => unreachable,
+ }
+ }
+
/// Asserts the value is an integer and not undefined.
/// Returns the number of bits the value requires to represent stored in twos complement form.
pub fn intBitCountTwosComp(self: Value) usize {
@@ -1455,6 +1493,20 @@ pub const Value = extern union {
.field_ptr => @panic("TODO: Implement more pointer eql cases"),
.eu_payload_ptr => @panic("TODO: Implement more pointer eql cases"),
.opt_payload_ptr => @panic("TODO: Implement more pointer eql cases"),
+ .array => {
+ const a_array = a.castTag(.array).?.data;
+ const b_array = b.castTag(.array).?.data;
+
+ if (a_array.len != b_array.len) return false;
+
+ const elem_ty = ty.childType();
+ for (a_array) |a_elem, i| {
+ const b_elem = b_array[i];
+
+ if (!eql(a_elem, b_elem, elem_ty)) return false;
+ }
+ return true;
+ },
else => {},
}
} else if (a_tag == .null_value or b_tag == .null_value) {
@@ -1488,6 +1540,19 @@ pub const Value = extern union {
const int_ty = ty.intTagType(&buf_ty);
return eql(a_val, b_val, int_ty);
},
+ .Array, .Vector => {
+ const len = ty.arrayLen();
+ const elem_ty = ty.childType();
+ var i: usize = 0;
+ var a_buf: ElemValueBuffer = undefined;
+ var b_buf: ElemValueBuffer = undefined;
+ while (i < len) : (i += 1) {
+ const a_elem = elemValueBuffer(a, i, &a_buf);
+ const b_elem = elemValueBuffer(b, i, &b_buf);
+ if (!eql(a_elem, b_elem, elem_ty)) return false;
+ }
+ return true;
+ },
else => return order(a, b).compare(.eq),
}
}
test/behavior/math.zig
@@ -72,6 +72,79 @@ fn testOneClz(comptime T: type, x: T) u32 {
return @clz(T, x);
}
+test "@clz vectors" {
+ try testClzVectors();
+ comptime try testClzVectors();
+}
+
+fn testClzVectors() !void {
+ @setEvalBranchQuota(10_000);
+ try testOneClzVector(u8, 64, @splat(64, @as(u8, 0b10001010)), @splat(64, @as(u4, 0)));
+ try testOneClzVector(u8, 64, @splat(64, @as(u8, 0b00001010)), @splat(64, @as(u4, 4)));
+ try testOneClzVector(u8, 64, @splat(64, @as(u8, 0b00011010)), @splat(64, @as(u4, 3)));
+ try testOneClzVector(u8, 64, @splat(64, @as(u8, 0b00000000)), @splat(64, @as(u4, 8)));
+ try testOneClzVector(u128, 64, @splat(64, @as(u128, 0xffffffffffffffff)), @splat(64, @as(u8, 64)));
+ try testOneClzVector(u128, 64, @splat(64, @as(u128, 0x10000000000000000)), @splat(64, @as(u8, 63)));
+}
+
+fn testOneClzVector(
+ comptime T: type,
+ comptime len: u32,
+ x: @Vector(len, T),
+ expected: @Vector(len, u32),
+) !void {
+ try expectVectorsEqual(@clz(T, x), expected);
+}
+
+fn expectVectorsEqual(a: anytype, b: anytype) !void {
+ const len_a = @typeInfo(@TypeOf(a)).Vector.len;
+ const len_b = @typeInfo(@TypeOf(b)).Vector.len;
+ try expect(len_a == len_b);
+
+ var i: usize = 0;
+ while (i < len_a) : (i += 1) {
+ try expect(a[i] == b[i]);
+ }
+}
+
+test "@ctz" {
+ try testCtz();
+ comptime try testCtz();
+}
+
+fn testCtz() !void {
+ try expect(testOneCtz(u8, 0b10100000) == 5);
+ try expect(testOneCtz(u8, 0b10001010) == 1);
+ try expect(testOneCtz(u8, 0b00000000) == 8);
+ try expect(testOneCtz(u16, 0b00000000) == 16);
+}
+
+fn testOneCtz(comptime T: type, x: T) u32 {
+ return @ctz(T, x);
+}
+
+test "@ctz vectors" {
+ try testCtzVectors();
+ comptime try testCtzVectors();
+}
+
+fn testCtzVectors() !void {
+ @setEvalBranchQuota(10_000);
+ try testOneCtzVector(u8, 64, @splat(64, @as(u8, 0b10100000)), @splat(64, @as(u4, 5)));
+ try testOneCtzVector(u8, 64, @splat(64, @as(u8, 0b10001010)), @splat(64, @as(u4, 1)));
+ try testOneCtzVector(u8, 64, @splat(64, @as(u8, 0b00000000)), @splat(64, @as(u4, 8)));
+ try testOneCtzVector(u16, 64, @splat(64, @as(u16, 0b00000000)), @splat(64, @as(u5, 16)));
+}
+
+fn testOneCtzVector(
+ comptime T: type,
+ comptime len: u32,
+ x: @Vector(len, T),
+ expected: @Vector(len, u32),
+) !void {
+ try expectVectorsEqual(@ctz(T, x), expected);
+}
+
test "const number literal" {
const one = 1;
const eleven = ten + one;
test/behavior/math_stage1.zig
@@ -6,46 +6,6 @@ const maxInt = std.math.maxInt;
const minInt = std.math.minInt;
const mem = std.mem;
-test "@clz vectors" {
- try testClzVectors();
- comptime try testClzVectors();
-}
-
-fn testClzVectors() !void {
- @setEvalBranchQuota(10_000);
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0)));
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4)));
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3)));
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
- try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64)));
- try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63)));
-}
-
-test "@ctz" {
- try testCtz();
- comptime try testCtz();
-}
-
-fn testCtz() !void {
- try expect(@ctz(u8, 0b10100000) == 5);
- try expect(@ctz(u8, 0b10001010) == 1);
- try expect(@ctz(u8, 0b00000000) == 8);
- try expect(@ctz(u16, 0b00000000) == 16);
-}
-
-test "@ctz vectors" {
- try testClzVectors();
- comptime try testClzVectors();
-}
-
-fn testCtzVectors() !void {
- @setEvalBranchQuota(10_000);
- try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5)));
- try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1)));
- try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
- try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
-}
-
test "allow signed integer division/remainder when values are comptime known and positive or exact" {
try expect(5 / 3 == 1);
try expect(-5 / -3 == 1);
test/behavior/popcount.zig
@@ -41,6 +41,6 @@ fn testPopCountIntegers() !void {
try expect(@popCount(u8, @bitCast(u8, @as(i8, -120))) == 2);
}
comptime {
- try expect(@popCount(i128, 0b11111111000110001100010000100001000011000011100101010001) == 24);
+ try expect(@popCount(i128, @as(i128, 0b11111111000110001100010000100001000011000011100101010001)) == 24);
}
}