mirror of
https://github.com/ziglang/zig.git
synced 2024-12-12 14:16:59 +00:00
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.
This commit is contained in:
parent
3b2e25ed87
commit
a0e195120d
@ -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,
|
||||
|
@ -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 => {
|
||||
|
@ -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 });
|
||||
|
258
src/Sema.zig
258
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,
|
||||
});
|
||||
_ = return_type;
|
||||
|
||||
return sema.fail(block, src, "TODO implement analysis of slice", .{});
|
||||
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,
|
||||
});
|
||||
|
||||
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.
|
||||
|
@ -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.
|
||||
|
@ -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});
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -140,6 +140,7 @@ const Writer = struct {
|
||||
.set_union_tag,
|
||||
.min,
|
||||
.max,
|
||||
.slice,
|
||||
=> try w.writeBinOp(s, inst),
|
||||
|
||||
.is_null,
|
||||
|
@ -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,
|
||||
|
@ -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"),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user