diff options
| -rw-r--r-- | src/Air.zig | 4 | ||||
| -rw-r--r-- | src/AstGen.zig | 36 | ||||
| -rw-r--r-- | src/Liveness.zig | 1 | ||||
| -rw-r--r-- | src/Sema.zig | 256 | ||||
| -rw-r--r-- | src/Zir.zig | 1 | ||||
| -rw-r--r-- | src/arch/aarch64/CodeGen.zig | 7 | ||||
| -rw-r--r-- | src/codegen.zig | 9 | ||||
| -rw-r--r-- | src/codegen/c.zig | 25 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 70 | ||||
| -rw-r--r-- | src/codegen/llvm/bindings.zig | 3 | ||||
| -rw-r--r-- | src/print_air.zig | 1 | ||||
| -rw-r--r-- | src/type.zig | 3 | ||||
| -rw-r--r-- | src/value.zig | 19 | ||||
| -rw-r--r-- | test/behavior/eval.zig | 4 | ||||
| -rw-r--r-- | test/behavior/slice.zig | 85 | ||||
| -rw-r--r-- | test/behavior/slice_stage1.zig | 85 |
16 files changed, 383 insertions, 226 deletions
diff --git a/src/Air.zig b/src/Air.zig index f3e0865ba8..91b496a8e4 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 e98e04f7d4..e67ae6cddf 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 c56bcc8c09..5d5bb64196 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 39e4dfe368..8ac31387f4 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 4d8cd57947..b21d12e33e 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 34d9090224..bb2fc471cf 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 bedbdf44f1..13169db1fd 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 1bb337743d..6e03de1cca 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 e9dccb4009..f924c0afc4 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 c583d0cd00..297450a032 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 3d1ab225f4..a9485a57f9 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 259a6b8c70..fceb3c4dfd 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 7c24cdab81..f1ca384b75 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 3d603ff732..83e121101a 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 5dc652d678..19c9e7e773 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 ab45d14543..cc472a4ff6 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 }; |
