diff --git a/lib/std/testing.zig b/lib/std/testing.zig index c6a498cfff..a09d448e0a 100644 --- a/lib/std/testing.zig +++ b/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; } } diff --git a/src/Air.zig b/src/Air.zig index 2e910f9c9a..274c30167a 100644 --- a/src/Air.zig +++ b/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, diff --git a/src/AstGen.zig b/src/AstGen.zig index 87cc07fae8..ed57f5a3cd 100644 --- a/src/AstGen.zig +++ b/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); } diff --git a/src/Liveness.zig b/src/Liveness.zig index 39de37d4b8..f7d4353e39 100644 --- a/src/Liveness.zig +++ b/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 }); diff --git a/src/Sema.zig b/src/Sema.zig index 7dd9b3497d..e52eb6e79c 100644 --- a/src/Sema.zig +++ b/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 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); - 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); + 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, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 1e69f7db5a..0261813dcb 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/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); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 3501a597f9..4d74d7000a 100644 --- a/src/arch/arm/CodeGen.zig +++ b/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); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index a8f2b69d90..76101aa8c7 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/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); diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index ae9c8b37b7..5c03253a60 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/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); diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 2cb7fc1ab7..9cfc7cdfa7 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/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); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e85ca6c705..0bd543fab7 100644 --- a/src/codegen/c.zig +++ b/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", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4a3ac80b70..7b94410912 100644 --- a/src/codegen/llvm.zig +++ b/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; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 3dac5bdca4..42c88d1351 100644 --- a/src/codegen/llvm/bindings.zig +++ b/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 {}; diff --git a/src/print_air.zig b/src/print_air.zig index 3101c109cf..b96e35b254 100644 --- a/src/print_air.zig +++ b/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; diff --git a/src/type.zig b/src/type.zig index 167248a179..40ce977711 100644 --- a/src/type.zig +++ b/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); diff --git a/src/value.zig b/src/value.zig index c4d35ad006..1e7f713307 100644 --- a/src/value.zig +++ b/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), } } diff --git a/test/behavior/math.zig b/test/behavior/math.zig index b3821f7732..d7d75ef2ec 100644 --- a/test/behavior/math.zig +++ b/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; diff --git a/test/behavior/math_stage1.zig b/test/behavior/math_stage1.zig index 2633f23aac..72824348dd 100644 --- a/test/behavior/math_stage1.zig +++ b/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); diff --git a/test/behavior/popcount.zig b/test/behavior/popcount.zig index 98493c7579..eb3e378058 100644 --- a/test/behavior/popcount.zig +++ b/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); } }