From 93b854eb745ab3294054ae71150fe60f134f4d10 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 12 Jan 2022 23:53:26 -0700 Subject: [PATCH] stage2: implement `@ctz` and `@clz` including SIMD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AIR: * `array_elem_val` is now allowed to be used with a vector as the array type. * New instructions: splat, vector_init AstGen: * The splat ZIR instruction uses coerced_ty for the ResultLoc, avoiding an unnecessary `as` instruction, since the coercion will be performed in Sema. * Builtins that accept vectors now ignore the type parameter. Comment from this commit reproduced here: The accepted proposal #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. Sema: * `clz` and `ctz` ZIR instructions are now handled by the same function which accept AIR tag and comptime eval function pointer to differentiate. * `@typeInfo` for vectors is implemented. * `@splat` is implemented. It takes advantage of `Value.Tag.repeated` 😎 * `elemValue` is implemented for vectors, when the index is a scalar. Handling a vector index is still TODO. * Element-wise coercion is implemented for vectors. It could probably be optimized a bit, but it is at least complete & correct. * `Type.intInfo` supports vectors, returning int info for the element. * `Value.ctz` initial implementation. Needs work. * `Value.eql` is implemented for arrays and vectors. LLVM backend: * Implement vector support when lowering `array_elem_val`. * Implement vector support when lowering `ctz` and `clz`. * Implement `splat` and `vector_init`. --- lib/std/testing.zig | 8 +- src/Air.zig | 14 +- src/AstGen.zig | 12 +- src/Liveness.zig | 28 ++- src/Sema.zig | 312 +++++++++++++++++++++++++++------- src/arch/aarch64/CodeGen.zig | 32 +++- src/arch/arm/CodeGen.zig | 30 ++++ src/arch/riscv64/CodeGen.zig | 30 ++++ src/arch/wasm/CodeGen.zig | 23 +++ src/arch/x86_64/CodeGen.zig | 32 +++- src/codegen/c.zig | 35 ++++ src/codegen/llvm.zig | 74 ++++++-- src/codegen/llvm/bindings.zig | 3 + src/print_air.zig | 16 ++ src/type.zig | 5 +- src/value.zig | 65 +++++++ test/behavior/math.zig | 73 ++++++++ test/behavior/math_stage1.zig | 40 ----- test/behavior/popcount.zig | 2 +- 19 files changed, 706 insertions(+), 128 deletions(-) diff --git a/lib/std/testing.zig b/lib/std/testing.zig index c6a498cfff530df4e48d139ff5a8dc867d79c2a3..a09d448e0acd39a337b9112229d1308c5e994c09 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 2e910f9c9a28f997e8a9c4a4aca43e32aa0a6b34..274c30167a2b09af2e14b9a012e34d03b05b6d64 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 87cc07fae89ec0b0fde64622066e0f1ef6987f37..ed57f5a3cd23c27f376750e7822f71175e841931 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 39de37d4b8f58c3060eb0181fafbac37e2f2267d..f7d4353e39e54190aea5f58891b166f98ccd8bbf 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 7dd9b3497dfe96ee7545ed4047129c293890d2f4..e52eb6e79ca232651735926e1b5af9631ddd82b9 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 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; + 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); - 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 1e69f7db5a89fe16d3b71964334c303ca78ec0bb..0261813dcb1fc5f7add29b3252d3134d836635c3 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 3501a597f96199591e66148b7acbce7c2641a349..4d74d7000a11015d17576effdc48e2abc686cba4 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 a8f2b69d9022e4577c7ddda2d03be2bab9eb41d6..76101aa8c7c366d6da57b70657be58cef35d404f 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 ae9c8b37b7dca524f93d03ecc50f2400ab414bee..5c03253a606197e955c8c9c7e2902dd6469d55aa 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 2cb7fc1ab727f704df433323c0428a4a34bf710e..9cfc7cdfa75e31baca3e34a4ded920fba015069b 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 e85ca6c7054443e7c70007f6f25afda3d82c29f3..0bd543fab7bba3388a50190cc5099e2143e99db8 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 4a3ac80b705e738f12843a107c00d53c4d56799a..7b944109126f3721320fe6cf7a4ea4d3f1580f77 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 3dac5bdca4d9cd3698ef089332c12983c776c56e..42c88d135154bce6437f5d8b9dd9ae324a6656bf 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 3101c109cfc0cf9794028835fcf3f5e1d91a4dc8..b96e35b254611452b9c30e281d2126fa0fa10ec8 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 167248a179fcce7d9d708f05397c84df03f1106d..40ce977711bede6a944070a20c696c6263139aa2 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 c4d35ad006a6a48ee75ca564ea40a887fa53f298..1e7f7133075f4f747e8407836d040201b28c1ca0 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 b3821f7732c08b8cc7631ee6d074808be8701151..d7d75ef2ecc7de8c4b33773d1d57e28a102dcfe8 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 2633f23aacc4a9cf038f3a84b07775ba539f5bad..72824348dd6371f587f3a28c52da8a4fa57c7c4b 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 98493c7579f69b812feddc02edb14af93b4736b0..eb3e3780586d2987573b17b6b7ece0033b326af1 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); } } -- 2.54.0