From a0e195120dd3dd7919fd249a6ce2201da9395898 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 20 Oct 2021 21:45:11 -0700 Subject: [PATCH] stage2: implement slicing * New AIR instruction: slice, which constructs a slice out of a pointer and a length. * AstGen: use `coerced_ty` for start and end expressions, use `none` for the sentinel, and don't try to load the result of the slice operation because it returns a by-value result. * Sema: pointer arithmetic is extracted into analyzePointerArithmetic and it is used by the implementation of slice. - Also I implemented comptime pointer addition. * Sema: extract logic into analyzeSlicePtr, analyzeSliceLen and use them inside the slice semantic analysis. - The approach in stage2 is much cleaner than stage1 because it uses more granular analysis calls for obtaining the slice pointer, doing arithmetic on it, and checking if the length is comptime-known. * Sema: use the slice Value Tag for slices when doing coercion from pointer-to-array. * LLVM backend: detect when emitting a GEP instruction into a pointer-to-array and add the extra index that is required. * Type: ptrAlignment for c_void returns 0. * Implement Value.hash and Value.eql for slices. * Remove accidentally duplicated behavior test. --- src/Air.zig | 4 + src/AstGen.zig | 36 ++--- src/Liveness.zig | 1 + src/Sema.zig | 256 +++++++++++++++++++++------------ src/Zir.zig | 1 + src/arch/aarch64/CodeGen.zig | 7 + src/codegen.zig | 9 ++ src/codegen/c.zig | 25 +++- src/codegen/llvm.zig | 70 +++++++-- src/codegen/llvm/bindings.zig | 3 + src/print_air.zig | 1 + src/type.zig | 3 +- src/value.zig | 19 +++ test/behavior/eval.zig | 4 - test/behavior/slice.zig | 85 +++++++++++ test/behavior/slice_stage1.zig | 85 ----------- 16 files changed, 383 insertions(+), 226 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index f3e0865ba810acff72390498e1a2dff0c1119e03..91b496a8e4aa26053ccf1fda2c78da3807ecd2ff 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -360,6 +360,9 @@ pub const Inst = struct { /// Given a tagged union value, get its tag value. /// Uses the `ty_op` field. get_union_tag, + /// Constructs a slice from a pointer and a length. + /// Uses the `ty_pl` field, payload is `Bin`. lhs is ptr, rhs is len. + slice, /// Given a slice value, return the length. /// Result type is always usize. /// Uses the `ty_op` field. @@ -694,6 +697,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .ptr_elem_ptr, .cmpxchg_weak, .cmpxchg_strong, + .slice, => return air.getRefType(datas[inst].ty_pl.ty), .not, diff --git a/src/AstGen.zig b/src/AstGen.zig index e98e04f7d445d37527c8679d24311959de6c4797..e67ae6cddf3fbefaac8c1deb8692a4ed1a9d5c24 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -727,56 +727,38 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .slice_open => { const lhs = try expr(gz, scope, .ref, node_datas[node].lhs); - const start = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs); + const start = try expr(gz, scope, .{ .coerced_ty = .usize_type }, node_datas[node].rhs); const result = try gz.addPlNode(.slice_start, node, Zir.Inst.SliceStart{ .lhs = lhs, .start = start, }); - switch (rl) { - .ref => return result, - else => { - const dereffed = try gz.addUnNode(.load, result, node); - return rvalue(gz, rl, dereffed, node); - }, - } + return rvalue(gz, rl, result, node); }, .slice => { const lhs = try expr(gz, scope, .ref, node_datas[node].lhs); const extra = tree.extraData(node_datas[node].rhs, Ast.Node.Slice); - const start = try expr(gz, scope, .{ .ty = .usize_type }, extra.start); - const end = try expr(gz, scope, .{ .ty = .usize_type }, extra.end); + const start = try expr(gz, scope, .{ .coerced_ty = .usize_type }, extra.start); + const end = try expr(gz, scope, .{ .coerced_ty = .usize_type }, extra.end); const result = try gz.addPlNode(.slice_end, node, Zir.Inst.SliceEnd{ .lhs = lhs, .start = start, .end = end, }); - switch (rl) { - .ref => return result, - else => { - const dereffed = try gz.addUnNode(.load, result, node); - return rvalue(gz, rl, dereffed, node); - }, - } + return rvalue(gz, rl, result, node); }, .slice_sentinel => { const lhs = try expr(gz, scope, .ref, node_datas[node].lhs); const extra = tree.extraData(node_datas[node].rhs, Ast.Node.SliceSentinel); - const start = try expr(gz, scope, .{ .ty = .usize_type }, extra.start); - const end = if (extra.end != 0) try expr(gz, scope, .{ .ty = .usize_type }, extra.end) else .none; - const sentinel = try expr(gz, scope, .{ .ty = .usize_type }, extra.sentinel); + const start = try expr(gz, scope, .{ .coerced_ty = .usize_type }, extra.start); + const end = if (extra.end != 0) try expr(gz, scope, .{ .coerced_ty = .usize_type }, extra.end) else .none; + const sentinel = try expr(gz, scope, .none, extra.sentinel); const result = try gz.addPlNode(.slice_sentinel, node, Zir.Inst.SliceSentinel{ .lhs = lhs, .start = start, .end = end, .sentinel = sentinel, }); - switch (rl) { - .ref => return result, - else => { - const dereffed = try gz.addUnNode(.load, result, node); - return rvalue(gz, rl, dereffed, node); - }, - } + return rvalue(gz, rl, result, node); }, .deref => { diff --git a/src/Liveness.zig b/src/Liveness.zig index c56bcc8c09e854e5d7f14fff0e44c1dcfcf9c851..5d5bb64196cdc75e119a219f3eec0ed1f808736a 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -266,6 +266,7 @@ fn analyzeInst( .set_union_tag, .min, .max, + .slice, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 39e4dfe368bd5e95970251f6f7dfd608c8273923..8ac31387f4a75315ebed0ea4b91c3226bc1e7941 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7083,7 +7083,6 @@ fn analyzeArithmetic( if (lhs_zig_ty_tag == .Pointer) switch (lhs_ty.ptrSize()) { .One, .Slice => {}, .Many, .C => { - // Pointer arithmetic. const op_src = src; // TODO better source location const air_tag: Air.Inst.Tag = switch (zir_tag) { .add => .ptr_add, @@ -7095,24 +7094,7 @@ fn analyzeArithmetic( .{@tagName(zir_tag)}, ), }; - // TODO if the operand is comptime-known to be negative, or is a negative int, - // coerce to isize instead of usize. - const casted_rhs = try sema.coerce(block, Type.usize, rhs, rhs_src); - const runtime_src = runtime_src: { - if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { - if (try sema.resolveDefinedValue(block, rhs_src, casted_rhs)) |rhs_val| { - _ = lhs_val; - _ = rhs_val; - return sema.fail(block, src, "TODO implement Sema for comptime pointer arithmetic", .{}); - } else { - break :runtime_src rhs_src; - } - } else { - break :runtime_src lhs_src; - } - }; - try sema.requireRuntimeBlock(block, runtime_src); - return block.addBinOp(air_tag, lhs, casted_rhs); + return analyzePtrArithmetic(sema, block, op_src, lhs, rhs, air_tag, lhs_src, rhs_src); }, }; @@ -7716,6 +7698,38 @@ fn analyzeArithmetic( return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } +fn analyzePtrArithmetic( + sema: *Sema, + block: *Block, + op_src: LazySrcLoc, + ptr: Air.Inst.Ref, + uncasted_offset: Air.Inst.Ref, + air_tag: Air.Inst.Tag, + ptr_src: LazySrcLoc, + offset_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + // TODO if the operand is comptime-known to be negative, or is a negative int, + // coerce to isize instead of usize. + const offset = try sema.coerce(block, Type.usize, uncasted_offset, offset_src); + // TODO adjust the return type according to alignment and other factors + const runtime_src = rs: { + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { + if (try sema.resolveDefinedValue(block, offset_src, offset)) |offset_val| { + if (air_tag == .ptr_sub) { + return sema.fail(block, op_src, "TODO implement Sema comptime pointer subtraction", .{}); + } + const offset_int = offset_val.toUnsignedInt(); + const new_ptr_val = try ptr_val.elemPtr(sema.arena, offset_int); + const new_ptr_ty = sema.typeOf(ptr); + return sema.addConstant(new_ptr_ty, new_ptr_val); + } else break :rs offset_src; + } else break :rs ptr_src; + }; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addBinOp(air_tag, ptr, offset); +} + fn zirLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -10820,33 +10834,13 @@ fn fieldVal( try sema.analyzeLoad(block, src, object, object_src) else object; - - const buf = try arena.create(Type.SlicePtrFieldTypeBuffer); - const result_ty = inner_ty.slicePtrFieldType(buf); - - if (try sema.resolveMaybeUndefVal(block, object_src, slice)) |val| { - if (val.isUndef()) return sema.addConstUndef(result_ty); - return sema.addConstant(result_ty, val.slicePtr()); - } - try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.slice_ptr, result_ty, slice); + return sema.analyzeSlicePtr(block, src, slice, inner_ty, object_src); } else if (mem.eql(u8, field_name, "len")) { const slice = if (is_pointer_to) try sema.analyzeLoad(block, src, object, object_src) else object; - - const result_ty = Type.usize; - - if (try sema.resolveMaybeUndefVal(block, object_src, slice)) |val| { - if (val.isUndef()) return sema.addConstUndef(result_ty); - return sema.addConstant( - result_ty, - try Value.Tag.int_u64.create(arena, val.sliceLen()), - ); - } - try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.slice_len, result_ty, slice); + return sema.analyzeSliceLen(block, src, slice); } else { return sema.fail( block, @@ -12349,8 +12343,13 @@ fn coerceArrayPtrToSlice( inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { - // The comptime Value representation is compatible with both types. - return sema.addConstant(dest_ty, val); + const ptr_array_ty = sema.typeOf(inst); + const array_ty = ptr_array_ty.childType(); + const slice_val = try Value.Tag.slice.create(sema.arena, .{ + .ptr = val, + .len = try Value.Tag.int_u64.create(sema.arena, array_ty.arrayLen()), + }); + return sema.addConstant(dest_ty, slice_val); } try sema.requireRuntimeBlock(block, inst_src); return block.addTyOp(.array_to_slice, dest_ty, inst); @@ -12632,6 +12631,25 @@ fn analyzeLoad( return block.addTyOp(.load, elem_ty, ptr); } +fn analyzeSlicePtr( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + slice: Air.Inst.Ref, + slice_ty: Type, + slice_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const buf = try sema.arena.create(Type.SlicePtrFieldTypeBuffer); + const result_ty = slice_ty.slicePtrFieldType(buf); + + if (try sema.resolveMaybeUndefVal(block, slice_src, slice)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_ty); + return sema.addConstant(result_ty, val.slicePtr()); + } + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.slice_ptr, result_ty, slice); +} + fn analyzeSliceLen( sema: *Sema, block: *Block, @@ -12703,74 +12721,128 @@ fn analyzeSlice( sema: *Sema, block: *Block, src: LazySrcLoc, - array_ptr: Air.Inst.Ref, - start: Air.Inst.Ref, - end_opt: Air.Inst.Ref, + ptr_ptr: Air.Inst.Ref, + uncasted_start: Air.Inst.Ref, + uncasted_end_opt: Air.Inst.Ref, sentinel_opt: Air.Inst.Ref, sentinel_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - const array_ptr_ty = sema.typeOf(array_ptr); - const ptr_child = switch (array_ptr_ty.zigTypeTag()) { - .Pointer => array_ptr_ty.elemType(), - else => return sema.fail(block, src, "expected pointer, found '{}'", .{array_ptr_ty}), + const ptr_src = src; // TODO better source location + const start_src = src; // TODO better source location + const end_src = src; // TODO better source location + // Slice expressions can operate on a variable whose type is an array. This requires + // the slice operand to be a pointer. In the case of a non-array, it will be a double pointer. + const ptr_ptr_ty = sema.typeOf(ptr_ptr); + const ptr_ptr_child_ty = switch (ptr_ptr_ty.zigTypeTag()) { + .Pointer => ptr_ptr_ty.elemType(), + else => return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ptr_ty}), }; - var array_type = ptr_child; - const elem_type = switch (ptr_child.zigTypeTag()) { - .Array => ptr_child.elemType(), - .Pointer => blk: { - if (ptr_child.isSinglePointer()) { - if (ptr_child.elemType().zigTypeTag() == .Array) { - array_type = ptr_child.elemType(); - break :blk ptr_child.elemType().elemType(); + var array_ty = ptr_ptr_child_ty; + var slice_ty = ptr_ptr_ty; + var ptr_or_slice = ptr_ptr; + var elem_ty = ptr_ptr_child_ty.childType(); + switch (ptr_ptr_child_ty.zigTypeTag()) { + .Array => {}, + .Pointer => { + if (ptr_ptr_child_ty.isSinglePointer()) { + const double_child_ty = ptr_ptr_child_ty.childType(); + if (double_child_ty.zigTypeTag() == .Array) { + ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src); + slice_ty = ptr_ptr_child_ty; + array_ty = double_child_ty; + elem_ty = double_child_ty.childType(); + } else { + return sema.fail(block, ptr_src, "slice of single-item pointer", .{}); } - - return sema.fail(block, src, "slice of single-item pointer", .{}); } - break :blk ptr_child.elemType(); }, - else => return sema.fail(block, src, "slice of non-array type '{}'", .{ptr_child}), + else => return sema.fail(block, ptr_src, "slice of non-array type '{}'", .{ptr_ptr_child_ty}), + } + const ptr = if (slice_ty.isSlice()) + try sema.analyzeSlicePtr(block, src, ptr_or_slice, slice_ty, ptr_src) + else + ptr_or_slice; + + const start = try sema.coerce(block, Type.usize, uncasted_start, start_src); + const new_ptr = try analyzePtrArithmetic(sema, block, src, ptr, start, .ptr_add, ptr_src, start_src); + + const end = e: { + if (uncasted_end_opt != .none) { + break :e try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); + } + + if (array_ty.zigTypeTag() == .Array) { + break :e try sema.addConstant( + Type.usize, + try Value.Tag.int_u64.create(sema.arena, array_ty.arrayLen()), + ); + } else if (slice_ty.isSlice()) { + break :e try sema.analyzeSliceLen(block, src, ptr_or_slice); + } + return sema.fail(block, end_src, "slice of pointer must include end value", .{}); }; const slice_sentinel = if (sentinel_opt != .none) blk: { - const casted = try sema.coerce(block, elem_type, sentinel_opt, sentinel_src); + const casted = try sema.coerce(block, elem_ty, sentinel_opt, sentinel_src); break :blk try sema.resolveConstValue(block, sentinel_src, casted); } else null; - var return_ptr_size: std.builtin.TypeInfo.Pointer.Size = .Slice; - var return_elem_type = elem_type; - if (end_opt != .none) { - if (try sema.resolveDefinedValue(block, src, end_opt)) |end_val| { - if (try sema.resolveDefinedValue(block, src, start)) |start_val| { - const start_u64 = start_val.toUnsignedInt(); - const end_u64 = end_val.toUnsignedInt(); - if (start_u64 > end_u64) { - return sema.fail(block, src, "out of bounds slice", .{}); - } + const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src); - const len = end_u64 - start_u64; - const array_sentinel = if (array_type.zigTypeTag() == .Array and end_u64 == array_type.arrayLen()) - array_type.sentinel() - else - slice_sentinel; - return_elem_type = try Type.array(sema.arena, len, array_sentinel, elem_type); - return_ptr_size = .One; - } + const opt_new_ptr_val = try sema.resolveDefinedValue(block, ptr_src, new_ptr); + const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len); + + const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data; + + if (opt_new_len_val) |new_len_val| { + const new_len_int = new_len_val.toUnsignedInt(); + + const sentinel = if (array_ty.zigTypeTag() == .Array and new_len_int == array_ty.arrayLen()) + array_ty.sentinel() + else + slice_sentinel; + + const return_ty = try Type.ptr(sema.arena, .{ + .pointee_type = try Type.array(sema.arena, new_len_int, sentinel, elem_ty), + .sentinel = null, + .@"align" = new_ptr_ty_info.@"align", + .@"addrspace" = new_ptr_ty_info.@"addrspace", + .mutable = new_ptr_ty_info.mutable, + .@"allowzero" = new_ptr_ty_info.@"allowzero", + .@"volatile" = new_ptr_ty_info.@"volatile", + .size = .One, + }); + + if (opt_new_ptr_val) |new_ptr_val| { + return sema.addConstant(return_ty, new_ptr_val); + } else { + return block.addTyOp(.bitcast, return_ty, new_ptr); } } - const return_type = try Type.ptr(sema.arena, .{ - .pointee_type = return_elem_type, - .sentinel = if (end_opt == .none) slice_sentinel else null, - .@"align" = 0, // TODO alignment - .@"addrspace" = if (ptr_child.zigTypeTag() == .Pointer) ptr_child.ptrAddressSpace() else .generic, - .mutable = !ptr_child.isConstPtr(), - .@"allowzero" = ptr_child.isAllowzeroPtr(), - .@"volatile" = ptr_child.isVolatilePtr(), - .size = return_ptr_size, + + const return_ty = try Type.ptr(sema.arena, .{ + .pointee_type = elem_ty, + .sentinel = slice_sentinel, + .@"align" = new_ptr_ty_info.@"align", + .@"addrspace" = new_ptr_ty_info.@"addrspace", + .mutable = new_ptr_ty_info.mutable, + .@"allowzero" = new_ptr_ty_info.@"allowzero", + .@"volatile" = new_ptr_ty_info.@"volatile", + .size = .Slice, }); - _ = return_type; - return sema.fail(block, src, "TODO implement analysis of slice", .{}); + try sema.requireRuntimeBlock(block, src); + return block.addInst(.{ + .tag = .slice, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(return_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = new_ptr, + .rhs = new_len, + }), + } }, + }); } /// Asserts that lhs and rhs types are both numeric. diff --git a/src/Zir.zig b/src/Zir.zig index 4d8cd579474493bef04eff37d1f82951b79eeba0..b21d12e33e84bc773b9144bd0ac64a15caadbff7 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -482,6 +482,7 @@ pub const Inst = struct { /// Includes a token source location. /// Uses the `un_tok` union field. /// The operand needs to get coerced to the function's return type. + /// TODO rename this to `ret_tok` because coercion is now done unconditionally in Sema. ret_coerce, /// Sends control flow back to the function's callee. /// The return operand is `error.foo` where `foo` is given by the string. diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 34d90902247c81d9bed4c7ba4be68467f41a02f9..bb2fc471cf1acaf638490fb86159a904ca4ab2e4 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -417,6 +417,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .shl_sat => try self.airShlSat(inst), .min => try self.airMin(inst), .max => try self.airMax(inst), + .slice => try self.airSlice(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -874,6 +875,12 @@ fn airMax(self: *Self, inst: Air.Inst.Index) !void { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } +fn airSlice(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement slice for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); +} + fn airAdd(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement add for {}", .{self.target.cpu.arch}); diff --git a/src/codegen.zig b/src/codegen.zig index bedbdf44f15c852837445616164bf9f6f2b1628a..13169db1fded8951e4408ac2fb374fd80b93140d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -765,6 +765,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .shl_sat => try self.airShlSat(inst), .min => try self.airMin(inst), .max => try self.airMax(inst), + .slice => try self.airSlice(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -1244,6 +1245,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airSlice(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement slice for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airAdd(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1bb337743d51dbd6c17f23f20e55d4c9e8e49dac..6e03de1cca5ba6560ba06acd7edc405208bc1009 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -992,6 +992,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .min => try airMinMax(f, inst, "<"), .max => try airMinMax(f, inst, ">"), + .slice => try airSlice(f, inst), + .cmp_eq => try airBinOp(f, inst, " == "), .cmp_gt => try airBinOp(f, inst, " > "), .cmp_gte => try airBinOp(f, inst, " >= "), @@ -1104,8 +1106,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO } fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { - if (f.liveness.isUnused(inst)) - return CValue.none; + if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); @@ -1641,6 +1642,26 @@ fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValu return local; } +fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const ptr = try f.resolveInst(bin_op.lhs); + const len = try f.resolveInst(bin_op.rhs); + + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + + try writer.writeAll(" = {"); + try f.writeCValue(writer, ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, len); + try writer.writeAll("};\n"); + + return local; +} + fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Call, pl_op.payload); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index e9dccb4009c4c4a98211cfdd867422539abaee1c..f924c0afc4ad4eb7da933c14d20cd8a7beb694e3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1091,11 +1091,22 @@ pub const DeclGen = struct { const elem_ptr = tv.val.castTag(.elem_ptr).?.data; const parent_ptr = try self.lowerParentPtr(elem_ptr.array_ptr); const llvm_usize = try self.llvmType(Type.usize); - const indices: [2]*const llvm.Value = .{ - llvm_usize.constInt(0, .False), - llvm_usize.constInt(elem_ptr.index, .False), - }; - return parent_ptr.constInBoundsGEP(&indices, indices.len); + if (parent_ptr.typeOf().getElementType().getTypeKind() == .Array) { + const indices: [2]*const llvm.Value = .{ + llvm_usize.constInt(0, .False), + llvm_usize.constInt(elem_ptr.index, .False), + }; + return parent_ptr.constInBoundsGEP(&indices, indices.len); + } else { + const indices: [1]*const llvm.Value = .{ + llvm_usize.constInt(elem_ptr.index, .False), + }; + return parent_ptr.constInBoundsGEP(&indices, indices.len); + } + }, + .null_value => { + const llvm_type = try self.llvmType(tv.ty); + return llvm_type.constNull(); }, else => |tag| return self.todo("implement const of pointer type '{}' ({})", .{ tv.ty, tag }), }, @@ -1666,6 +1677,7 @@ pub const FuncGen = struct { .shl_exact => try self.airShlExact(inst), .min => try self.airMin(inst), .max => try self.airMax(inst), + .slice => try self.airSlice(inst), .bit_and, .bool_and => try self.airAnd(inst), .bit_or, .bool_or => try self.airOr(inst), @@ -2124,8 +2136,7 @@ pub const FuncGen = struct { } fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand = try self.resolveInst(ty_op.operand); @@ -2721,6 +2732,19 @@ pub const FuncGen = struct { return self.builder.buildUMax(lhs, rhs, ""); } + fn airSlice(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ptr = try self.resolveInst(bin_op.lhs); + const len = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + const llvm_slice_ty = try self.dg.llvmType(inst_ty); + + const partial = self.builder.buildInsertValue(llvm_slice_ty.getUndef(), ptr, 0, ""); + return self.builder.buildInsertValue(partial, len, 1, ""); + } + fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -2886,26 +2910,42 @@ pub const FuncGen = struct { } fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const base_ptr = try self.resolveInst(bin_op.lhs); const offset = try self.resolveInst(bin_op.rhs); - const indices: [1]*const llvm.Value = .{offset}; - return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + const ptr_ty = self.air.typeOf(bin_op.lhs); + if (ptr_ty.ptrSize() == .One) { + // It's a pointer to an array, so according to LLVM we need an extra GEP index. + const indices: [2]*const llvm.Value = .{ + self.context.intType(32).constNull(), offset, + }; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } else { + const indices: [1]*const llvm.Value = .{offset}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } } fn airPtrSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const base_ptr = try self.resolveInst(bin_op.lhs); const offset = try self.resolveInst(bin_op.rhs); const negative_offset = self.builder.buildNeg(offset, ""); - const indices: [1]*const llvm.Value = .{negative_offset}; - return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + const ptr_ty = self.air.typeOf(bin_op.lhs); + if (ptr_ty.ptrSize() == .One) { + // It's a pointer to an array, so according to LLVM we need an extra GEP index. + const indices: [2]*const llvm.Value = .{ + self.context.intType(32).constNull(), negative_offset, + }; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } else { + const indices: [1]*const llvm.Value = .{negative_offset}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } } fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index c583d0cd0019d77ade35cd75ffa5ef4cf14c87a9..297450a03220b89e35fc221e6242c2231d9f3d7f 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -234,6 +234,9 @@ pub const Type = opaque { pub const getTypeKind = LLVMGetTypeKind; extern fn LLVMGetTypeKind(Ty: *const Type) TypeKind; + + pub const getElementType = LLVMGetElementType; + extern fn LLVMGetElementType(Ty: *const Type) *const Type; }; pub const Module = opaque { diff --git a/src/print_air.zig b/src/print_air.zig index 3d1ab225f446d10c8f07c4d9f05af75ba99cbd17..a9485a57f9c0df3b81bb5ea4473c73f76824f7df 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -140,6 +140,7 @@ const Writer = struct { .set_union_tag, .min, .max, + .slice, => try w.writeBinOp(s, inst), .is_null, diff --git a/src/type.zig b/src/type.zig index 259a6b8c70b5604ab32ba7f45450679a25661c84..fceb3c4dfdfc6b6985e85f83f6228317dd306e4d 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1529,6 +1529,7 @@ pub const Type = extern union { return fast_result; } + /// Returns 0 if the pointer is naturally aligned and the element type is 0-bit. pub fn ptrAlignment(self: Type, target: Target) u32 { switch (self.tag()) { .single_const_pointer, @@ -1739,10 +1740,10 @@ pub const Type = extern union { .empty_struct, .void, + .c_void, => return 0, .empty_struct_literal, - .c_void, .type, .comptime_int, .comptime_float, diff --git a/src/value.zig b/src/value.zig index 7c24cdab8132db5ba35acce932d387138d6c20b7..f1ca384b75a033d25a2cfd994ac56241a6072a6f 100644 --- a/src/value.zig +++ b/src/value.zig @@ -761,6 +761,7 @@ pub const Value = extern union { return decl_val.toAllocatedBytes(decl.ty, allocator); }, .the_only_possible_value => return &[_]u8{}, + .slice => return toAllocatedBytes(val.castTag(.slice).?.data.ptr, ty, allocator), else => unreachable, } } @@ -1402,6 +1403,16 @@ pub const Value = extern union { var buffer: Type.Payload.ElemType = undefined; return eql(a_payload, b_payload, ty.optionalChild(&buffer)); }, + .slice => { + const a_payload = a.castTag(.slice).?.data; + const b_payload = b.castTag(.slice).?.data; + if (!eql(a_payload.len, b_payload.len, Type.usize)) return false; + + var ptr_buf: Type.SlicePtrFieldTypeBuffer = undefined; + const ptr_ty = ty.slicePtrFieldType(&ptr_buf); + + return eql(a_payload.ptr, b_payload.ptr, ptr_ty); + }, .elem_ptr => @panic("TODO: Implement more pointer eql cases"), .field_ptr => @panic("TODO: Implement more pointer eql cases"), .eu_payload_ptr => @panic("TODO: Implement more pointer eql cases"), @@ -1475,6 +1486,14 @@ pub const Value = extern union { .variable, => std.hash.autoHash(hasher, val.pointerDecl().?), + .slice => { + const slice = val.castTag(.slice).?.data; + var ptr_buf: Type.SlicePtrFieldTypeBuffer = undefined; + const ptr_ty = ty.slicePtrFieldType(&ptr_buf); + hash(slice.ptr, ptr_ty, hasher); + hash(slice.len, Type.usize, hasher); + }, + .elem_ptr => @panic("TODO: Implement more pointer hashing cases"), .field_ptr => @panic("TODO: Implement more pointer hashing cases"), .eu_payload_ptr => @panic("TODO: Implement more pointer hashing cases"), diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 3d603ff73283308fb446bc1e6f0db442b053721a..83e121101ade8168c3e65f874b9c4dfa75b3d617 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -395,10 +395,6 @@ test "f32 at compile time is lossy" { try expect(@as(f32, 1 << 24) + 1 == 1 << 24); } -test "f32 at compile time is lossy" { - try expect(@as(f32, 1 << 24) + 1 == 1 << 24); -} - test "f64 at compile time is lossy" { try expect(@as(f64, 1 << 53) + 1 == 1 << 53); } diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 5dc652d6781d1e7b0956eff324b0e80a450cf285..19c9e7e7730aec97c2971884e97491c49a0776c5 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -24,3 +24,88 @@ comptime { var pos = S.indexOfScalar(type, list, c_ulong).?; if (pos != 1) @compileError("bad pos"); } + +test "slicing" { + var array: [20]i32 = undefined; + + array[5] = 1234; + + var slice = array[5..10]; + + if (slice.len != 5) unreachable; + + const ptr = &slice[0]; + if (ptr.* != 1234) unreachable; + + var slice_rest = array[10..]; + if (slice_rest.len != 10) unreachable; +} + +test "const slice" { + comptime { + const a = "1234567890"; + try expect(a.len == 10); + const b = a[1..2]; + try expect(b.len == 1); + try expect(b[0] == '2'); + } +} + +test "comptime slice of undefined pointer of length 0" { + const slice1 = @as([*]i32, undefined)[0..0]; + try expect(slice1.len == 0); + const slice2 = @as([*]i32, undefined)[100..100]; + try expect(slice2.len == 0); +} + +test "implicitly cast array of size 0 to slice" { + var msg = [_]u8{}; + try assertLenIsZero(&msg); +} + +fn assertLenIsZero(msg: []const u8) !void { + try expect(msg.len == 0); +} + +test "access len index of sentinel-terminated slice" { + const S = struct { + fn doTheTest() !void { + var slice: [:0]const u8 = "hello"; + + try expect(slice.len == 5); + try expect(slice[5] == 0); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "comptime slice of slice preserves comptime var" { + comptime { + var buff: [10]u8 = undefined; + buff[0..][0..][0] = 1; + try expect(buff[0..][0..][0] == 1); + } +} + +test "slice of type" { + comptime { + var types_array = [_]type{ i32, f64, type }; + for (types_array) |T, i| { + switch (i) { + 0 => try expect(T == i32), + 1 => try expect(T == f64), + 2 => try expect(T == type), + else => unreachable, + } + } + for (types_array[0..]) |T, i| { + switch (i) { + 0 => try expect(T == i32), + 1 => try expect(T == f64), + 2 => try expect(T == type), + else => unreachable, + } + } + } +} diff --git a/test/behavior/slice_stage1.zig b/test/behavior/slice_stage1.zig index ab45d14543dd37e70bd45c4f2e72c35f6b7175e5..cc472a4ff6f633272aa601f13fe55268277dd7bd 100644 --- a/test/behavior/slice_stage1.zig +++ b/test/behavior/slice_stage1.zig @@ -4,39 +4,6 @@ const expectEqualSlices = std.testing.expectEqualSlices; const expectEqual = std.testing.expectEqual; const mem = std.mem; -test "slicing" { - var array: [20]i32 = undefined; - - array[5] = 1234; - - var slice = array[5..10]; - - if (slice.len != 5) unreachable; - - const ptr = &slice[0]; - if (ptr.* != 1234) unreachable; - - var slice_rest = array[10..]; - if (slice_rest.len != 10) unreachable; -} - -test "const slice" { - comptime { - const a = "1234567890"; - try expect(a.len == 10); - const b = a[1..2]; - try expect(b.len == 1); - try expect(b[0] == '2'); - } -} - -test "comptime slice of undefined pointer of length 0" { - const slice1 = @as([*]i32, undefined)[0..0]; - try expect(slice1.len == 0); - const slice2 = @as([*]i32, undefined)[100..100]; - try expect(slice2.len == 0); -} - test "slicing zero length array" { const s1 = ""[0..]; const s2 = ([_]u32{})[0..]; @@ -97,15 +64,6 @@ fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 { return a_slice[start..end]; } -test "implicitly cast array of size 0 to slice" { - var msg = [_]u8{}; - try assertLenIsZero(&msg); -} - -fn assertLenIsZero(msg: []const u8) !void { - try expect(msg.len == 0); -} - test "C pointer" { var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf"; var len: u32 = 10; @@ -150,19 +108,6 @@ test "slice type with custom alignment" { try expect(array[1].anything == 42); } -test "access len index of sentinel-terminated slice" { - const S = struct { - fn doTheTest() !void { - var slice: [:0]const u8 = "hello"; - - try expect(slice.len == 5); - try expect(slice[5] == 0); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - test "obtaining a null terminated slice" { // here we have a normal array var buf: [50]u8 = undefined; @@ -407,14 +352,6 @@ test "type coercion of pointer to anon struct literal to pointer to slice" { comptime try S.doTheTest(); } -test "comptime slice of slice preserves comptime var" { - comptime { - var buff: [10]u8 = undefined; - buff[0..][0..][0] = 1; - try expect(buff[0..][0..][0] == 1); - } -} - test "comptime slice of pointer preserves comptime var" { comptime { var buff: [10]u8 = undefined; @@ -433,28 +370,6 @@ test "array concat of slices gives slice" { } } -test "slice of type" { - comptime { - var types_array = [_]type{ i32, f64, type }; - for (types_array) |T, i| { - switch (i) { - 0 => try expect(T == i32), - 1 => try expect(T == f64), - 2 => try expect(T == type), - else => unreachable, - } - } - for (types_array[0..]) |T, i| { - switch (i) { - 0 => try expect(T == i32), - 1 => try expect(T == f64), - 2 => try expect(T == type), - else => unreachable, - } - } - } -} - test "comptime pointer cast array and then slice" { const array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; -- 2.54.0