authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-22 18:57:46-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-22 18:57:46-07:00
log568f333681e6ecf8c60c5bbe04ea1e494d966d48
treee4116f9c74a4caf5ebd9f895b81155392968097e
parent2f391df2a7ea7cc6e7500da214100fb49ea8f661

astgen: improve the ensure_unused_result elision


4 files changed, 212 insertions(+), 170 deletions(-)

src/Module.zig-157
...@@ -1398,163 +1398,6 @@ pub const WipZirCode = struct {...@@ -1398,163 +1398,6 @@ pub const WipZirCode = struct {
1398 return result;1398 return result;
1399 }1399 }
14001400
1401 /// Returns `true` if and only if the instruction *always* has a void type, or
1402 /// *always* has a NoReturn type. Function calls return false because
1403 /// the answer depends on their type.
1404 /// This is used to elide unnecessary `ensure_result_used` instructions.
1405 pub fn isVoidOrNoReturn(wzc: WipZirCode, inst_ref: zir.Inst.Ref) bool {
1406 if (inst_ref >= wzc.ref_start_index) {
1407 const inst = inst_ref - wzc.ref_start_index;
1408 const tags = wzc.instructions.items(.tag);
1409 switch (tags[inst]) {
1410 .@"const" => {
1411 const tv = wzc.instructions.items(.data)[inst].@"const";
1412 return switch (tv.ty.zigTypeTag()) {
1413 .NoReturn, .Void => true,
1414 else => false,
1415 };
1416 },
1417
1418 .add,
1419 .addwrap,
1420 .alloc,
1421 .alloc_mut,
1422 .alloc_inferred,
1423 .alloc_inferred_mut,
1424 .array_cat,
1425 .array_mul,
1426 .array_type,
1427 .array_type_sentinel,
1428 .indexable_ptr_len,
1429 .as,
1430 .as_node,
1431 .@"asm",
1432 .asm_volatile,
1433 .bit_and,
1434 .bitcast,
1435 .bitcast_ref,
1436 .bitcast_result_ptr,
1437 .bit_or,
1438 .block,
1439 .block_comptime,
1440 .bool_br_and,
1441 .bool_br_or,
1442 .bool_not,
1443 .bool_and,
1444 .bool_or,
1445 .call,
1446 .call_compile_time,
1447 .call_none,
1448 .cmp_lt,
1449 .cmp_lte,
1450 .cmp_eq,
1451 .cmp_gte,
1452 .cmp_gt,
1453 .cmp_neq,
1454 .coerce_result_ptr,
1455 .decl_ref,
1456 .decl_val,
1457 .deref_node,
1458 .div,
1459 .elem_ptr,
1460 .elem_val,
1461 .elem_ptr_node,
1462 .elem_val_node,
1463 .floatcast,
1464 .field_ptr,
1465 .field_val,
1466 .field_ptr_named,
1467 .field_val_named,
1468 .fn_type,
1469 .fn_type_var_args,
1470 .fn_type_cc,
1471 .fn_type_cc_var_args,
1472 .int,
1473 .intcast,
1474 .int_type,
1475 .is_non_null,
1476 .is_null,
1477 .is_non_null_ptr,
1478 .is_null_ptr,
1479 .is_err,
1480 .is_err_ptr,
1481 .mod_rem,
1482 .mul,
1483 .mulwrap,
1484 .param_type,
1485 .ptrtoint,
1486 .ref,
1487 .ret_ptr,
1488 .ret_type,
1489 .shl,
1490 .shr,
1491 .str,
1492 .sub,
1493 .subwrap,
1494 .negate,
1495 .negate_wrap,
1496 .typeof,
1497 .xor,
1498 .optional_type,
1499 .optional_type_from_ptr_elem,
1500 .optional_payload_safe,
1501 .optional_payload_unsafe,
1502 .optional_payload_safe_ptr,
1503 .optional_payload_unsafe_ptr,
1504 .err_union_payload_safe,
1505 .err_union_payload_unsafe,
1506 .err_union_payload_safe_ptr,
1507 .err_union_payload_unsafe_ptr,
1508 .err_union_code,
1509 .err_union_code_ptr,
1510 .ptr_type,
1511 .ptr_type_simple,
1512 .enum_literal,
1513 .enum_literal_small,
1514 .merge_error_sets,
1515 .error_union_type,
1516 .bit_not,
1517 .error_set,
1518 .error_value,
1519 .slice_start,
1520 .slice_end,
1521 .slice_sentinel,
1522 .import,
1523 .typeof_peer,
1524 => return false,
1525
1526 .breakpoint,
1527 .dbg_stmt_node,
1528 .ensure_result_used,
1529 .ensure_result_non_error,
1530 .set_eval_branch_quota,
1531 .compile_log,
1532 .ensure_err_payload_void,
1533 .@"break",
1534 .break_void_tok,
1535 .break_flat,
1536 .condbr,
1537 .compile_error,
1538 .ret_node,
1539 .ret_tok,
1540 .ret_coerce,
1541 .@"unreachable",
1542 .loop,
1543 .elided,
1544 .store,
1545 .store_to_block_ptr,
1546 .store_to_inferred_ptr,
1547 .resolve_inferred_alloc,
1548 => return true,
1549 }
1550 }
1551 return switch (inst_ref) {
1552 @enumToInt(zir.Const.unused) => unreachable,
1553 @enumToInt(zir.Const.void_value), @enumToInt(zir.Const.unreachable_value) => true,
1554 else => false,
1555 };
1556 }
1557
1558 pub fn deinit(wzc: *WipZirCode) void {1401 pub fn deinit(wzc: *WipZirCode) void {
1559 wzc.instructions.deinit(wzc.gpa);1402 wzc.instructions.deinit(wzc.gpa);
1560 wzc.extra.deinit(wzc.gpa);1403 wzc.extra.deinit(wzc.gpa);
src/Sema.zig+32-10
...@@ -126,9 +126,11 @@ pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Inde...@@ -126,9 +126,11 @@ pub fn analyzeBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Inde
126 .bool_or => try sema.zirBoolOp(block, inst, true),126 .bool_or => try sema.zirBoolOp(block, inst, true),
127 .bool_br_and => try sema.zirBoolBr(block, inst, false),127 .bool_br_and => try sema.zirBoolBr(block, inst, false),
128 .bool_br_or => try sema.zirBoolBr(block, inst, true),128 .bool_br_or => try sema.zirBoolBr(block, inst, true),
129 .call => try sema.zirCall(block, inst, .auto),129 .call => try sema.zirCall(block, inst, .auto, false),
130 .call_compile_time => try sema.zirCall(block, inst, .compile_time),130 .call_chkused => try sema.zirCall(block, inst, .auto, true),
131 .call_none => try sema.zirCallNone(block, inst),131 .call_compile_time => try sema.zirCall(block, inst, .compile_time, false),
132 .call_none => try sema.zirCallNone(block, inst, false),
133 .call_none_chkused => try sema.zirCallNone(block, inst, true),
132 .cmp_eq => try sema.zirCmp(block, inst, .eq),134 .cmp_eq => try sema.zirCmp(block, inst, .eq),
133 .cmp_gt => try sema.zirCmp(block, inst, .gt),135 .cmp_gt => try sema.zirCmp(block, inst, .gt),
134 .cmp_gte => try sema.zirCmp(block, inst, .gte),136 .cmp_gte => try sema.zirCmp(block, inst, .gte),
...@@ -457,6 +459,16 @@ fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) I...@@ -457,6 +459,16 @@ fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) I
457 const inst_data = sema.code.instructions.items(.data)[inst].un_node;459 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
458 const operand = try sema.resolveInst(inst_data.operand);460 const operand = try sema.resolveInst(inst_data.operand);
459 const src = inst_data.src();461 const src = inst_data.src();
462
463 return sema.ensureResultUsed(block, operand, src);
464}
465
466fn ensureResultUsed(
467 sema: *Sema,
468 block: *Scope.Block,
469 operand: *Inst,
470 src: LazySrcLoc,
471) InnerError!void {
460 switch (operand.ty.zigTypeTag()) {472 switch (operand.ty.zigTypeTag()) {
461 .Void, .NoReturn => return,473 .Void, .NoReturn => return,
462 else => return sema.mod.fail(&block.base, src, "expression value is ignored", .{}),474 else => return sema.mod.fail(&block.base, src, "expression value is ignored", .{}),
...@@ -1027,14 +1039,19 @@ fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError...@@ -1027,14 +1039,19 @@ fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError
1027 return sema.analyzeDeclVal(block, .unneeded, decl);1039 return sema.analyzeDeclVal(block, .unneeded, decl);
1028}1040}
10291041
1030fn zirCallNone(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {1042fn zirCallNone(
1043 sema: *Sema,
1044 block: *Scope.Block,
1045 inst: zir.Inst.Index,
1046 ensure_result_used: bool,
1047) InnerError!*Inst {
1031 const tracy = trace(@src());1048 const tracy = trace(@src());
1032 defer tracy.end();1049 defer tracy.end();
10331050
1034 const inst_data = sema.code.instructions.items(.data)[inst].un_node;1051 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
1035 const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };1052 const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
10361053
1037 return sema.analyzeCall(block, inst_data.operand, func_src, inst_data.src(), .auto, &.{});1054 return sema.analyzeCall(block, inst_data.operand, func_src, inst_data.src(), .auto, ensure_result_used, &.{});
1038}1055}
10391056
1040fn zirCall(1057fn zirCall(
...@@ -1042,6 +1059,7 @@ fn zirCall(...@@ -1042,6 +1059,7 @@ fn zirCall(
1042 block: *Scope.Block,1059 block: *Scope.Block,
1043 inst: zir.Inst.Index,1060 inst: zir.Inst.Index,
1044 modifier: std.builtin.CallOptions.Modifier,1061 modifier: std.builtin.CallOptions.Modifier,
1062 ensure_result_used: bool,
1045) InnerError!*Inst {1063) InnerError!*Inst {
1046 const tracy = trace(@src());1064 const tracy = trace(@src());
1047 defer tracy.end();1065 defer tracy.end();
...@@ -1052,7 +1070,7 @@ fn zirCall(...@@ -1052,7 +1070,7 @@ fn zirCall(
1052 const extra = sema.code.extraData(zir.Inst.Call, inst_data.payload_index);1070 const extra = sema.code.extraData(zir.Inst.Call, inst_data.payload_index);
1053 const args = sema.code.extra[extra.end..][0..extra.data.args_len];1071 const args = sema.code.extra[extra.end..][0..extra.data.args_len];
10541072
1055 return sema.analyzeCall(block, extra.data.callee, func_src, call_src, modifier, args);1073 return sema.analyzeCall(block, extra.data.callee, func_src, call_src, modifier, ensure_result_used, args);
1056}1074}
10571075
1058fn analyzeCall(1076fn analyzeCall(
...@@ -1062,6 +1080,7 @@ fn analyzeCall(...@@ -1062,6 +1080,7 @@ fn analyzeCall(
1062 func_src: LazySrcLoc,1080 func_src: LazySrcLoc,
1063 call_src: LazySrcLoc,1081 call_src: LazySrcLoc,
1064 modifier: std.builtin.CallOptions.Modifier,1082 modifier: std.builtin.CallOptions.Modifier,
1083 ensure_result_used: bool,
1065 zir_args: []const zir.Inst.Ref,1084 zir_args: []const zir.Inst.Ref,
1066) InnerError!*ir.Inst {1085) InnerError!*ir.Inst {
1067 const func = try sema.resolveInst(zir_func);1086 const func = try sema.resolveInst(zir_func);
...@@ -1121,7 +1140,7 @@ fn analyzeCall(...@@ -1121,7 +1140,7 @@ fn analyzeCall(
1121 const is_comptime_call = block.is_comptime or modifier == .compile_time;1140 const is_comptime_call = block.is_comptime or modifier == .compile_time;
1122 const is_inline_call = is_comptime_call or modifier == .always_inline or1141 const is_inline_call = is_comptime_call or modifier == .always_inline or
1123 func.ty.fnCallingConvention() == .Inline;1142 func.ty.fnCallingConvention() == .Inline;
1124 if (is_inline_call) {1143 const result: *Inst = if (is_inline_call) res: {
1125 const func_val = try sema.resolveConstValue(block, func_src, func);1144 const func_val = try sema.resolveConstValue(block, func_src, func);
1126 const module_fn = switch (func_val.tag()) {1145 const module_fn = switch (func_val.tag()) {
1127 .function => func_val.castTag(.function).?.data,1146 .function => func_val.castTag(.function).?.data,
...@@ -1195,10 +1214,13 @@ fn analyzeCall(...@@ -1195,10 +1214,13 @@ fn analyzeCall(
1195 // the block_inst above.1214 // the block_inst above.
1196 _ = try sema.root(&child_block);1215 _ = try sema.root(&child_block);
11971216
1198 return sema.analyzeBlockBody(block, &child_block, merges);1217 break :res try sema.analyzeBlockBody(block, &child_block, merges);
1199 }1218 } else try block.addCall(call_src, ret_type, func, casted_args);
12001219
1201 return block.addCall(call_src, ret_type, func, casted_args);1220 if (ensure_result_used) {
1221 try sema.ensureResultUsed(block, result, call_src);
1222 }
1223 return result;
1202}1224}
12031225
1204fn zirIntType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {1226fn zirIntType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
src/astgen.zig+172-3
...@@ -1024,9 +1024,178 @@ fn blockExprStmts(...@@ -1024,9 +1024,178 @@ fn blockExprStmts(
1024 .assign_mul_wrap => try assignOp(mod, scope, statement, .mulwrap),1024 .assign_mul_wrap => try assignOp(mod, scope, statement, .mulwrap),
10251025
1026 else => {1026 else => {
1027 const possibly_unused_result = try expr(mod, scope, .none, statement);1027 // We need to emit an error if the result is not `noreturn` or `void`, but
1028 if (!gz.zir_code.isVoidOrNoReturn(possibly_unused_result)) {1028 // we want to avoid adding the ZIR instruction if possible for performance.
1029 _ = try gz.addUnNode(.ensure_result_used, possibly_unused_result, statement);1029 const maybe_unused_result = try expr(mod, scope, .none, statement);
1030 const elide_check = if (maybe_unused_result >= gz.zir_code.ref_start_index) b: {
1031 const inst = maybe_unused_result - gz.zir_code.ref_start_index;
1032 // Note that this array becomes invalid after appending more items to it
1033 // in the above while loop.
1034 const zir_tags = gz.zir_code.instructions.items(.tag);
1035 switch (zir_tags[inst]) {
1036 .@"const" => {
1037 const tv = gz.zir_code.instructions.items(.data)[inst].@"const";
1038 break :b switch (tv.ty.zigTypeTag()) {
1039 .NoReturn, .Void => true,
1040 else => false,
1041 };
1042 },
1043 // For some instructions, swap in a slightly different ZIR tag
1044 // so we can avoid a separate ensure_result_used instruction.
1045 .call_none_chkused => unreachable,
1046 .call_none => {
1047 zir_tags[inst] = .call_none_chkused;
1048 break :b true;
1049 },
1050 .call_chkused => unreachable,
1051 .call => {
1052 zir_tags[inst] = .call_chkused;
1053 break :b true;
1054 },
1055
1056 // ZIR instructions that might be a type other than `noreturn` or `void`.
1057 .add,
1058 .addwrap,
1059 .alloc,
1060 .alloc_mut,
1061 .alloc_inferred,
1062 .alloc_inferred_mut,
1063 .array_cat,
1064 .array_mul,
1065 .array_type,
1066 .array_type_sentinel,
1067 .indexable_ptr_len,
1068 .as,
1069 .as_node,
1070 .@"asm",
1071 .asm_volatile,
1072 .bit_and,
1073 .bitcast,
1074 .bitcast_ref,
1075 .bitcast_result_ptr,
1076 .bit_or,
1077 .block,
1078 .block_comptime,
1079 .bool_br_and,
1080 .bool_br_or,
1081 .bool_not,
1082 .bool_and,
1083 .bool_or,
1084 .call_compile_time,
1085 .cmp_lt,
1086 .cmp_lte,
1087 .cmp_eq,
1088 .cmp_gte,
1089 .cmp_gt,
1090 .cmp_neq,
1091 .coerce_result_ptr,
1092 .decl_ref,
1093 .decl_val,
1094 .deref_node,
1095 .div,
1096 .elem_ptr,
1097 .elem_val,
1098 .elem_ptr_node,
1099 .elem_val_node,
1100 .floatcast,
1101 .field_ptr,
1102 .field_val,
1103 .field_ptr_named,
1104 .field_val_named,
1105 .fn_type,
1106 .fn_type_var_args,
1107 .fn_type_cc,
1108 .fn_type_cc_var_args,
1109 .int,
1110 .intcast,
1111 .int_type,
1112 .is_non_null,
1113 .is_null,
1114 .is_non_null_ptr,
1115 .is_null_ptr,
1116 .is_err,
1117 .is_err_ptr,
1118 .mod_rem,
1119 .mul,
1120 .mulwrap,
1121 .param_type,
1122 .ptrtoint,
1123 .ref,
1124 .ret_ptr,
1125 .ret_type,
1126 .shl,
1127 .shr,
1128 .str,
1129 .sub,
1130 .subwrap,
1131 .negate,
1132 .negate_wrap,
1133 .typeof,
1134 .xor,
1135 .optional_type,
1136 .optional_type_from_ptr_elem,
1137 .optional_payload_safe,
1138 .optional_payload_unsafe,
1139 .optional_payload_safe_ptr,
1140 .optional_payload_unsafe_ptr,
1141 .err_union_payload_safe,
1142 .err_union_payload_unsafe,
1143 .err_union_payload_safe_ptr,
1144 .err_union_payload_unsafe_ptr,
1145 .err_union_code,
1146 .err_union_code_ptr,
1147 .ptr_type,
1148 .ptr_type_simple,
1149 .enum_literal,
1150 .enum_literal_small,
1151 .merge_error_sets,
1152 .error_union_type,
1153 .bit_not,
1154 .error_set,
1155 .error_value,
1156 .slice_start,
1157 .slice_end,
1158 .slice_sentinel,
1159 .import,
1160 .typeof_peer,
1161 => break :b false,
1162
1163 // ZIR instructions that are always either `noreturn` or `void`.
1164 .breakpoint,
1165 .dbg_stmt_node,
1166 .ensure_result_used,
1167 .ensure_result_non_error,
1168 .set_eval_branch_quota,
1169 .compile_log,
1170 .ensure_err_payload_void,
1171 .@"break",
1172 .break_void_tok,
1173 .break_flat,
1174 .condbr,
1175 .compile_error,
1176 .ret_node,
1177 .ret_tok,
1178 .ret_coerce,
1179 .@"unreachable",
1180 .loop,
1181 .elided,
1182 .store,
1183 .store_to_block_ptr,
1184 .store_to_inferred_ptr,
1185 .resolve_inferred_alloc,
1186 => break :b true,
1187 }
1188 } else switch (maybe_unused_result) {
1189 @enumToInt(zir.Const.unused) => unreachable,
1190
1191 @enumToInt(zir.Const.void_value),
1192 @enumToInt(zir.Const.unreachable_value),
1193 => true,
1194
1195 else => false,
1196 };
1197 if (!elide_check) {
1198 _ = try gz.addUnNode(.ensure_result_used, maybe_unused_result, statement);
1030 }1199 }
1031 },1200 },
1032 }1201 }
src/zir.zig+8
...@@ -489,11 +489,15 @@ pub const Inst = struct {...@@ -489,11 +489,15 @@ pub const Inst = struct {
489 /// Function call with modifier `.auto`.489 /// Function call with modifier `.auto`.
490 /// Uses `pl_node`. AST node is the function call. Payload is `Call`.490 /// Uses `pl_node`. AST node is the function call. Payload is `Call`.
491 call,491 call,
492 /// Same as `call` but it also does `ensure_result_used` on the return value.
493 call_chkused,
492 /// Same as `call` but with modifier `.compile_time`.494 /// Same as `call` but with modifier `.compile_time`.
493 call_compile_time,495 call_compile_time,
494 /// Function call with modifier `.auto`, empty parameter list.496 /// Function call with modifier `.auto`, empty parameter list.
495 /// Uses the `un_node` field. Operand is callee. AST node is the function call.497 /// Uses the `un_node` field. Operand is callee. AST node is the function call.
496 call_none,498 call_none,
499 /// Same as `call_none` but it also does `ensure_result_used` on the return value.
500 call_none_chkused,
497 /// `<`501 /// `<`
498 /// Uses the `pl_node` union field. Payload is `Bin`.502 /// Uses the `pl_node` union field. Payload is `Bin`.
499 cmp_lt,503 cmp_lt,
...@@ -898,8 +902,10 @@ pub const Inst = struct {...@@ -898,8 +902,10 @@ pub const Inst = struct {
898 .bool_or,902 .bool_or,
899 .breakpoint,903 .breakpoint,
900 .call,904 .call,
905 .call_chkused,
901 .call_compile_time,906 .call_compile_time,
902 .call_none,907 .call_none,
908 .call_none_chkused,
903 .cmp_lt,909 .cmp_lt,
904 .cmp_lte,910 .cmp_lte,
905 .cmp_eq,911 .cmp_eq,
...@@ -1337,6 +1343,7 @@ const Writer = struct {...@@ -1337,6 +1343,7 @@ const Writer = struct {
1337 .negate,1343 .negate,
1338 .negate_wrap,1344 .negate_wrap,
1339 .call_none,1345 .call_none,
1346 .call_none_chkused,
1340 .compile_error,1347 .compile_error,
1341 .deref_node,1348 .deref_node,
1342 .ensure_result_used,1349 .ensure_result_used,
...@@ -1393,6 +1400,7 @@ const Writer = struct {...@@ -1393,6 +1400,7 @@ const Writer = struct {
1393 .block,1400 .block,
1394 .block_comptime,1401 .block_comptime,
1395 .call,1402 .call,
1403 .call_chkused,
1396 .call_compile_time,1404 .call_compile_time,
1397 .compile_log,1405 .compile_log,
1398 .condbr,1406 .condbr,