authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-04 23:39:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-04 23:49:49-07:00
log713d2a9b3883942491b40738245232680877cc66
tree9b28339bd354a4739344197042d26c38315147a0
parent4bc6b4925c2a09a34bf0af861c1b77f93a7df511

Sema: better code generated for struct literals

Add a variant of the `validate_struct_init` ZIR instruction: `validate_struct_init_comptime` which is the same thing except it indicates a comptime scope. Sema code for this instruction now handles default struct field values and detects when the struct initialization resulted in a comptime value, replacing the already-emitted AIR instructions to store each individual field with a single `store` instruction with a comptime struct value as the operand. In the case of a comptime scope, there is a simpler path that only evals the implicit store instructions for default field values, avoiding the mechanism for detecting comptime values. This regressed one test case for the wasm backend, but it's just hitting a different prong of `emitConstant` which currently has "TODO" in there, so I think it's fine.

5 files changed, 181 insertions(+), 12 deletions(-)

src/AstGen.zig+7-1
......@@ -1600,7 +1600,12 @@ fn structInitExprRlPtrInner(
16001600 _ = try expr(gz, scope, .{ .ptr = field_ptr }, field_init);
16011601 }
16021602
1603 _ = try gz.addPlNodePayloadIndex(.validate_struct_init, node, payload_index);
1603 const tag: Zir.Inst.Tag = if (gz.force_comptime)
1604 .validate_struct_init_comptime
1605 else
1606 .validate_struct_init;
1607
1608 _ = try gz.addPlNodePayloadIndex(tag, node, payload_index);
16041609 return Zir.Inst.Ref.void_value;
16051610}
16061611
......@@ -2310,6 +2315,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
23102315 .store_to_inferred_ptr,
23112316 .resolve_inferred_alloc,
23122317 .validate_struct_init,
2318 .validate_struct_init_comptime,
23132319 .validate_array_init,
23142320 .set_align_stack,
23152321 .set_cold,
src/Sema.zig+167-10
......@@ -805,7 +805,12 @@ pub fn analyzeBody(
805805 continue;
806806 },
807807 .validate_struct_init => {
808 try sema.zirValidateStructInit(block, inst);
808 try sema.zirValidateStructInit(block, inst, false);
809 i += 1;
810 continue;
811 },
812 .validate_struct_init_comptime => {
813 try sema.zirValidateStructInit(block, inst, true);
809814 i += 1;
810815 continue;
811816 },
......@@ -2438,7 +2443,12 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
24382443 }
24392444}
24402445
2441fn zirValidateStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
2446fn zirValidateStructInit(
2447 sema: *Sema,
2448 block: *Block,
2449 inst: Zir.Inst.Index,
2450 is_comptime: bool,
2451) CompileError!void {
24422452 const tracy = trace(@src());
24432453 defer tracy.end();
24442454
......@@ -2456,6 +2466,7 @@ fn zirValidateStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
24562466 agg_ty.castTag(.@"struct").?.data,
24572467 init_src,
24582468 instrs,
2469 is_comptime,
24592470 ),
24602471 .Union => return sema.validateUnionInit(
24612472 block,
......@@ -2529,6 +2540,7 @@ fn validateStructInit(
25292540 struct_obj: *Module.Struct,
25302541 init_src: LazySrcLoc,
25312542 instrs: []const Zir.Inst.Index,
2543 is_comptime: bool,
25322544) CompileError!void {
25332545 const gpa = sema.gpa;
25342546
......@@ -2537,10 +2549,13 @@ fn validateStructInit(
25372549 defer gpa.free(found_fields);
25382550 mem.set(Zir.Inst.Index, found_fields, 0);
25392551
2552 var struct_ptr_zir_ref: Zir.Inst.Ref = undefined;
2553
25402554 for (instrs) |field_ptr| {
25412555 const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
25422556 const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };
25432557 const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
2558 struct_ptr_zir_ref = field_ptr_extra.lhs;
25442559 const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);
25452560 const field_index = struct_obj.fields.getIndex(field_name) orelse
25462561 return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name);
......@@ -2561,19 +2576,127 @@ fn validateStructInit(
25612576
25622577 var root_msg: ?*Module.ErrorMsg = null;
25632578
2564 // TODO handle default struct field values
2579 const fields = struct_obj.fields.values();
2580 const struct_ptr = sema.resolveInst(struct_ptr_zir_ref);
2581 const struct_ty = sema.typeOf(struct_ptr).childType();
2582
2583 if (is_comptime or block.is_comptime) {
2584 // In this case the only thing we need to do is evaluate the implicit
2585 // store instructions for default field values, and report any missing fields.
2586 // Avoid the cost of the extra machinery for detecting a comptime struct init value.
2587 for (found_fields) |field_ptr, i| {
2588 if (field_ptr != 0) continue;
2589
2590 const field = fields[i];
2591 const field_name = struct_obj.fields.keys()[i];
2592
2593 if (field.default_val.tag() == .unreachable_value) {
2594 const template = "missing struct field: {s}";
2595 const args = .{field_name};
2596 if (root_msg) |msg| {
2597 try sema.errNote(block, init_src, msg, template, args);
2598 } else {
2599 root_msg = try sema.errMsg(block, init_src, template, args);
2600 }
2601 continue;
2602 }
2603
2604 const default_field_ptr = try sema.structFieldPtr(block, init_src, struct_ptr, field_name, init_src, struct_ty);
2605 const init = try sema.addConstant(field.ty, field.default_val);
2606 const field_src = init_src; // TODO better source location
2607 try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);
2608 }
2609
2610 if (root_msg) |msg| {
2611 const fqn = try struct_obj.getFullyQualifiedName(gpa);
2612 defer gpa.free(fqn);
2613 try sema.mod.errNoteNonLazy(
2614 struct_obj.srcLoc(),
2615 msg,
2616 "struct '{s}' declared here",
2617 .{fqn},
2618 );
2619 return sema.failWithOwnedErrorMsg(msg);
2620 }
2621
2622 return;
2623 }
2624
2625 var struct_is_comptime = true;
2626 var first_block_index: usize = std.math.maxInt(u32);
2627
2628 const air_tags = sema.air_instructions.items(.tag);
2629 const air_datas = sema.air_instructions.items(.data);
2630
2631 // We collect the comptime field values in case the struct initialization
2632 // ends up being comptime-known.
2633 const field_values = try sema.arena.alloc(Value, fields.len);
2634
25652635 for (found_fields) |field_ptr, i| {
2566 if (field_ptr != 0) continue;
2636 const field = fields[i];
2637
2638 if (field_ptr != 0) {
2639 const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
2640 const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };
2641
2642 // Determine whether the value stored to this pointer is comptime-known.
2643 if (try sema.typeHasOnePossibleValue(block, field_src, field.ty)) |opv| {
2644 field_values[i] = opv;
2645 continue;
2646 }
2647
2648 const field_ptr_air_ref = sema.inst_map.get(field_ptr).?;
2649 const field_ptr_air_inst = Air.refToIndex(field_ptr_air_ref).?;
2650 // Find the block index of the field_ptr so that we can look at the next
2651 // instruction after it within the same block.
2652 // Possible performance enhancement: save the `block_index` between iterations
2653 // of the for loop.
2654 const next_air_inst = inst: {
2655 var block_index = block.instructions.items.len - 1;
2656 while (block.instructions.items[block_index] != field_ptr_air_inst) {
2657 block_index -= 1;
2658 }
2659 first_block_index = @minimum(first_block_index, block_index);
2660 break :inst block.instructions.items[block_index + 1];
2661 };
2662
2663 // If the next instructon is a store with a comptime operand, this field
2664 // is comptime.
2665 switch (air_tags[next_air_inst]) {
2666 .store => {
2667 const bin_op = air_datas[next_air_inst].bin_op;
2668 if (bin_op.lhs != field_ptr_air_ref) {
2669 struct_is_comptime = false;
2670 continue;
2671 }
2672 if (try sema.resolveMaybeUndefValAllowVariables(block, field_src, bin_op.rhs)) |val| {
2673 field_values[i] = val;
2674 } else {
2675 struct_is_comptime = false;
2676 }
2677 continue;
2678 },
2679 else => {
2680 struct_is_comptime = false;
2681 continue;
2682 },
2683 }
2684 }
25672685
25682686 const field_name = struct_obj.fields.keys()[i];
2569 const template = "missing struct field: {s}";
2570 const args = .{field_name};
2571 if (root_msg) |msg| {
2572 try sema.errNote(block, init_src, msg, template, args);
2573 } else {
2574 root_msg = try sema.errMsg(block, init_src, template, args);
2687
2688 if (field.default_val.tag() == .unreachable_value) {
2689 const template = "missing struct field: {s}";
2690 const args = .{field_name};
2691 if (root_msg) |msg| {
2692 try sema.errNote(block, init_src, msg, template, args);
2693 } else {
2694 root_msg = try sema.errMsg(block, init_src, template, args);
2695 }
2696 continue;
25752697 }
25762698 }
2699
25772700 if (root_msg) |msg| {
25782701 const fqn = try struct_obj.getFullyQualifiedName(gpa);
25792702 defer gpa.free(fqn);
......@@ -2585,6 +2708,40 @@ fn validateStructInit(
25852708 );
25862709 return sema.failWithOwnedErrorMsg(msg);
25872710 }
2711
2712 if (struct_is_comptime) {
2713 // Our task is to delete all the `field_ptr` and `store` instructions, and insert
2714 // instead a single `store` to the struct_ptr with a comptime struct value.
2715
2716 block.instructions.shrinkRetainingCapacity(first_block_index);
2717
2718 // The `field_values` array has been populated for all the non-default struct
2719 // fields. Here we fill in the default field values.
2720 for (found_fields) |field_ptr, i| {
2721 if (field_ptr != 0) continue;
2722
2723 field_values[i] = fields[i].default_val;
2724 }
2725
2726 const struct_val = try Value.Tag.@"struct".create(sema.arena, field_values);
2727 const struct_init = try sema.addConstant(struct_ty, struct_val);
2728 try sema.storePtr2(block, init_src, struct_ptr, init_src, struct_init, init_src, .store);
2729 return;
2730 }
2731
2732 // Our task is to insert `store` instructions for all the default field values.
2733
2734 for (found_fields) |field_ptr, i| {
2735 if (field_ptr != 0) continue;
2736
2737 const field = fields[i];
2738 const field_name = struct_obj.fields.keys()[i];
2739 const default_field_ptr = try sema.structFieldPtr(block, init_src, struct_ptr, field_name, init_src, struct_ty);
2740
2741 const init = try sema.addConstant(field.ty, field.default_val);
2742 const field_src = init_src; // TODO better source location
2743 try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);
2744 }
25882745}
25892746
25902747fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
src/Zir.zig+5
......@@ -653,6 +653,9 @@ pub const Inst = struct {
653653 /// because it must use one of them to find out the struct type.
654654 /// Uses the `pl_node` field. Payload is `Block`.
655655 validate_struct_init,
656 /// Same as `validate_struct_init` but additionally communicates that the
657 /// resulting struct initialization value is within a comptime scope.
658 validate_struct_init_comptime,
656659 /// Given a set of `elem_ptr_imm` instructions, assumes they are all part of an
657660 /// array initialization expression, and emits a compile error if the number of
658661 /// elements does not match the array type.
......@@ -1082,6 +1085,7 @@ pub const Inst = struct {
10821085 .switch_cond,
10831086 .switch_cond_ref,
10841087 .validate_struct_init,
1088 .validate_struct_init_comptime,
10851089 .validate_array_init,
10861090 .struct_init_empty,
10871091 .struct_init,
......@@ -1335,6 +1339,7 @@ pub const Inst = struct {
13351339 .switch_capture_else = .switch_capture,
13361340 .switch_capture_else_ref = .switch_capture,
13371341 .validate_struct_init = .pl_node,
1342 .validate_struct_init_comptime = .pl_node,
13381343 .validate_array_init = .pl_node,
13391344 .struct_init_empty = .un_node,
13401345 .field_type = .pl_node,
src/print_zir.zig+1
......@@ -367,6 +367,7 @@ const Writer = struct {
367367 .suspend_block,
368368 .loop,
369369 .validate_struct_init,
370 .validate_struct_init_comptime,
370371 .validate_array_init,
371372 .c_import,
372373 => try self.writePlNodeBlock(stream, inst),
test/behavior.zig+1-1
......@@ -10,7 +10,6 @@ test {
1010 if (builtin.zig_backend != .stage2_x86_64) {
1111 // Tests that pass for stage1, llvm backend, C backend, wasm backend, and arm backend.
1212 _ = @import("behavior/bugs/679.zig");
13 _ = @import("behavior/bugs/4560.zig");
1413 _ = @import("behavior/bugs/6850.zig");
1514 _ = @import("behavior/fn_in_struct_in_comptime.zig");
1615 _ = @import("behavior/hasfield.zig");
......@@ -61,6 +60,7 @@ test {
6160 // Tests that pass for stage1, llvm backend, C backend
6261 _ = @import("behavior/align.zig");
6362 _ = @import("behavior/array.zig");
63 _ = @import("behavior/bugs/4560.zig");
6464 _ = @import("behavior/cast.zig");
6565 _ = @import("behavior/for.zig");
6666 _ = @import("behavior/int128.zig");