authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-12 13:31:50-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-13 22:13:44-07:00
log336d0c97feabad4c93525ba6ef73a6b6163f49c7
tree0cf55d52805897bc9ce255c903bd9dc18043973f
parentb019a19b5546d51865175359ec1ae8e5aa3f4128

stage2: detection of comptime array literals

Introduce `validate_array_init_comptime`, similar to `validate_struct_init_comptime` introduced in 713d2a9b3883942491b40738245232680877cc66. `zirValidateArrayInit` is improved to detect comptime array literals and emit AIR accordingly. This code is very similar to the changes introduced in that same commit for `zirValidateStructInit`. The C backend needed some improvements to continue passing the same set of tests: * `resolveInst` for arrays now will add a local `static const` with the array value and so then `elem_val` instructions reference that local. It memoizes accesses using `value_map`, which is changed to use `Air.Inst.Ref` as the key rather than `Air.Inst.Index`. * This required a mechanism for writing to a "header" which is lines that appear at the beginning of a function body, before everything else. * dbg_stmt output comments rather than `#line` directives. TODO comment reproduced here: We need to re-evaluate whether to emit these or not. If we naively emit these directives, the output file will report bogus line numbers because every newline after the #line directive adds one to the line. We also don't print the filename yet, so the output is strictly unhelpful. If we wanted to go this route, we would need to go all the way and not output newlines until the next dbg_stmt occurs. Perhaps an additional compilation option is in order? `Value.elemValue` is improved to support `elem_ptr` values.

8 files changed, 187 insertions(+), 18 deletions(-)

