diff options
Diffstat (limited to 'src/Sema.zig')
| -rw-r--r-- | src/Sema.zig | 304 |
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; |
