aboutsummaryrefslogtreecommitdiff
path: root/src/Sema.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Sema.zig')
-rw-r--r--src/Sema.zig304
1 files changed, 220 insertions, 84 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index 165f629aab..7504352576 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -836,7 +836,12 @@ pub fn analyzeBody(
continue;
},
.validate_array_init => {
- try sema.zirValidateArrayInit(block, inst);
+ try sema.zirValidateArrayInit(block, inst, false);
+ i += 1;
+ continue;
+ },
+ .validate_array_init_comptime => {
+ try sema.zirValidateArrayInit(block, inst, true);
i += 1;
continue;
},
@@ -2815,13 +2820,18 @@ fn validateStructInit(
}
}
-fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
+fn zirValidateArrayInit(
+ sema: *Sema,
+ block: *Block,
+ inst: Zir.Inst.Index,
+ is_comptime: bool,
+) CompileError!void {
const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;
const init_src = validate_inst.src();
const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len];
- const elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
- const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, elem_ptr_data.payload_index).data;
+ const first_elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
+ const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, first_elem_ptr_data.payload_index).data;
const array_ptr = sema.resolveInst(elem_ptr_extra.ptr);
const array_ty = sema.typeOf(array_ptr).childType();
const array_len = array_ty.arrayLen();
@@ -2831,6 +2841,82 @@ fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
array_len, instrs.len,
});
}
+
+ if (is_comptime or block.is_comptime) {
+ // In this case the comptime machinery will have evaluated the store instructions
+ // at comptime and we have nothing to do here.
+ return;
+ }
+
+ var array_is_comptime = true;
+ var first_block_index: usize = std.math.maxInt(u32);
+
+ // Collect the comptime element values in case the array literal ends up
+ // being comptime-known.
+ const element_vals = try sema.arena.alloc(Value, instrs.len);
+ const opt_opv = try sema.typeHasOnePossibleValue(block, init_src, array_ty);
+ const air_tags = sema.air_instructions.items(.tag);
+ const air_datas = sema.air_instructions.items(.data);
+
+ for (instrs) |elem_ptr, i| {
+ const elem_ptr_data = sema.code.instructions.items(.data)[elem_ptr].pl_node;
+ const elem_src: LazySrcLoc = .{ .node_offset = elem_ptr_data.src_node };
+
+ // Determine whether the value stored to this pointer is comptime-known.
+
+ if (opt_opv) |opv| {
+ element_vals[i] = opv;
+ continue;
+ }
+
+ const elem_ptr_air_ref = sema.inst_map.get(elem_ptr).?;
+ const elem_ptr_air_inst = Air.refToIndex(elem_ptr_air_ref).?;
+ // Find the block index of the elem_ptr so that we can look at the next
+ // instruction after it within the same block.
+ // Possible performance enhancement: save the `block_index` between iterations
+ // of the for loop.
+ const next_air_inst = inst: {
+ var block_index = block.instructions.items.len - 1;
+ while (block.instructions.items[block_index] != elem_ptr_air_inst) {
+ block_index -= 1;
+ }
+ first_block_index = @minimum(first_block_index, block_index);
+ break :inst block.instructions.items[block_index + 1];
+ };
+
+ // If the next instructon is a store with a comptime operand, this element
+ // is comptime.
+ switch (air_tags[next_air_inst]) {
+ .store => {
+ const bin_op = air_datas[next_air_inst].bin_op;
+ if (bin_op.lhs != elem_ptr_air_ref) {
+ array_is_comptime = false;
+ continue;
+ }
+ if (try sema.resolveMaybeUndefValAllowVariables(block, elem_src, bin_op.rhs)) |val| {
+ element_vals[i] = val;
+ } else {
+ array_is_comptime = false;
+ }
+ continue;
+ },
+ else => {
+ array_is_comptime = false;
+ continue;
+ },
+ }
+ }
+
+ if (array_is_comptime) {
+ // Our task is to delete all the `elem_ptr` and `store` instructions, and insert
+ // instead a single `store` to the array_ptr with a comptime struct value.
+
+ block.instructions.shrinkRetainingCapacity(first_block_index);
+
+ const array_val = try Value.Tag.array.create(sema.arena, element_vals);
+ const array_init = try sema.addConstant(array_ty, array_val);
+ try sema.storePtr2(block, init_src, array_ptr, init_src, array_init, init_src, .store);
+ }
}
fn failWithBadMemberAccess(
@@ -14085,88 +14171,112 @@ fn beginComptimePtrMutation(
.elem_ptr => {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr);
- const elem_ty = parent.ty.childType();
- switch (parent.val.tag()) {
- .undef => {
- // An array has been initialized to undefined at comptime and now we
- // are for the first time setting an element. We must change the representation
- // of the array from `undef` to `array`.
- const arena = parent.beginArena(sema.gpa);
- defer parent.finishArena();
+ switch (parent.ty.zigTypeTag()) {
+ .Array, .Vector => {
+ const check_len = parent.ty.arrayLenIncludingSentinel();
+ if (elem_ptr.index >= check_len) {
+ // TODO have the parent include the decl so we can say "declared here"
+ return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{
+ elem_ptr.index, check_len,
+ });
+ }
+ const elem_ty = parent.ty.childType();
+ switch (parent.val.tag()) {
+ .undef => {
+ // An array has been initialized to undefined at comptime and now we
+ // are for the first time setting an element. We must change the representation
+ // of the array from `undef` to `array`.
+ const arena = parent.beginArena(sema.gpa);
+ defer parent.finishArena();
+
+ const array_len_including_sentinel =
+ try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
+ const elems = try arena.alloc(Value, array_len_including_sentinel);
+ mem.set(Value, elems, Value.undef);
+
+ parent.val.* = try Value.Tag.array.create(arena, elems);
- const array_len_including_sentinel =
- try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
- const elems = try arena.alloc(Value, array_len_including_sentinel);
- mem.set(Value, elems, Value.undef);
+ return ComptimePtrMutationKit{
+ .decl_ref_mut = parent.decl_ref_mut,
+ .val = &elems[elem_ptr.index],
+ .ty = elem_ty,
+ };
+ },
+ .bytes => {
+ // An array is memory-optimized to store a slice of bytes, but we are about
+ // to modify an individual field and the representation has to change.
+ // If we wanted to avoid this, there would need to be special detection
+ // elsewhere to identify when writing a value to an array element that is stored
+ // using the `bytes` tag, and handle it without making a call to this function.
+ const arena = parent.beginArena(sema.gpa);
+ defer parent.finishArena();
+
+ const bytes = parent.val.castTag(.bytes).?.data;
+ const dest_len = parent.ty.arrayLenIncludingSentinel();
+ // bytes.len may be one greater than dest_len because of the case when
+ // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
+ assert(bytes.len >= dest_len);
+ const elems = try arena.alloc(Value, @intCast(usize, dest_len));
+ for (elems) |*elem, i| {
+ elem.* = try Value.Tag.int_u64.create(arena, bytes[i]);
+ }
- parent.val.* = try Value.Tag.array.create(arena, elems);
+ parent.val.* = try Value.Tag.array.create(arena, elems);
- return ComptimePtrMutationKit{
- .decl_ref_mut = parent.decl_ref_mut,
- .val = &elems[elem_ptr.index],
- .ty = elem_ty,
- };
- },
- .bytes => {
- // An array is memory-optimized to store a slice of bytes, but we are about
- // to modify an individual field and the representation has to change.
- // If we wanted to avoid this, there would need to be special detection
- // elsewhere to identify when writing a value to an array element that is stored
- // using the `bytes` tag, and handle it without making a call to this function.
- const arena = parent.beginArena(sema.gpa);
- defer parent.finishArena();
+ return ComptimePtrMutationKit{
+ .decl_ref_mut = parent.decl_ref_mut,
+ .val = &elems[elem_ptr.index],
+ .ty = elem_ty,
+ };
+ },
+ .repeated => {
+ // An array is memory-optimized to store only a single element value, and
+ // that value is understood to be the same for the entire length of the array.
+ // However, now we want to modify an individual field and so the
+ // representation has to change. If we wanted to avoid this, there would
+ // need to be special detection elsewhere to identify when writing a value to an
+ // array element that is stored using the `repeated` tag, and handle it
+ // without making a call to this function.
+ const arena = parent.beginArena(sema.gpa);
+ defer parent.finishArena();
+
+ const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena);
+ const array_len_including_sentinel =
+ try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
+ const elems = try arena.alloc(Value, array_len_including_sentinel);
+ mem.set(Value, elems, repeated_val);
+
+ parent.val.* = try Value.Tag.array.create(arena, elems);
- const bytes = parent.val.castTag(.bytes).?.data;
- const dest_len = parent.ty.arrayLenIncludingSentinel();
- // bytes.len may be one greater than dest_len because of the case when
- // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
- assert(bytes.len >= dest_len);
- const elems = try arena.alloc(Value, @intCast(usize, dest_len));
- for (elems) |*elem, i| {
- elem.* = try Value.Tag.int_u64.create(arena, bytes[i]);
- }
+ return ComptimePtrMutationKit{
+ .decl_ref_mut = parent.decl_ref_mut,
+ .val = &elems[elem_ptr.index],
+ .ty = elem_ty,
+ };
+ },
- parent.val.* = try Value.Tag.array.create(arena, elems);
+ .array => return ComptimePtrMutationKit{
+ .decl_ref_mut = parent.decl_ref_mut,
+ .val = &parent.val.castTag(.array).?.data[elem_ptr.index],
+ .ty = elem_ty,
+ },
- return ComptimePtrMutationKit{
- .decl_ref_mut = parent.decl_ref_mut,
- .val = &elems[elem_ptr.index],
- .ty = elem_ty,
- };
+ else => unreachable,
+ }
},
- .repeated => {
- // An array is memory-optimized to store only a single element value, and
- // that value is understood to be the same for the entire length of the array.
- // However, now we want to modify an individual field and so the
- // representation has to change. If we wanted to avoid this, there would
- // need to be special detection elsewhere to identify when writing a value to an
- // array element that is stored using the `repeated` tag, and handle it
- // without making a call to this function.
- const arena = parent.beginArena(sema.gpa);
- defer parent.finishArena();
-
- const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena);
- const array_len_including_sentinel =
- try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
- const elems = try arena.alloc(Value, array_len_including_sentinel);
- mem.set(Value, elems, repeated_val);
-
- parent.val.* = try Value.Tag.array.create(arena, elems);
-
+ else => {
+ if (elem_ptr.index != 0) {
+ // TODO include a "declared here" note for the decl
+ return sema.fail(block, src, "out of bounds comptime store of index {d}", .{
+ elem_ptr.index,
+ });
+ }
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
- .val = &elems[elem_ptr.index],
- .ty = elem_ty,
+ .val = parent.val,
+ .ty = parent.ty,
};
},
-
- .array => return ComptimePtrMutationKit{
- .decl_ref_mut = parent.decl_ref_mut,
- .val = &parent.val.castTag(.array).?.data[elem_ptr.index],
- .ty = elem_ty,
- },
-
- else => unreachable,
}
},
.field_ptr => {
@@ -14296,15 +14406,41 @@ fn beginComptimePtrLoad(
.elem_ptr => {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
const parent = try beginComptimePtrLoad(sema, block, src, elem_ptr.array_ptr);
- const elem_ty = parent.ty.childType();
- const elem_size = elem_ty.abiSize(target);
- return ComptimePtrLoadKit{
- .root_val = parent.root_val,
- .val = try parent.val.elemValue(sema.arena, elem_ptr.index),
- .ty = elem_ty,
- .byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
- .is_mutable = parent.is_mutable,
- };
+ switch (parent.ty.zigTypeTag()) {
+ .Array, .Vector => {
+ const check_len = parent.ty.arrayLenIncludingSentinel();
+ if (elem_ptr.index >= check_len) {
+ // TODO have the parent include the decl so we can say "declared here"
+ return sema.fail(block, src, "comptime load of index {d} out of bounds of array length {d}", .{
+ elem_ptr.index, check_len,
+ });
+ }
+ const elem_ty = parent.ty.childType();
+ const elem_size = elem_ty.abiSize(target);
+ return ComptimePtrLoadKit{
+ .root_val = parent.root_val,
+ .val = try parent.val.elemValue(sema.arena, elem_ptr.index),
+ .ty = elem_ty,
+ .byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
+ .is_mutable = parent.is_mutable,
+ };
+ },
+ else => {
+ if (elem_ptr.index != 0) {
+ // TODO have the parent include the decl so we can say "declared here"
+ return sema.fail(block, src, "out of bounds comptime load of index {d}", .{
+ elem_ptr.index,
+ });
+ }
+ return ComptimePtrLoadKit{
+ .root_val = parent.root_val,
+ .val = parent.val,
+ .ty = parent.ty,
+ .byte_offset = parent.byte_offset,
+ .is_mutable = parent.is_mutable,
+ };
+ },
+ }
},
.field_ptr => {
const field_ptr = ptr_val.castTag(.field_ptr).?.data;