authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-30 17:40:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-30 17:40:30-07:00
log6e78c007dff96de98c44c52da890cdae3d6e1389
treef01868c88c8a045906a0b868243b5df094afe3c6
parent507dc1f2e7fac212e79f152e557cbec98a3c30e9

Sema: improved AIR when one operand of bool cmp is known

When doing `x == true` or `x == false` it is now lowered as either a no-op or a not, respectively, rather than a cmp instruction. This commit also extracts a zirCmpEq function out from zirCmp, reducing the amount of branching (on is_equality_cmp) in both functions.

1 files changed, 121 insertions(+), 53 deletions(-)

src/Sema.zig+121-53
...@@ -193,12 +193,12 @@ pub fn analyzeBody(...@@ -193,12 +193,12 @@ pub fn analyzeBody(
193 .call_compile_time => try sema.zirCall(block, inst, .compile_time, false),193 .call_compile_time => try sema.zirCall(block, inst, .compile_time, false),
194 .call_nosuspend => try sema.zirCall(block, inst, .no_async, false),194 .call_nosuspend => try sema.zirCall(block, inst, .no_async, false),
195 .call_async => try sema.zirCall(block, inst, .async_kw, false),195 .call_async => try sema.zirCall(block, inst, .async_kw, false),
196 .cmp_eq => try sema.zirCmp(block, inst, .eq),
197 .cmp_gt => try sema.zirCmp(block, inst, .gt),
198 .cmp_gte => try sema.zirCmp(block, inst, .gte),
199 .cmp_lt => try sema.zirCmp(block, inst, .lt),196 .cmp_lt => try sema.zirCmp(block, inst, .lt),
200 .cmp_lte => try sema.zirCmp(block, inst, .lte),197 .cmp_lte => try sema.zirCmp(block, inst, .lte),
201 .cmp_neq => try sema.zirCmp(block, inst, .neq),198 .cmp_eq => try sema.zirCmpEq(block, inst, .eq, .cmp_eq),
199 .cmp_gte => try sema.zirCmp(block, inst, .gte),
200 .cmp_gt => try sema.zirCmp(block, inst, .gt),
201 .cmp_neq => try sema.zirCmpEq(block, inst, .neq, .cmp_neq),
202 .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst),202 .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst),
203 .decl_ref => try sema.zirDeclRef(block, inst),203 .decl_ref => try sema.zirDeclRef(block, inst),
204 .decl_val => try sema.zirDeclVal(block, inst),204 .decl_val => try sema.zirDeclVal(block, inst),
...@@ -5040,17 +5040,18 @@ fn zirAsm(...@@ -5040,17 +5040,18 @@ fn zirAsm(
5040 return asm_air;5040 return asm_air;
5041}5041}
50425042
5043fn zirCmp(5043/// Only called for equality operators. See also `zirCmp`.
5044fn zirCmpEq(
5044 sema: *Sema,5045 sema: *Sema,
5045 block: *Scope.Block,5046 block: *Scope.Block,
5046 inst: Zir.Inst.Index,5047 inst: Zir.Inst.Index,
5047 op: std.math.CompareOperator,5048 op: std.math.CompareOperator,
5049 air_tag: Air.Inst.Tag,
5048) CompileError!Air.Inst.Ref {5050) CompileError!Air.Inst.Ref {
5049 const tracy = trace(@src());5051 const tracy = trace(@src());
5050 defer tracy.end();5052 defer tracy.end();
50515053
5052 const mod = sema.mod;5054 const mod = sema.mod;
5053
5054 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;5055 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
5055 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;5056 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
5056 const src: LazySrcLoc = inst_data.src();5057 const src: LazySrcLoc = inst_data.src();
...@@ -5059,73 +5060,65 @@ fn zirCmp(...@@ -5059,73 +5060,65 @@ fn zirCmp(
5059 const lhs = sema.resolveInst(extra.lhs);5060 const lhs = sema.resolveInst(extra.lhs);
5060 const rhs = sema.resolveInst(extra.rhs);5061 const rhs = sema.resolveInst(extra.rhs);
50615062
5062 const is_equality_cmp = switch (op) {
5063 .eq, .neq => true,
5064 else => false,
5065 };
5066 const lhs_ty = sema.typeOf(lhs);5063 const lhs_ty = sema.typeOf(lhs);
5067 const rhs_ty = sema.typeOf(rhs);5064 const rhs_ty = sema.typeOf(rhs);
5068 const lhs_ty_tag = lhs_ty.zigTypeTag();5065 const lhs_ty_tag = lhs_ty.zigTypeTag();
5069 const rhs_ty_tag = rhs_ty.zigTypeTag();5066 const rhs_ty_tag = rhs_ty.zigTypeTag();
5070 if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {5067 if (lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
5071 // null == null, null != null5068 // null == null, null != null
5072 if (op == .eq) {5069 if (op == .eq) {
5073 return Air.Inst.Ref.bool_true;5070 return Air.Inst.Ref.bool_true;
5074 } else {5071 } else {
5075 return Air.Inst.Ref.bool_false;5072 return Air.Inst.Ref.bool_false;
5076 }5073 }
5077 } else if (is_equality_cmp and5074 }
5078 ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or5075 if (((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
5079 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))5076 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
5080 {5077 {
5081 // comparing null with optionals5078 // comparing null with optionals
5082 const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;5079 const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
5083 return sema.analyzeIsNull(block, src, opt_operand, op == .neq);5080 return sema.analyzeIsNull(block, src, opt_operand, op == .neq);
5084 } else if (is_equality_cmp and5081 }
5085 ((lhs_ty_tag == .Null and rhs_ty.isCPtr()) or (rhs_ty_tag == .Null and lhs_ty.isCPtr())))5082 if (((lhs_ty_tag == .Null and rhs_ty.isCPtr()) or (rhs_ty_tag == .Null and lhs_ty.isCPtr()))) {
5086 {
5087 return mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});5083 return mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});
5088 } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {5084 }
5085 if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
5089 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;5086 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;
5090 return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});5087 return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
5091 } else if (is_equality_cmp and5088 }
5092 ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or5089 if (((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
5093 (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))5090 (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
5094 {5091 {
5095 return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});5092 return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
5096 } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {5093 }
5097 if (!is_equality_cmp) {5094 if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
5098 return mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});5095 const runtime_src: LazySrcLoc = src: {
5099 }5096 if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| {
5100 if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| {5097 if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rval| {
5101 if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rval| {5098 if (lval.isUndef() or rval.isUndef()) {
5102 if (lval.isUndef() or rval.isUndef()) {5099 return sema.addConstUndef(Type.initTag(.bool));
5103 return sema.addConstUndef(Type.initTag(.bool));5100 }
5104 }5101 // TODO optimisation opportunity: evaluate if mem.eql is faster with the names,
5105 // TODO optimisation opportunity: evaluate if mem.eql is faster with the names,5102 // or calling to Module.getErrorValue to get the values and then compare them is
5106 // or calling to Module.getErrorValue to get the values and then compare them is5103 // faster.
5107 // faster.5104 const lhs_name = lval.castTag(.@"error").?.data.name;
5108 const lhs_name = lval.castTag(.@"error").?.data.name;5105 const rhs_name = rval.castTag(.@"error").?.data.name;
5109 const rhs_name = rval.castTag(.@"error").?.data.name;5106 if (mem.eql(u8, lhs_name, rhs_name) == (op == .eq)) {
5110 if (mem.eql(u8, lhs_name, rhs_name) == (op == .eq)) {5107 return Air.Inst.Ref.bool_true;
5111 return Air.Inst.Ref.bool_true;5108 } else {
5109 return Air.Inst.Ref.bool_false;
5110 }
5112 } else {5111 } else {
5113 return Air.Inst.Ref.bool_false;5112 break :src rhs_src;
5114 }5113 }
5114 } else {
5115 break :src lhs_src;
5115 }5116 }
5116 }5117 };
5117 try sema.requireRuntimeBlock(block, src);5118 try sema.requireRuntimeBlock(block, runtime_src);
5118 const tag: Air.Inst.Tag = if (op == .eq) .cmp_eq else .cmp_neq;5119 return block.addBinOp(air_tag, lhs, rhs);
5119 return block.addBinOp(tag, lhs, rhs);5120 }
5120 } else if (lhs_ty.isNumeric() and rhs_ty.isNumeric()) {5121 if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
5121 // This operation allows any combination of integer and float types, regardless of the
5122 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
5123 // numeric types.
5124 return sema.cmpNumeric(block, src, lhs, rhs, op, lhs_src, rhs_src);
5125 } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
5126 if (!is_equality_cmp) {
5127 return mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)});
5128 }
5129 const lhs_as_type = try sema.analyzeAsType(block, lhs_src, lhs);5122 const lhs_as_type = try sema.analyzeAsType(block, lhs_src, lhs);
5130 const rhs_as_type = try sema.analyzeAsType(block, rhs_src, rhs);5123 const rhs_as_type = try sema.analyzeAsType(block, rhs_src, rhs);
5131 if (lhs_as_type.eql(rhs_as_type) == (op == .eq)) {5124 if (lhs_as_type.eql(rhs_as_type) == (op == .eq)) {
...@@ -5134,11 +5127,54 @@ fn zirCmp(...@@ -5134,11 +5127,54 @@ fn zirCmp(
5134 return Air.Inst.Ref.bool_false;5127 return Air.Inst.Ref.bool_false;
5135 }5128 }
5136 }5129 }
5130 return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true);
5131}
5132
5133/// Only called for non-equality operators. See also `zirCmpEq`.
5134fn zirCmp(
5135 sema: *Sema,
5136 block: *Scope.Block,
5137 inst: Zir.Inst.Index,
5138 op: std.math.CompareOperator,
5139) CompileError!Air.Inst.Ref {
5140 const tracy = trace(@src());
5141 defer tracy.end();
51375142
5143 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
5144 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
5145 const src: LazySrcLoc = inst_data.src();
5146 const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
5147 const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
5148 const lhs = sema.resolveInst(extra.lhs);
5149 const rhs = sema.resolveInst(extra.rhs);
5150 return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, false);
5151}
5152
5153fn analyzeCmp(
5154 sema: *Sema,
5155 block: *Scope.Block,
5156 src: LazySrcLoc,
5157 lhs: Air.Inst.Ref,
5158 rhs: Air.Inst.Ref,
5159 op: std.math.CompareOperator,
5160 lhs_src: LazySrcLoc,
5161 rhs_src: LazySrcLoc,
5162 is_equality_cmp: bool,
5163) CompileError!Air.Inst.Ref {
5164 const lhs_ty = sema.typeOf(lhs);
5165 const rhs_ty = sema.typeOf(rhs);
5166 if (lhs_ty.isNumeric() and rhs_ty.isNumeric()) {
5167 // This operation allows any combination of integer and float types, regardless of the
5168 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
5169 // numeric types.
5170 return sema.cmpNumeric(block, src, lhs, rhs, op, lhs_src, rhs_src);
5171 }
5138 const instructions = &[_]Air.Inst.Ref{ lhs, rhs };5172 const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
5139 const resolved_type = try sema.resolvePeerTypes(block, src, instructions);5173 const resolved_type = try sema.resolvePeerTypes(block, src, instructions);
5140 if (!resolved_type.isSelfComparable(is_equality_cmp)) {5174 if (!resolved_type.isSelfComparable(is_equality_cmp)) {
5141 return mod.fail(&block.base, src, "operator not allowed for type '{}'", .{resolved_type});5175 return sema.mod.fail(&block.base, src, "{s} operator not allowed for type '{}'", .{
5176 @tagName(op), resolved_type,
5177 });
5142 }5178 }
51435179
5144 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);5180 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
...@@ -5146,19 +5182,31 @@ fn zirCmp(...@@ -5146,19 +5182,31 @@ fn zirCmp(
51465182
5147 const runtime_src: LazySrcLoc = src: {5183 const runtime_src: LazySrcLoc = src: {
5148 if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {5184 if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
5185 if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type);
5149 if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {5186 if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
5150 if (lhs_val.isUndef() or rhs_val.isUndef()) {5187 if (rhs_val.isUndef()) return sema.addConstUndef(resolved_type);
5151 return sema.addConstUndef(resolved_type);5188
5152 }
5153 if (lhs_val.compare(op, rhs_val, resolved_type)) {5189 if (lhs_val.compare(op, rhs_val, resolved_type)) {
5154 return Air.Inst.Ref.bool_true;5190 return Air.Inst.Ref.bool_true;
5155 } else {5191 } else {
5156 return Air.Inst.Ref.bool_false;5192 return Air.Inst.Ref.bool_false;
5157 }5193 }
5158 } else {5194 } else {
5195 if (resolved_type.zigTypeTag() == .Bool) {
5196 // We can lower bool eq/neq more efficiently.
5197 return sema.runtimeBoolCmp(block, op, casted_rhs, lhs_val.toBool(), rhs_src);
5198 }
5159 break :src rhs_src;5199 break :src rhs_src;
5160 }5200 }
5161 } else {5201 } else {
5202 // For bools, we still check the other operand, because we can lower
5203 // bool eq/neq more efficiently.
5204 if (resolved_type.zigTypeTag() == .Bool) {
5205 if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
5206 if (rhs_val.isUndef()) return sema.addConstUndef(resolved_type);
5207 return sema.runtimeBoolCmp(block, op, casted_lhs, rhs_val.toBool(), lhs_src);
5208 }
5209 }
5162 break :src lhs_src;5210 break :src lhs_src;
5163 }5211 }
5164 };5212 };
...@@ -5176,6 +5224,26 @@ fn zirCmp(...@@ -5176,6 +5224,26 @@ fn zirCmp(
5176 return block.addBinOp(tag, casted_lhs, casted_rhs);5224 return block.addBinOp(tag, casted_lhs, casted_rhs);
5177}5225}
51785226
5227/// cmp_eq (x, false) => not(x)
5228/// cmp_eq (x, true ) => x
5229/// cmp_neq(x, false) => x
5230/// cmp_neq(x, true ) => not(x)
5231fn runtimeBoolCmp(
5232 sema: *Sema,
5233 block: *Scope.Block,
5234 op: std.math.CompareOperator,
5235 lhs: Air.Inst.Ref,
5236 rhs: bool,
5237 runtime_src: LazySrcLoc,
5238) CompileError!Air.Inst.Ref {
5239 if ((op == .neq) == rhs) {
5240 try sema.requireRuntimeBlock(block, runtime_src);
5241 return block.addTyOp(.not, Type.initTag(.bool), lhs);
5242 } else {
5243 return lhs;
5244 }
5245}
5246
5179fn zirSizeOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {5247fn zirSizeOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
5180 const inst_data = sema.code.instructions.items(.data)[inst].un_node;5248 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
5181 const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };5249 const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };