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(...@@ -1600,7 +1600,12 @@ fn structInitExprRlPtrInner(
1600 _ = try expr(gz, scope, .{ .ptr = field_ptr }, field_init);1600 _ = try expr(gz, scope, .{ .ptr = field_ptr }, field_init);
1601 }1601 }
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);
1604 return Zir.Inst.Ref.void_value;1609 return Zir.Inst.Ref.void_value;
1605}1610}
16061611
...@@ -2310,6 +2315,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner...@@ -2310,6 +2315,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
2310 .store_to_inferred_ptr,2315 .store_to_inferred_ptr,
2311 .resolve_inferred_alloc,2316 .resolve_inferred_alloc,
2312 .validate_struct_init,2317 .validate_struct_init,
2318 .validate_struct_init_comptime,
2313 .validate_array_init,2319 .validate_array_init,
2314 .set_align_stack,2320 .set_align_stack,
2315 .set_cold,2321 .set_cold,
src/Sema.zig+167-10
...@@ -805,7 +805,12 @@ pub fn analyzeBody(...@@ -805,7 +805,12 @@ pub fn analyzeBody(
805 continue;805 continue;
806 },806 },
807 .validate_struct_init => {807 .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);
809 i += 1;814 i += 1;
810 continue;815 continue;
811 },816 },
...@@ -2438,7 +2443,12 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com...@@ -2438,7 +2443,12 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
2438 }2443 }
2439}2444}
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 {
2442 const tracy = trace(@src());2452 const tracy = trace(@src());
2443 defer tracy.end();2453 defer tracy.end();
24442454
...@@ -2456,6 +2466,7 @@ fn zirValidateStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi...@@ -2456,6 +2466,7 @@ fn zirValidateStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
2456 agg_ty.castTag(.@"struct").?.data,2466 agg_ty.castTag(.@"struct").?.data,
2457 init_src,2467 init_src,
2458 instrs,2468 instrs,
2469 is_comptime,
2459 ),2470 ),
2460 .Union => return sema.validateUnionInit(2471 .Union => return sema.validateUnionInit(
2461 block,2472 block,
...@@ -2529,6 +2540,7 @@ fn validateStructInit(...@@ -2529,6 +2540,7 @@ fn validateStructInit(
2529 struct_obj: *Module.Struct,2540 struct_obj: *Module.Struct,
2530 init_src: LazySrcLoc,2541 init_src: LazySrcLoc,
2531 instrs: []const Zir.Inst.Index,2542 instrs: []const Zir.Inst.Index,
2543 is_comptime: bool,
2532) CompileError!void {2544) CompileError!void {
2533 const gpa = sema.gpa;2545 const gpa = sema.gpa;
25342546
...@@ -2537,10 +2549,13 @@ fn validateStructInit(...@@ -2537,10 +2549,13 @@ fn validateStructInit(
2537 defer gpa.free(found_fields);2549 defer gpa.free(found_fields);
2538 mem.set(Zir.Inst.Index, found_fields, 0);2550 mem.set(Zir.Inst.Index, found_fields, 0);
25392551
2552 var struct_ptr_zir_ref: Zir.Inst.Ref = undefined;
2553
2540 for (instrs) |field_ptr| {2554 for (instrs) |field_ptr| {
2541 const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;2555 const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
2542 const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };2556 const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };
2543 const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;2557 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;
2544 const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);2559 const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);
2545 const field_index = struct_obj.fields.getIndex(field_name) orelse2560 const field_index = struct_obj.fields.getIndex(field_name) orelse
2546 return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name);2561 return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name);
...@@ -2561,19 +2576,127 @@ fn validateStructInit(...@@ -2561,19 +2576,127 @@ fn validateStructInit(
25612576
2562 var root_msg: ?*Module.ErrorMsg = null;2577 var root_msg: ?*Module.ErrorMsg = null;
25632578
2564 // TODO handle default struct field values2579 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
2565 for (found_fields) |field_ptr, i| {2635 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
2568 const field_name = struct_obj.fields.keys()[i];2686 const field_name = struct_obj.fields.keys()[i];
2569 const template = "missing struct field: {s}";2687
2570 const args = .{field_name};2688 if (field.default_val.tag() == .unreachable_value) {
2571 if (root_msg) |msg| {2689 const template = "missing struct field: {s}";
2572 try sema.errNote(block, init_src, msg, template, args);2690 const args = .{field_name};
2573 } else {2691 if (root_msg) |msg| {
2574 root_msg = try sema.errMsg(block, init_src, template, args);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;
2575 }2697 }
2576 }2698 }
2699
2577 if (root_msg) |msg| {2700 if (root_msg) |msg| {
2578 const fqn = try struct_obj.getFullyQualifiedName(gpa);2701 const fqn = try struct_obj.getFullyQualifiedName(gpa);
2579 defer gpa.free(fqn);2702 defer gpa.free(fqn);
...@@ -2585,6 +2708,40 @@ fn validateStructInit(...@@ -2585,6 +2708,40 @@ fn validateStructInit(
2585 );2708 );
2586 return sema.failWithOwnedErrorMsg(msg);2709 return sema.failWithOwnedErrorMsg(msg);
2587 }2710 }
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 }
2588}2745}
25892746
2590fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {2747fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
src/Zir.zig+5
...@@ -653,6 +653,9 @@ pub const Inst = struct {...@@ -653,6 +653,9 @@ pub const Inst = struct {
653 /// because it must use one of them to find out the struct type.653 /// because it must use one of them to find out the struct type.
654 /// Uses the `pl_node` field. Payload is `Block`.654 /// Uses the `pl_node` field. Payload is `Block`.
655 validate_struct_init,655 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,
656 /// Given a set of `elem_ptr_imm` instructions, assumes they are all part of an659 /// Given a set of `elem_ptr_imm` instructions, assumes they are all part of an
657 /// array initialization expression, and emits a compile error if the number of660 /// array initialization expression, and emits a compile error if the number of
658 /// elements does not match the array type.661 /// elements does not match the array type.
...@@ -1082,6 +1085,7 @@ pub const Inst = struct {...@@ -1082,6 +1085,7 @@ pub const Inst = struct {
1082 .switch_cond,1085 .switch_cond,
1083 .switch_cond_ref,1086 .switch_cond_ref,
1084 .validate_struct_init,1087 .validate_struct_init,
1088 .validate_struct_init_comptime,
1085 .validate_array_init,1089 .validate_array_init,
1086 .struct_init_empty,1090 .struct_init_empty,
1087 .struct_init,1091 .struct_init,
...@@ -1335,6 +1339,7 @@ pub const Inst = struct {...@@ -1335,6 +1339,7 @@ pub const Inst = struct {
1335 .switch_capture_else = .switch_capture,1339 .switch_capture_else = .switch_capture,
1336 .switch_capture_else_ref = .switch_capture,1340 .switch_capture_else_ref = .switch_capture,
1337 .validate_struct_init = .pl_node,1341 .validate_struct_init = .pl_node,
1342 .validate_struct_init_comptime = .pl_node,
1338 .validate_array_init = .pl_node,1343 .validate_array_init = .pl_node,
1339 .struct_init_empty = .un_node,1344 .struct_init_empty = .un_node,
1340 .field_type = .pl_node,1345 .field_type = .pl_node,
src/print_zir.zig+1
...@@ -367,6 +367,7 @@ const Writer = struct {...@@ -367,6 +367,7 @@ const Writer = struct {
367 .suspend_block,367 .suspend_block,
368 .loop,368 .loop,
369 .validate_struct_init,369 .validate_struct_init,
370 .validate_struct_init_comptime,
370 .validate_array_init,371 .validate_array_init,
371 .c_import,372 .c_import,
372 => try self.writePlNodeBlock(stream, inst),373 => try self.writePlNodeBlock(stream, inst),
test/behavior.zig+1-1
...@@ -10,7 +10,6 @@ test {...@@ -10,7 +10,6 @@ test {
10 if (builtin.zig_backend != .stage2_x86_64) {10 if (builtin.zig_backend != .stage2_x86_64) {
11 // Tests that pass for stage1, llvm backend, C backend, wasm backend, and arm backend.11 // Tests that pass for stage1, llvm backend, C backend, wasm backend, and arm backend.
12 _ = @import("behavior/bugs/679.zig");12 _ = @import("behavior/bugs/679.zig");
13 _ = @import("behavior/bugs/4560.zig");
14 _ = @import("behavior/bugs/6850.zig");13 _ = @import("behavior/bugs/6850.zig");
15 _ = @import("behavior/fn_in_struct_in_comptime.zig");14 _ = @import("behavior/fn_in_struct_in_comptime.zig");
16 _ = @import("behavior/hasfield.zig");15 _ = @import("behavior/hasfield.zig");
...@@ -61,6 +60,7 @@ test {...@@ -61,6 +60,7 @@ test {
61 // Tests that pass for stage1, llvm backend, C backend60 // Tests that pass for stage1, llvm backend, C backend
62 _ = @import("behavior/align.zig");61 _ = @import("behavior/align.zig");
63 _ = @import("behavior/array.zig");62 _ = @import("behavior/array.zig");
63 _ = @import("behavior/bugs/4560.zig");
64 _ = @import("behavior/cast.zig");64 _ = @import("behavior/cast.zig");
65 _ = @import("behavior/for.zig");65 _ = @import("behavior/for.zig");
66 _ = @import("behavior/int128.zig");66 _ = @import("behavior/int128.zig");