src/AstGen.zig+8-1
......@@ -1418,7 +1418,13 @@ fn arrayInitExprRlPtrInner(
14181418 extra_index += 1;
14191419 _ = try expr(gz, scope, .{ .ptr = elem_ptr }, elem_init);
14201420 }
1421 _ = try gz.addPlNodePayloadIndex(.validate_array_init, node, payload_index);
1421
1422 const tag: Zir.Inst.Tag = if (gz.force_comptime)
1423 .validate_array_init_comptime
1424 else
1425 .validate_array_init;
1426
1427 _ = try gz.addPlNodePayloadIndex(tag, node, payload_index);
14221428 return .void_value;
14231429}
14241430
......@@ -2317,6 +2323,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
23172323 .validate_struct_init,
23182324 .validate_struct_init_comptime,
23192325 .validate_array_init,
2326 .validate_array_init_comptime,
23202327 .set_align_stack,
23212328 .set_cold,
23222329 .set_float_mode,
src/Sema.zig+90-4
......@@ -836,7 +836,12 @@ pub fn analyzeBody(
836836 continue;
837837 },
838838 .validate_array_init => {
839 try sema.zirValidateArrayInit(block, inst);
839 try sema.zirValidateArrayInit(block, inst, false);
840 i += 1;
841 continue;
842 },
843 .validate_array_init_comptime => {
844 try sema.zirValidateArrayInit(block, inst, true);
840845 i += 1;
841846 continue;
842847 },
......@@ -2815,13 +2820,18 @@ fn validateStructInit(
28152820 }
28162821}
28172822
2818fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
2823fn zirValidateArrayInit(
2824 sema: *Sema,
2825 block: *Block,
2826 inst: Zir.Inst.Index,
2827 is_comptime: bool,
2828) CompileError!void {
28192829 const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;
28202830 const init_src = validate_inst.src();
28212831 const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
28222832 const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len];
2823 const elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
2824 const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, elem_ptr_data.payload_index).data;
2833 const first_elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
2834 const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, first_elem_ptr_data.payload_index).data;
28252835 const array_ptr = sema.resolveInst(elem_ptr_extra.ptr);
28262836 const array_ty = sema.typeOf(array_ptr).childType();
28272837 const array_len = array_ty.arrayLen();
......@@ -2831,6 +2841,82 @@ fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
28312841 array_len, instrs.len,
28322842 });
28332843 }
2844
2845 if (is_comptime or block.is_comptime) {
2846 // In this case the comptime machinery will have evaluated the store instructions
2847 // at comptime and we have nothing to do here.
2848 return;
2849 }
2850
2851 var array_is_comptime = true;
2852 var first_block_index: usize = std.math.maxInt(u32);
2853
2854 // Collect the comptime element values in case the array literal ends up
2855 // being comptime-known.
2856 const element_vals = try sema.arena.alloc(Value, instrs.len);
2857 const opt_opv = try sema.typeHasOnePossibleValue(block, init_src, array_ty);
2858 const air_tags = sema.air_instructions.items(.tag);
2859 const air_datas = sema.air_instructions.items(.data);
2860
2861 for (instrs) |elem_ptr, i| {
2862 const elem_ptr_data = sema.code.instructions.items(.data)[elem_ptr].pl_node;
2863 const elem_src: LazySrcLoc = .{ .node_offset = elem_ptr_data.src_node };
2864
2865 // Determine whether the value stored to this pointer is comptime-known.
2866
2867 if (opt_opv) |opv| {
2868 element_vals[i] = opv;
2869 continue;
2870 }
2871
2872 const elem_ptr_air_ref = sema.inst_map.get(elem_ptr).?;
2873 const elem_ptr_air_inst = Air.refToIndex(elem_ptr_air_ref).?;
2874 // Find the block index of the elem_ptr so that we can look at the next
2875 // instruction after it within the same block.
2876 // Possible performance enhancement: save the `block_index` between iterations
2877 // of the for loop.
2878 const next_air_inst = inst: {
2879 var block_index = block.instructions.items.len - 1;
2880 while (block.instructions.items[block_index] != elem_ptr_air_inst) {
2881 block_index -= 1;
2882 }
2883 first_block_index = @minimum(first_block_index, block_index);
2884 break :inst block.instructions.items[block_index + 1];
2885 };
2886
2887 // If the next instructon is a store with a comptime operand, this element
2888 // is comptime.
2889 switch (air_tags[next_air_inst]) {
2890 .store => {
2891 const bin_op = air_datas[next_air_inst].bin_op;
2892 if (bin_op.lhs != elem_ptr_air_ref) {
2893 array_is_comptime = false;
2894 continue;
2895 }
2896 if (try sema.resolveMaybeUndefValAllowVariables(block, elem_src, bin_op.rhs)) |val| {
2897 element_vals[i] = val;
2898 } else {
2899 array_is_comptime = false;
2900 }
2901 continue;
2902 },
2903 else => {
2904 array_is_comptime = false;
2905 continue;
2906 },
2907 }
2908 }
2909
2910 if (array_is_comptime) {
2911 // Our task is to delete all the `elem_ptr` and `store` instructions, and insert
2912 // instead a single `store` to the array_ptr with a comptime struct value.
2913
2914 block.instructions.shrinkRetainingCapacity(first_block_index);
2915
2916 const array_val = try Value.Tag.array.create(sema.arena, element_vals);
2917 const array_init = try sema.addConstant(array_ty, array_val);
2918 try sema.storePtr2(block, init_src, array_ptr, init_src, array_init, init_src, .store);
2919 }
28342920}
28352921
28362922fn failWithBadMemberAccess(
src/Zir.zig+5
......@@ -663,6 +663,9 @@ pub const Inst = struct {
663663 /// because it must use one of them to find out the array type.
664664 /// Uses the `pl_node` field. Payload is `Block`.
665665 validate_array_init,
666 /// Same as `validate_array_init` but additionally communicates that the
667 /// resulting array initialization value is within a comptime scope.
668 validate_array_init_comptime,
666669 /// A struct literal with a specified type, with no fields.
667670 /// Uses the `un_node` field.
668671 struct_init_empty,
......@@ -1087,6 +1090,7 @@ pub const Inst = struct {
10871090 .validate_struct_init,
10881091 .validate_struct_init_comptime,
10891092 .validate_array_init,
1093 .validate_array_init_comptime,
10901094 .struct_init_empty,
10911095 .struct_init,
10921096 .struct_init_ref,
......@@ -1341,6 +1345,7 @@ pub const Inst = struct {
13411345 .validate_struct_init = .pl_node,
13421346 .validate_struct_init_comptime = .pl_node,
13431347 .validate_array_init = .pl_node,
1348 .validate_array_init_comptime = .pl_node,
13441349 .struct_init_empty = .un_node,
13451350 .field_type = .pl_node,
13461351 .field_type_ref = .pl_node,
src/codegen/c.zig+64-12
......@@ -44,7 +44,7 @@ const BlockData = struct {
4444 result: CValue,
4545};
4646
47pub const CValueMap = std.AutoHashMap(Air.Inst.Index, CValue);
47pub const CValueMap = std.AutoHashMap(Air.Inst.Ref, CValue);
4848pub const TypedefMap = std.ArrayHashMap(
4949 Type,
5050 struct { name: []const u8, rendered: []u8 },
......@@ -110,11 +110,29 @@ pub const Function = struct {
110110 func: *Module.Fn,
111111
112112 fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue {
113 if (f.air.value(inst)) |_| {
114 return CValue{ .constant = inst };
113 const gop = try f.value_map.getOrPut(inst);
114 if (gop.found_existing) return gop.value_ptr.*;
115
116 const val = f.air.value(inst).?;
117 const ty = f.air.typeOf(inst);
118 switch (ty.zigTypeTag()) {
119 .Array => {
120 const writer = f.object.code_header.writer();
121 const decl_c_value = f.allocLocalValue();
122 gop.value_ptr.* = decl_c_value;
123 try writer.writeAll("static ");
124 try f.object.dg.renderTypeAndName(writer, ty, decl_c_value, .Const);
125 try writer.writeAll(" = ");
126 try f.object.dg.renderValue(writer, ty, val);
127 try writer.writeAll(";\n ");
128 return decl_c_value;
129 },
130 else => {
131 const result = CValue{ .constant = inst };
132 gop.value_ptr.* = result;
133 return result;
134 },
115135 }
116 const index = Air.refToIndex(inst).?;
117 return f.value_map.get(index).?; // Assertion means instruction does not dominate usage.
118136 }
119137
120138 fn allocLocalValue(f: *Function) CValue {
......@@ -154,6 +172,8 @@ pub const Function = struct {
154172pub const Object = struct {
155173 dg: DeclGen,
156174 code: std.ArrayList(u8),
175 /// Goes before code. Initialized and deinitialized in `genFunc`.
176 code_header: std.ArrayList(u8) = undefined,
157177 indent_writer: IndentWriter(std.ArrayList(u8).Writer),
158178
159179 fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
......@@ -218,12 +238,18 @@ pub const DeclGen = struct {
218238 // Determine if we must pointer cast.
219239 if (ty.eql(decl.ty)) {
220240 try writer.writeByte('&');
221 } else {
222 try writer.writeAll("(");
223 try dg.renderType(writer, ty);
224 try writer.writeAll(")&");
241 try dg.renderDeclName(decl, writer);
242 return;
225243 }
244
245 try writer.writeAll("((");
246 try dg.renderType(writer, ty);
247 try writer.writeAll(")&");
248 try dg.renderDeclName(decl, writer);
249 try writer.writeByte(')');
250 return;
226251 }
252
227253 try dg.renderDeclName(decl, writer);
228254 }
229255
......@@ -1010,6 +1036,10 @@ pub fn genFunc(f: *Function) !void {
10101036 defer tracy.end();
10111037
10121038 const o = &f.object;
1039
1040 o.code_header = std.ArrayList(u8).init(f.object.dg.gpa);
1041 defer o.code_header.deinit();
1042
10131043 const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl);
10141044 const fwd_decl_writer = o.dg.fwd_decl.writer();
10151045 if (is_global) {
......@@ -1020,12 +1050,26 @@ pub fn genFunc(f: *Function) !void {
10201050
10211051 try o.indent_writer.insertNewline();
10221052 try o.dg.renderFunctionSignature(o.writer(), is_global);
1023
10241053 try o.writer().writeByte(' ');
1054
1055 // In case we need to use the header, populate it with a copy of the function
1056 // signature here. We anticipate a brace, newline, and space.
1057 try o.code_header.ensureUnusedCapacity(o.code.items.len + 3);
1058 o.code_header.appendSliceAssumeCapacity(o.code.items);
1059 o.code_header.appendSliceAssumeCapacity("{\n ");
1060 const empty_header_len = o.code_header.items.len;
1061
10251062 const main_body = f.air.getMainBody();
10261063 try genBody(f, main_body);
10271064
10281065 try o.indent_writer.insertNewline();
1066
1067 // If we have a header to insert, append the body to the header
1068 // and then return the result, freeing the body.
1069 if (o.code_header.items.len > empty_header_len) {
1070 try o.code_header.appendSlice(o.code.items[empty_header_len..]);
1071 mem.swap(std.ArrayList(u8), &o.code, &o.code_header);
1072 }
10291073}
10301074
10311075pub fn genDecl(o: *Object) !void {
......@@ -1289,7 +1333,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
12891333 };
12901334 switch (result_value) {
12911335 .none => {},
1292 else => try f.value_map.putNoClobber(inst, result_value),
1336 else => try f.value_map.putNoClobber(Air.indexToRef(inst), result_value),
12931337 }
12941338 }
12951339
......@@ -2189,7 +2233,15 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {
21892233fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {
21902234 const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt;
21912235 const writer = f.object.writer();
2192 try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
2236 // TODO re-evaluate whether to emit these or not. If we naively emit
2237 // these directives, the output file will report bogus line numbers because
2238 // every newline after the #line directive adds one to the line.
2239 // We also don't print the filename yet, so the output is strictly unhelpful.
2240 // If we wanted to go this route, we would need to go all the way and not output
2241 // newlines until the next dbg_stmt occurs.
2242 // Perhaps an additional compilation option is in order?
2243 //try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
2244 try writer.print("/* file:{d}:{d} */\n", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
21932245 return CValue.none;
21942246}
21952247
src/print_zir.zig+1
......@@ -369,6 +369,7 @@ const Writer = struct {
369369 .validate_struct_init,
370370 .validate_struct_init_comptime,
371371 .validate_array_init,
372 .validate_array_init_comptime,
372373 .c_import,
373374 => try self.writePlNodeBlock(stream, inst),
374375
src/value.zig+6-1
......@@ -1817,8 +1817,13 @@ pub const Value = extern union {
18171817
18181818 .decl_ref => return val.castTag(.decl_ref).?.data.val.elemValueAdvanced(index, arena, buffer),
18191819 .decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValueAdvanced(index, arena, buffer),
1820 .elem_ptr => {
1821 const data = val.castTag(.elem_ptr).?.data;
1822 return data.array_ptr.elemValueAdvanced(index + data.index, arena, buffer);
1823 },
18201824
1821 // The child type of arrays which have only one possible value need to have only one possible value itself.
1825 // The child type of arrays which have only one possible value need
1826 // to have only one possible value itself.
18221827 .the_only_possible_value => return val,
18231828
18241829 else => unreachable,
test/behavior/array.zig+7
......@@ -114,6 +114,13 @@ test "void arrays" {
114114}
115115
116116test "nested arrays" {
117 if (builtin.zig_backend == .stage2_wasm) {
118 // TODO this is a recent stage2 test case regression due to an enhancement;
119 // now arrays are properly detected as comptime. This exercised a new code
120 // path in the wasm backend that is not yet implemented.
121 return error.SkipZigTest;
122 }
123
117124 const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };
118125 for (array_of_strings) |s, i| {
119126 if (i == 0) try expect(mem.eql(u8, s, "hello"));
test/behavior/for.zig+6
......@@ -62,6 +62,12 @@ test "ignore lval with underscore (for loop)" {
6262}
6363
6464test "basic for loop" {
65 if (@import("builtin").zig_backend == .stage2_wasm) {
66 // TODO this is a recent stage2 test case regression due to an enhancement;
67 // now arrays are properly detected as comptime. This exercised a new code
68 // path in the wasm backend that is not yet implemented.
69 return error.SkipZigTest;
70 }
6571 const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;
6672
6773 var buffer: [expected_result.len]u8 = undefined;