| ... | ... | @@ -193,12 +193,12 @@ pub fn analyzeBody( |
| 193 | 193 | .call_compile_time => try sema.zirCall(block, inst, .compile_time, false), |
| 194 | 194 | .call_nosuspend => try sema.zirCall(block, inst, .no_async, false), |
| 195 | 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 | 196 | .cmp_lt => try sema.zirCmp(block, inst, .lt), |
| 200 | 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 | 202 | .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst), |
| 203 | 203 | .decl_ref => try sema.zirDeclRef(block, inst), |
| 204 | 204 | .decl_val => try sema.zirDeclVal(block, inst), |
| ... | ... | @@ -5040,17 +5040,18 @@ fn zirAsm( |
| 5040 | 5040 | return asm_air; |
| 5041 | 5041 | } |
| 5042 | 5042 | |
| 5043 | | fn zirCmp( |
| 5043 | /// Only called for equality operators. See also `zirCmp`. |
| 5044 | fn zirCmpEq( |
| 5044 | 5045 | sema: *Sema, |
| 5045 | 5046 | block: *Scope.Block, |
| 5046 | 5047 | inst: Zir.Inst.Index, |
| 5047 | 5048 | op: std.math.CompareOperator, |
| 5049 | air_tag: Air.Inst.Tag, |
| 5048 | 5050 | ) CompileError!Air.Inst.Ref { |
| 5049 | 5051 | const tracy = trace(@src()); |
| 5050 | 5052 | defer tracy.end(); |
| 5051 | 5053 | |
| 5052 | 5054 | const mod = sema.mod; |
| 5053 | | |
| 5054 | 5055 | const inst_data = sema.code.instructions.items(.data)[inst].pl_node; |
| 5055 | 5056 | const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; |
| 5056 | 5057 | const src: LazySrcLoc = inst_data.src(); |
| ... | ... | @@ -5059,73 +5060,65 @@ fn zirCmp( |
| 5059 | 5060 | const lhs = sema.resolveInst(extra.lhs); |
| 5060 | 5061 | const rhs = sema.resolveInst(extra.rhs); |
| 5061 | 5062 | |
| 5062 | | const is_equality_cmp = switch (op) { |
| 5063 | | .eq, .neq => true, |
| 5064 | | else => false, |
| 5065 | | }; |
| 5066 | 5063 | const lhs_ty = sema.typeOf(lhs); |
| 5067 | 5064 | const rhs_ty = sema.typeOf(rhs); |
| 5068 | 5065 | const lhs_ty_tag = lhs_ty.zigTypeTag(); |
| 5069 | 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 | 5068 | // null == null, null != null |
| 5072 | 5069 | if (op == .eq) { |
| 5073 | 5070 | return Air.Inst.Ref.bool_true; |
| 5074 | 5071 | } else { |
| 5075 | 5072 | return Air.Inst.Ref.bool_false; |
| 5076 | 5073 | } |
| 5077 | | } else if (is_equality_cmp and |
| 5078 | | ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or |
| 5074 | } |
| 5075 | if (((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or |
| 5079 | 5076 | rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) |
| 5080 | 5077 | { |
| 5081 | 5078 | // comparing null with optionals |
| 5082 | 5079 | const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; |
| 5083 | 5080 | return sema.analyzeIsNull(block, src, opt_operand, op == .neq); |
| 5084 | | } else if (is_equality_cmp and |
| 5085 | | ((lhs_ty_tag == .Null and rhs_ty.isCPtr()) or (rhs_ty_tag == .Null and lhs_ty.isCPtr()))) |
| 5086 | | { |
| 5081 | } |
| 5082 | if (((lhs_ty_tag == .Null and rhs_ty.isCPtr()) or (rhs_ty_tag == .Null and lhs_ty.isCPtr()))) { |
| 5087 | 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 | 5086 | const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty; |
| 5090 | 5087 | return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type}); |
| 5091 | | } else if (is_equality_cmp and |
| 5092 | | ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or |
| 5088 | } |
| 5089 | if (((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or |
| 5093 | 5090 | (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) |
| 5094 | 5091 | { |
| 5095 | 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) { |
| 5097 | | if (!is_equality_cmp) { |
| 5098 | | return mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)}); |
| 5099 | | } |
| 5100 | | if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| { |
| 5101 | | if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rval| { |
| 5102 | | if (lval.isUndef() or rval.isUndef()) { |
| 5103 | | return sema.addConstUndef(Type.initTag(.bool)); |
| 5104 | | } |
| 5105 | | // TODO optimisation opportunity: evaluate if mem.eql is faster with the names, |
| 5106 | | // or calling to Module.getErrorValue to get the values and then compare them is |
| 5107 | | // faster. |
| 5108 | | const lhs_name = lval.castTag(.@"error").?.data.name; |
| 5109 | | const rhs_name = rval.castTag(.@"error").?.data.name; |
| 5110 | | if (mem.eql(u8, lhs_name, rhs_name) == (op == .eq)) { |
| 5111 | | return Air.Inst.Ref.bool_true; |
| 5093 | } |
| 5094 | if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { |
| 5095 | const runtime_src: LazySrcLoc = src: { |
| 5096 | if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| { |
| 5097 | if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rval| { |
| 5098 | if (lval.isUndef() or rval.isUndef()) { |
| 5099 | return sema.addConstUndef(Type.initTag(.bool)); |
| 5100 | } |
| 5101 | // 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 |
| 5103 | // faster. |
| 5104 | const lhs_name = lval.castTag(.@"error").?.data.name; |
| 5105 | const rhs_name = rval.castTag(.@"error").?.data.name; |
| 5106 | if (mem.eql(u8, lhs_name, rhs_name) == (op == .eq)) { |
| 5107 | return Air.Inst.Ref.bool_true; |
| 5108 | } else { |
| 5109 | return Air.Inst.Ref.bool_false; |
| 5110 | } |
| 5112 | 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 | | try sema.requireRuntimeBlock(block, src); |
| 5118 | | const tag: Air.Inst.Tag = if (op == .eq) .cmp_eq else .cmp_neq; |
| 5119 | | return block.addBinOp(tag, lhs, rhs); |
| 5120 | | } else if (lhs_ty.isNumeric() and rhs_ty.isNumeric()) { |
| 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 | | } |
| 5117 | }; |
| 5118 | try sema.requireRuntimeBlock(block, runtime_src); |
| 5119 | return block.addBinOp(air_tag, lhs, rhs); |
| 5120 | } |
| 5121 | if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) { |
| 5129 | 5122 | const lhs_as_type = try sema.analyzeAsType(block, lhs_src, lhs); |
| 5130 | 5123 | const rhs_as_type = try sema.analyzeAsType(block, rhs_src, rhs); |
| 5131 | 5124 | if (lhs_as_type.eql(rhs_as_type) == (op == .eq)) { |
| ... | ... | @@ -5134,11 +5127,54 @@ fn zirCmp( |
| 5134 | 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`. |
| 5134 | fn 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(); |
| 5137 | 5142 | |
| 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 | |
| 5153 | fn 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 | 5172 | const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; |
| 5139 | 5173 | const resolved_type = try sema.resolvePeerTypes(block, src, instructions); |
| 5140 | 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 | } |
| 5143 | 5179 | |
| 5144 | 5180 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| ... | ... | @@ -5146,19 +5182,31 @@ fn zirCmp( |
| 5146 | 5182 | |
| 5147 | 5183 | const runtime_src: LazySrcLoc = src: { |
| 5148 | 5184 | if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { |
| 5185 | if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type); |
| 5149 | 5186 | if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { |
| 5150 | | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 5151 | | return sema.addConstUndef(resolved_type); |
| 5152 | | } |
| 5187 | if (rhs_val.isUndef()) return sema.addConstUndef(resolved_type); |
| 5188 | |
| 5153 | 5189 | if (lhs_val.compare(op, rhs_val, resolved_type)) { |
| 5154 | 5190 | return Air.Inst.Ref.bool_true; |
| 5155 | 5191 | } else { |
| 5156 | 5192 | return Air.Inst.Ref.bool_false; |
| 5157 | 5193 | } |
| 5158 | 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 | 5199 | break :src rhs_src; |
| 5160 | 5200 | } |
| 5161 | 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 | 5210 | break :src lhs_src; |
| 5163 | 5211 | } |
| 5164 | 5212 | }; |
| ... | ... | @@ -5176,6 +5224,26 @@ fn zirCmp( |
| 5176 | 5224 | return block.addBinOp(tag, casted_lhs, casted_rhs); |
| 5177 | 5225 | } |
| 5178 | 5226 | |
| 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) |
| 5231 | fn 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 | |
| 5179 | 5247 | fn zirSizeOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 5180 | 5248 | const inst_data = sema.code.instructions.items(.data)[inst].un_node; |
| 5181 | 5249 | const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; |