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(...@@ -1418,7 +1418,13 @@ fn arrayInitExprRlPtrInner(
1418 extra_index += 1;1418 extra_index += 1;
1419 _ = try expr(gz, scope, .{ .ptr = elem_ptr }, elem_init);1419 _ = try expr(gz, scope, .{ .ptr = elem_ptr }, elem_init);
1420 }1420 }
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);
1422 return .void_value;1428 return .void_value;
1423}1429}
14241430
...@@ -2317,6 +2323,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner...@@ -2317,6 +2323,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
2317 .validate_struct_init,2323 .validate_struct_init,
2318 .validate_struct_init_comptime,2324 .validate_struct_init_comptime,
2319 .validate_array_init,2325 .validate_array_init,
2326 .validate_array_init_comptime,
2320 .set_align_stack,2327 .set_align_stack,
2321 .set_cold,2328 .set_cold,
2322 .set_float_mode,2329 .set_float_mode,
src/Sema.zig+90-4
...@@ -836,7 +836,12 @@ pub fn analyzeBody(...@@ -836,7 +836,12 @@ pub fn analyzeBody(
836 continue;836 continue;
837 },837 },
838 .validate_array_init => {838 .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);
840 i += 1;845 i += 1;
841 continue;846 continue;
842 },847 },
...@@ -2815,13 +2820,18 @@ fn validateStructInit(...@@ -2815,13 +2820,18 @@ fn validateStructInit(
2815 }2820 }
2816}2821}
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 {
2819 const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;2829 const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;
2820 const init_src = validate_inst.src();2830 const init_src = validate_inst.src();
2821 const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);2831 const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
2822 const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len];2832 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;2833 const first_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;2834 const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, first_elem_ptr_data.payload_index).data;
2825 const array_ptr = sema.resolveInst(elem_ptr_extra.ptr);2835 const array_ptr = sema.resolveInst(elem_ptr_extra.ptr);
2826 const array_ty = sema.typeOf(array_ptr).childType();2836 const array_ty = sema.typeOf(array_ptr).childType();
2827 const array_len = array_ty.arrayLen();2837 const array_len = array_ty.arrayLen();
...@@ -2831,6 +2841,82 @@ fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil...@@ -2831,6 +2841,82 @@ fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
2831 array_len, instrs.len,2841 array_len, instrs.len,
2832 });2842 });
2833 }2843 }
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 }
2834}2920}
28352921
2836fn failWithBadMemberAccess(2922fn failWithBadMemberAccess(
src/Zir.zig+5
...@@ -663,6 +663,9 @@ pub const Inst = struct {...@@ -663,6 +663,9 @@ pub const Inst = struct {
663 /// because it must use one of them to find out the array type.663 /// because it must use one of them to find out the array type.
664 /// Uses the `pl_node` field. Payload is `Block`.664 /// Uses the `pl_node` field. Payload is `Block`.
665 validate_array_init,665 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,
666 /// A struct literal with a specified type, with no fields.669 /// A struct literal with a specified type, with no fields.
667 /// Uses the `un_node` field.670 /// Uses the `un_node` field.
668 struct_init_empty,671 struct_init_empty,
...@@ -1087,6 +1090,7 @@ pub const Inst = struct {...@@ -1087,6 +1090,7 @@ pub const Inst = struct {
1087 .validate_struct_init,1090 .validate_struct_init,
1088 .validate_struct_init_comptime,1091 .validate_struct_init_comptime,
1089 .validate_array_init,1092 .validate_array_init,
1093 .validate_array_init_comptime,
1090 .struct_init_empty,1094 .struct_init_empty,
1091 .struct_init,1095 .struct_init,
1092 .struct_init_ref,1096 .struct_init_ref,
...@@ -1341,6 +1345,7 @@ pub const Inst = struct {...@@ -1341,6 +1345,7 @@ pub const Inst = struct {
1341 .validate_struct_init = .pl_node,1345 .validate_struct_init = .pl_node,
1342 .validate_struct_init_comptime = .pl_node,1346 .validate_struct_init_comptime = .pl_node,
1343 .validate_array_init = .pl_node,1347 .validate_array_init = .pl_node,
1348 .validate_array_init_comptime = .pl_node,
1344 .struct_init_empty = .un_node,1349 .struct_init_empty = .un_node,
1345 .field_type = .pl_node,1350 .field_type = .pl_node,
1346 .field_type_ref = .pl_node,1351 .field_type_ref = .pl_node,
src/codegen/c.zig+64-12
...@@ -44,7 +44,7 @@ const BlockData = struct {...@@ -44,7 +44,7 @@ const BlockData = struct {
44 result: CValue,44 result: CValue,
45};45};
4646
47pub const CValueMap = std.AutoHashMap(Air.Inst.Index, CValue);47pub const CValueMap = std.AutoHashMap(Air.Inst.Ref, CValue);
48pub const TypedefMap = std.ArrayHashMap(48pub const TypedefMap = std.ArrayHashMap(
49 Type,49 Type,
50 struct { name: []const u8, rendered: []u8 },50 struct { name: []const u8, rendered: []u8 },
...@@ -110,11 +110,29 @@ pub const Function = struct {...@@ -110,11 +110,29 @@ pub const Function = struct {
110 func: *Module.Fn,110 func: *Module.Fn,
111111
112 fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue {112 fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue {
113 if (f.air.value(inst)) |_| {113 const gop = try f.value_map.getOrPut(inst);
114 return CValue{ .constant = 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 },
115 }135 }
116 const index = Air.refToIndex(inst).?;
117 return f.value_map.get(index).?; // Assertion means instruction does not dominate usage.
118 }136 }
119137
120 fn allocLocalValue(f: *Function) CValue {138 fn allocLocalValue(f: *Function) CValue {
...@@ -154,6 +172,8 @@ pub const Function = struct {...@@ -154,6 +172,8 @@ pub const Function = struct {
154pub const Object = struct {172pub const Object = struct {
155 dg: DeclGen,173 dg: DeclGen,
156 code: std.ArrayList(u8),174 code: std.ArrayList(u8),
175 /// Goes before code. Initialized and deinitialized in `genFunc`.
176 code_header: std.ArrayList(u8) = undefined,
157 indent_writer: IndentWriter(std.ArrayList(u8).Writer),177 indent_writer: IndentWriter(std.ArrayList(u8).Writer),
158178
159 fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {179 fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
...@@ -218,12 +238,18 @@ pub const DeclGen = struct {...@@ -218,12 +238,18 @@ pub const DeclGen = struct {
218 // Determine if we must pointer cast.238 // Determine if we must pointer cast.
219 if (ty.eql(decl.ty)) {239 if (ty.eql(decl.ty)) {
220 try writer.writeByte('&');240 try writer.writeByte('&');
221 } else {241 try dg.renderDeclName(decl, writer);
222 try writer.writeAll("(");242 return;
223 try dg.renderType(writer, ty);
224 try writer.writeAll(")&");
225 }243 }
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;
226 }251 }
252
227 try dg.renderDeclName(decl, writer);253 try dg.renderDeclName(decl, writer);
228 }254 }
229255
...@@ -1010,6 +1036,10 @@ pub fn genFunc(f: *Function) !void {...@@ -1010,6 +1036,10 @@ pub fn genFunc(f: *Function) !void {
1010 defer tracy.end();1036 defer tracy.end();
10111037
1012 const o = &f.object;1038 const o = &f.object;
1039
1040 o.code_header = std.ArrayList(u8).init(f.object.dg.gpa);
1041 defer o.code_header.deinit();
1042
1013 const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl);1043 const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl);
1014 const fwd_decl_writer = o.dg.fwd_decl.writer();1044 const fwd_decl_writer = o.dg.fwd_decl.writer();
1015 if (is_global) {1045 if (is_global) {
...@@ -1020,12 +1050,26 @@ pub fn genFunc(f: *Function) !void {...@@ -1020,12 +1050,26 @@ pub fn genFunc(f: *Function) !void {
10201050
1021 try o.indent_writer.insertNewline();1051 try o.indent_writer.insertNewline();
1022 try o.dg.renderFunctionSignature(o.writer(), is_global);1052 try o.dg.renderFunctionSignature(o.writer(), is_global);
1023
1024 try o.writer().writeByte(' ');1053 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
1025 const main_body = f.air.getMainBody();1062 const main_body = f.air.getMainBody();
1026 try genBody(f, main_body);1063 try genBody(f, main_body);
10271064
1028 try o.indent_writer.insertNewline();1065 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 }
1029}1073}
10301074
1031pub fn genDecl(o: *Object) !void {1075pub fn genDecl(o: *Object) !void {
...@@ -1289,7 +1333,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO...@@ -1289,7 +1333,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
1289 };1333 };
1290 switch (result_value) {1334 switch (result_value) {
1291 .none => {},1335 .none => {},
1292 else => try f.value_map.putNoClobber(inst, result_value),1336 else => try f.value_map.putNoClobber(Air.indexToRef(inst), result_value),
1293 }1337 }
1294 }1338 }
12951339
...@@ -2189,7 +2233,15 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -2189,7 +2233,15 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {
2189fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {2233fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {
2190 const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt;2234 const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt;
2191 const writer = f.object.writer();2235 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 });
2193 return CValue.none;2245 return CValue.none;
2194}2246}
21952247
src/print_zir.zig+1
...@@ -369,6 +369,7 @@ const Writer = struct {...@@ -369,6 +369,7 @@ const Writer = struct {
369 .validate_struct_init,369 .validate_struct_init,
370 .validate_struct_init_comptime,370 .validate_struct_init_comptime,
371 .validate_array_init,371 .validate_array_init,
372 .validate_array_init_comptime,
372 .c_import,373 .c_import,
373 => try self.writePlNodeBlock(stream, inst),374 => try self.writePlNodeBlock(stream, inst),
374375
src/value.zig+6-1
...@@ -1817,8 +1817,13 @@ pub const Value = extern union {...@@ -1817,8 +1817,13 @@ pub const Value = extern union {
18171817
1818 .decl_ref => return val.castTag(.decl_ref).?.data.val.elemValueAdvanced(index, arena, buffer),1818 .decl_ref => return val.castTag(.decl_ref).?.data.val.elemValueAdvanced(index, arena, buffer),
1819 .decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValueAdvanced(index, arena, buffer),1819 .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.
1822 .the_only_possible_value => return val,1827 .the_only_possible_value => return val,
18231828
1824 else => unreachable,1829 else => unreachable,
test/behavior/array.zig+7
...@@ -114,6 +114,13 @@ test "void arrays" {...@@ -114,6 +114,13 @@ test "void arrays" {
114}114}
115115
116test "nested arrays" {116test "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
117 const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };124 const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };
118 for (array_of_strings) |s, i| {125 for (array_of_strings) |s, i| {
119 if (i == 0) try expect(mem.eql(u8, s, "hello"));126 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)" {...@@ -62,6 +62,12 @@ test "ignore lval with underscore (for loop)" {
62}62}
6363
64test "basic for loop" {64test "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 }
65 const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;71 const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;
6672
67 var buffer: [expected_result.len]u8 = undefined;73 var buffer: [expected_result.len]u8 = undefined;