diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-01-12 23:53:26 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2022-01-12 23:53:26 -0700 |
| commit | 93b854eb745ab3294054ae71150fe60f134f4d10 (patch) | |
| tree | d40e26fcf2524c70c30302f0b503d14a0cdf4e51 /src | |
| parent | c4681b4889652d5228a84ac7af5ad5e17ac39055 (diff) | |
| download | zig-93b854eb745ab3294054ae71150fe60f134f4d10.tar.gz zig-93b854eb745ab3294054ae71150fe60f134f4d10.zip | |
stage2: implement `@ctz` and `@clz` including SIMD
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`.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Air.zig | 14 | ||||
| -rw-r--r-- | src/AstGen.zig | 12 | ||||
| -rw-r--r-- | src/Liveness.zig | 28 | ||||
| -rw-r--r-- | src/Sema.zig | 314 | ||||
| -rw-r--r-- | src/arch/aarch64/CodeGen.zig | 32 | ||||
| -rw-r--r-- | src/arch/arm/CodeGen.zig | 30 | ||||
| -rw-r--r-- | src/arch/riscv64/CodeGen.zig | 30 | ||||
| -rw-r--r-- | src/arch/wasm/CodeGen.zig | 23 | ||||
| -rw-r--r-- | src/arch/x86_64/CodeGen.zig | 32 | ||||
| -rw-r--r-- | src/codegen/c.zig | 35 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 74 | ||||
| -rw-r--r-- | src/codegen/llvm/bindings.zig | 3 | ||||
| -rw-r--r-- | src/print_air.zig | 16 | ||||
| -rw-r--r-- | src/type.zig | 5 | ||||
| -rw-r--r-- | src/value.zig | 65 |
15 files changed, 628 insertions, 85 deletions
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 runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { - if (val.isUndef()) return sema.addConstUndef(result_ty); - return sema.fail(block, operand_src, "TODO: implement comptime @ctz", .{}); - } else operand_src; - - try sema.requireRuntimeBlock(block, runtime_src); - return block.addTyOp(.ctz, result_ty, operand); + const result_scalar_ty = try Type.smallestUnsignedInt(sema.arena, bits); + switch (operand_ty.zigTypeTag()) { + .Vector => { + const vec_len = operand_ty.arrayLen(); + const result_ty = try Type.vector(sema.arena, vec_len, result_scalar_ty); + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_ty); + + var elem_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, vec_len); + const scalar_ty = operand_ty.scalarType(); + for (elems) |*elem, i| { + const elem_val = val.elemValueBuffer(i, &elem_buf); + const count = comptimeOp(elem_val, scalar_ty, target); + elem.* = try Value.Tag.int_u64.create(sema.arena, count); + } + return sema.addConstant( + result_ty, + try Value.Tag.array.create(sema.arena, elems), + ); + } else { + try sema.requireRuntimeBlock(block, operand_src); + return block.addTyOp(air_tag, result_ty, operand); + } + }, + .Int => { + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_scalar_ty); + return sema.addIntUnsigned(result_scalar_ty, comptimeOp(val, operand_ty, target)); + } else { + try sema.requireRuntimeBlock(block, operand_src); + return block.addTyOp(air_tag, result_scalar_ty, operand); + } + }, + else => unreachable, + } } fn zirPopCount(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -11126,6 +11172,19 @@ fn checkPtrType( } } +fn checkVectorElemType( + sema: *Sema, + block: *Block, + ty_src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .Int, .Float, .Bool => return, + else => if (ty.isPtrAtRuntime()) return, + } + return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{}'", .{ty}); +} + fn checkFloatType( sema: *Sema, block: *Block, @@ -11243,6 +11302,22 @@ fn checkComptimeVarStore( } } +fn checkIntOrVector( + sema: *Sema, + block: *Block, + operand: Air.Inst.Ref, + operand_src: LazySrcLoc, +) CompileError!void { + const operand_ty = sema.typeOf(operand); + const operand_zig_ty_tag = try operand_ty.zigTypeTagOrPoison(); + switch (operand_zig_ty_tag) { + .Vector, .Int => return, + else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{ + operand_ty, + }), + } +} + const SimdBinOp = struct { len: ?usize, /// Coerced to `result_ty`. @@ -11464,8 +11539,28 @@ fn zirCmpxchg( fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirSplat", .{}); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const len_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const scalar_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const len = @intCast(u32, try sema.resolveInt(block, len_src, extra.lhs, Type.u32)); + const scalar = sema.resolveInst(extra.rhs); + const scalar_ty = sema.typeOf(scalar); + try sema.checkVectorElemType(block, scalar_src, scalar_ty); + const vector_ty = try Type.Tag.vector.create(sema.arena, .{ + .len = len, + .elem_type = scalar_ty, + }); + if (try sema.resolveMaybeUndefVal(block, scalar_src, scalar)) |scalar_val| { + if (scalar_val.isUndef()) return sema.addConstUndef(vector_ty); + + return sema.addConstant( + vector_ty, + try Value.Tag.repeated.create(sema.arena, scalar_val), + ); + } + + try sema.requireRuntimeBlock(block, scalar_src); + return block.addTyOp(.splat, vector_ty, scalar); } fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -13138,6 +13233,8 @@ fn elemVal( return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty}); } + // TODO in case of a vector of pointers, we need to detect whether the element + // index is a scalar or vector instead of unconditionally casting to usize. const elem_index = try sema.coerce(block, Type.usize, elem_index_uncasted, elem_index_src); switch (array_ty.zigTypeTag()) { @@ -13178,25 +13275,38 @@ fn elemVal( return sema.analyzeLoad(block, array_src, elem_ptr, elem_index_src); }, }, - .Array => { - if (try sema.resolveMaybeUndefVal(block, array_src, array)) |array_val| { - const elem_ty = array_ty.childType(); - if (array_val.isUndef()) return sema.addConstUndef(elem_ty); - const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index); - if (maybe_index_val) |index_val| { - const index = @intCast(usize, index_val.toUnsignedInt()); - const elem_val = try array_val.elemValue(sema.arena, index); - return sema.addConstant(elem_ty, elem_val); - } - } - try sema.requireRuntimeBlock(block, array_src); - return block.addBinOp(.array_elem_val, array, elem_index); + .Array => return elemValArray(sema, block, array, elem_index, array_src, elem_index_src), + .Vector => { + // TODO: If the index is a vector, the result should be a vector. + return elemValArray(sema, block, array, elem_index, array_src, elem_index_src); }, - .Vector => return sema.fail(block, array_src, "TODO implement Sema for elemVal for vector", .{}), else => unreachable, } } +fn elemValArray( + sema: *Sema, + block: *Block, + array: Air.Inst.Ref, + elem_index: Air.Inst.Ref, + array_src: LazySrcLoc, + elem_index_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const array_ty = sema.typeOf(array); + if (try sema.resolveMaybeUndefVal(block, array_src, array)) |array_val| { + const elem_ty = array_ty.childType(); + if (array_val.isUndef()) return sema.addConstUndef(elem_ty); + const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index); + if (maybe_index_val) |index_val| { + const index = @intCast(usize, index_val.toUnsignedInt()); + const elem_val = try array_val.elemValue(sema.arena, index); + return sema.addConstant(elem_ty, elem_val); + } + } + try sema.requireRuntimeBlock(block, array_src); + return block.addBinOp(.array_elem_val, array, elem_index); +} + fn elemPtrArray( sema: *Sema, block: *Block, @@ -13530,6 +13640,7 @@ fn coerce( }, .Vector => switch (inst_ty.zigTypeTag()) { .Array => return sema.coerceVectorInMemory(block, dest_ty, dest_ty_src, inst, inst_src), + .Vector => return sema.coerceVectors(block, dest_ty, dest_ty_src, inst, inst_src), else => {}, }, else => {}, @@ -14410,8 +14521,9 @@ fn coerceEnumToUnion( return sema.failWithOwnedErrorMsg(msg); } -// Coerces vectors/arrays which have the same in-memory layout. This can be used for -// both coercing from and to vectors. +/// Coerces vectors/arrays which have the same in-memory layout. This can be used for +/// both coercing from and to vectors. +/// TODO (affects the lang spec) delete this in favor of always using `coerceVectors`. fn coerceVectorInMemory( sema: *Sema, block: *Block, @@ -14455,6 +14567,78 @@ fn coerceVectorInMemory( return block.addBitCast(dest_ty, inst); } +/// If the lengths match, coerces element-wise. +fn coerceVectors( + sema: *Sema, + block: *Block, + dest_ty: Type, + dest_ty_src: LazySrcLoc, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, +) !Air.Inst.Ref { + const inst_ty = sema.typeOf(inst); + const inst_len = inst_ty.arrayLen(); + const dest_len = dest_ty.arrayLen(); + + if (dest_len != inst_len) { + const msg = msg: { + const msg = try sema.errMsg(block, inst_src, "expected {}, found {}", .{ + dest_ty, inst_ty, + }); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, dest_ty_src, msg, "destination has length {d}", .{dest_len}); + try sema.errNote(block, inst_src, msg, "source has length {d}", .{inst_len}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + + const target = sema.mod.getTarget(); + const dest_elem_ty = dest_ty.childType(); + const inst_elem_ty = inst_ty.childType(); + const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, false, target, dest_ty_src, inst_src); + if (in_memory_result == .ok) { + if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |inst_val| { + // These types share the same comptime value representation. + return sema.addConstant(dest_ty, inst_val); + } + try sema.requireRuntimeBlock(block, inst_src); + return block.addBitCast(dest_ty, inst); + } + + const element_vals = try sema.arena.alloc(Value, dest_len); + const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len); + var runtime_src: ?LazySrcLoc = null; + + for (element_vals) |*elem, i| { + const index_ref = try sema.addConstant( + Type.usize, + try Value.Tag.int_u64.create(sema.arena, i), + ); + const elem_src = inst_src; // TODO better source location + const elem_ref = try elemValArray(sema, block, inst, index_ref, inst_src, elem_src); + const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src); + element_refs[i] = coerced; + if (runtime_src == null) { + if (try sema.resolveMaybeUndefVal(block, elem_src, coerced)) |elem_val| { + elem.* = elem_val; + } else { + runtime_src = elem_src; + } + } + } + + if (runtime_src) |rs| { + try sema.requireRuntimeBlock(block, rs); + return block.addVectorInit(dest_ty, element_refs); + } + + return sema.addConstant( + dest_ty, + try Value.Tag.array.create(sema.arena, element_vals), + ); +} + fn analyzeDeclVal( sema: *Sema, block: *Block, 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), } } |
