aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Air.zig4
-rw-r--r--src/AstGen.zig36
-rw-r--r--src/Liveness.zig1
-rw-r--r--src/Sema.zig256
-rw-r--r--src/Zir.zig1
-rw-r--r--src/arch/aarch64/CodeGen.zig7
-rw-r--r--src/codegen.zig9
-rw-r--r--src/codegen/c.zig25
-rw-r--r--src/codegen/llvm.zig70
-rw-r--r--src/codegen/llvm/bindings.zig3
-rw-r--r--src/print_air.zig1
-rw-r--r--src/type.zig3
-rw-r--r--src/value.zig19
-rw-r--r--test/behavior/eval.zig4
-rw-r--r--test/behavior/slice.zig85
-rw-r--r--test/behavior/slice_stage1.zig85
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 };