Browse Source

Mock out `cg_emit_comp`

gingerBill 2 years ago
parent
commit
7f4efa90c8
2 changed files with 584 additions and 6 deletions
  1. 10 5
      src/tilde_backend.hpp
  2. 574 1
      src/tilde_expr.cpp

+ 10 - 5
src/tilde_backend.hpp

@@ -30,10 +30,10 @@ struct cgModule;
 
 
 enum cgValueKind : u32 {
-	cgValue_Value,
-	cgValue_Addr,
-	cgValue_Symbol,
-	cgValue_Multi,
+	cgValue_Value,  // rvalue
+	cgValue_Addr,   // lvalue
+	cgValue_Symbol, // global
+	cgValue_Multi,  // multiple values
 };
 
 struct cgValueMulti;
@@ -266,4 +266,9 @@ gb_internal cgValue cg_emit_ptr_offset(cgProcedure *p, cgValue ptr, cgValue inde
 gb_internal cgValue cg_emit_array_ep(cgProcedure *p, cgValue s, cgValue index);
 gb_internal cgValue cg_emit_array_epi(cgProcedure *p, cgValue s, i64 index);
 gb_internal cgValue cg_emit_struct_ep(cgProcedure *p, cgValue s, i64 index);
-gb_internal cgValue cg_emit_deep_field_gep(cgProcedure *p, cgValue e, Selection const &sel);
+gb_internal cgValue cg_emit_deep_field_gep(cgProcedure *p, cgValue e, Selection const &sel);
+
+gb_internal cgValue cg_emit_conv(cgProcedure *p, cgValue value, Type *t);
+gb_internal cgValue cg_emit_comp_against_nil(cgProcedure *p, TokenKind op_kind, cgValue x);
+gb_internal cgValue cg_emit_comp(cgProcedure *p, TokenKind op_kind, cgValue left, cgValue right);
+gb_internal cgValue cg_emit_arith(cgProcedure *p, TokenKind op, cgValue lhs, cgValue rhs, Type *type);

+ 574 - 1
src/tilde_expr.cpp

@@ -2,7 +2,15 @@ gb_internal cgValue cg_flatten_value(cgProcedure *p, cgValue value) {
 	GB_ASSERT(value.kind != cgValue_Multi);
 	if (value.kind == cgValue_Symbol) {
 		GB_ASSERT(is_type_internally_pointer_like(value.type));
-		value = cg_value(tb_inst_get_symbol_address(p->func, value.symbol), value.type);
+		return cg_value(tb_inst_get_symbol_address(p->func, value.symbol), value.type);
+	} else if (value.kind == cgValue_Addr) {
+		// TODO(bill): Is this a good idea?
+		// this converts an lvalue to an rvalue if trivially possible
+		TB_DataType dt = cg_data_type(value.type);
+		if (!TB_IS_VOID_TYPE(dt)) {
+			TB_CharUnits align = cast(TB_CharUnits)type_align_of(value.type);
+			return cg_value(tb_inst_load(p->func, dt, value.node, align, false), value.type);
+		}
 	}
 	return value;
 }
@@ -198,6 +206,571 @@ gb_internal cgValue cg_emit_byte_swap(cgProcedure *p, cgValue value, Type *end_t
 	return cg_emit_transmute(p, value, end_type);
 }
 
+gb_internal cgValue cg_emit_comp(cgProcedure *p, TokenKind op_kind, cgValue left, cgValue right) {
+	GB_ASSERT(gb_is_between(op_kind, Token__ComparisonBegin+1, Token__ComparisonEnd-1));
+
+	Type *a = core_type(left.type);
+	Type *b = core_type(right.type);
+
+	cgValue nil_check = {};
+	if (is_type_array_like(left.type) || is_type_array_like(right.type)) {
+		// don't do `nil` check if it is array-like
+	} else if (is_type_untyped_nil(left.type)) {
+		nil_check = cg_emit_comp_against_nil(p, op_kind, right);
+	} else if (is_type_untyped_nil(right.type)) {
+		nil_check = cg_emit_comp_against_nil(p, op_kind, left);
+	}
+	if (nil_check.node != nullptr) {
+		return nil_check;
+	}
+
+	if (are_types_identical(a, b)) {
+		// NOTE(bill): No need for a conversion
+	} /*else if (cg_is_const(left) || cg_is_const_nil(left)) {
+		left = cg_emit_conv(p, left, right.type);
+	} else if (cg_is_const(right) || cg_is_const_nil(right)) {
+		right = cg_emit_conv(p, right, left.type);
+	}*/ else {
+		Type *lt = left.type;
+		Type *rt = right.type;
+
+		lt = left.type;
+		rt = right.type;
+		i64 ls = type_size_of(lt);
+		i64 rs = type_size_of(rt);
+
+		// NOTE(bill): Quick heuristic, larger types are usually the target type
+		if (ls < rs) {
+			left = cg_emit_conv(p, left, rt);
+		} else if (ls > rs) {
+			right = cg_emit_conv(p, right, lt);
+		} else {
+			if (is_type_union(rt)) {
+				left = cg_emit_conv(p, left, rt);
+			} else {
+				right = cg_emit_conv(p, right, lt);
+			}
+		}
+	}
+
+	a = core_type(left.type);
+	b = core_type(right.type);
+	left  = cg_flatten_value(p, left);
+	right = cg_flatten_value(p, right);
+
+
+	if (is_type_matrix(a) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) {
+		GB_PANIC("TODO(bill): cg_emit_comp matrix");
+		// Type *tl = base_type(a);
+		// lbValue lhs = lb_address_from_load_or_generate_local(p, left);
+		// lbValue rhs = lb_address_from_load_or_generate_local(p, right);
+
+
+		// // TODO(bill): Test to see if this is actually faster!!!!
+		// auto args = array_make<lbValue>(permanent_allocator(), 3);
+		// args[0] = lb_emit_conv(p, lhs, t_rawptr);
+		// args[1] = lb_emit_conv(p, rhs, t_rawptr);
+		// args[2] = lb_const_int(p->module, t_int, type_size_of(tl));
+		// lbValue val = lb_emit_runtime_call(p, "memory_compare", args);
+		// lbValue res = lb_emit_comp(p, op_kind, val, lb_const_nil(p->module, val.type));
+		// return lb_emit_conv(p, res, t_bool);
+	}
+	if (is_type_array_like(a)) {
+		GB_PANIC("TODO(bill): cg_emit_comp is_type_array_like");
+		// Type *tl = base_type(a);
+		// lbValue lhs = lb_address_from_load_or_generate_local(p, left);
+		// lbValue rhs = lb_address_from_load_or_generate_local(p, right);
+
+
+		// TokenKind cmp_op = Token_And;
+		// lbValue res = lb_const_bool(p->module, t_llvm_bool, true);
+		// if (op_kind == Token_NotEq) {
+		// 	res = lb_const_bool(p->module, t_llvm_bool, false);
+		// 	cmp_op = Token_Or;
+		// } else if (op_kind == Token_CmpEq) {
+		// 	res = lb_const_bool(p->module, t_llvm_bool, true);
+		// 	cmp_op = Token_And;
+		// }
+
+		// bool inline_array_arith = lb_can_try_to_inline_array_arith(tl);
+		// i32 count = 0;
+		// switch (tl->kind) {
+		// case Type_Array:           count = cast(i32)tl->Array.count;           break;
+		// case Type_EnumeratedArray: count = cast(i32)tl->EnumeratedArray.count; break;
+		// }
+
+		// if (inline_array_arith) {
+		// 	// inline
+		// 	lbAddr val = lb_add_local_generated(p, t_bool, false);
+		// 	lb_addr_store(p, val, res);
+		// 	for (i32 i = 0; i < count; i++) {
+		// 		lbValue x = lb_emit_load(p, lb_emit_array_epi(p, lhs, i));
+		// 		lbValue y = lb_emit_load(p, lb_emit_array_epi(p, rhs, i));
+		// 		lbValue cmp = lb_emit_comp(p, op_kind, x, y);
+		// 		lbValue new_res = lb_emit_arith(p, cmp_op, lb_addr_load(p, val), cmp, t_bool);
+		// 		lb_addr_store(p, val, lb_emit_conv(p, new_res, t_bool));
+		// 	}
+
+		// 	return lb_addr_load(p, val);
+		// } else {
+		// 	if (is_type_simple_compare(tl) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) {
+		// 		// TODO(bill): Test to see if this is actually faster!!!!
+		// 		auto args = array_make<lbValue>(permanent_allocator(), 3);
+		// 		args[0] = lb_emit_conv(p, lhs, t_rawptr);
+		// 		args[1] = lb_emit_conv(p, rhs, t_rawptr);
+		// 		args[2] = lb_const_int(p->module, t_int, type_size_of(tl));
+		// 		lbValue val = lb_emit_runtime_call(p, "memory_compare", args);
+		// 		lbValue res = lb_emit_comp(p, op_kind, val, lb_const_nil(p->module, val.type));
+		// 		return lb_emit_conv(p, res, t_bool);
+		// 	} else {
+		// 		lbAddr val = lb_add_local_generated(p, t_bool, false);
+		// 		lb_addr_store(p, val, res);
+		// 		auto loop_data = lb_loop_start(p, count, t_i32);
+		// 		{
+		// 			lbValue i = loop_data.idx;
+		// 			lbValue x = lb_emit_load(p, lb_emit_array_ep(p, lhs, i));
+		// 			lbValue y = lb_emit_load(p, lb_emit_array_ep(p, rhs, i));
+		// 			lbValue cmp = lb_emit_comp(p, op_kind, x, y);
+		// 			lbValue new_res = lb_emit_arith(p, cmp_op, lb_addr_load(p, val), cmp, t_bool);
+		// 			lb_addr_store(p, val, lb_emit_conv(p, new_res, t_bool));
+		// 		}
+		// 		lb_loop_end(p, loop_data);
+
+		// 		return lb_addr_load(p, val);
+		// 	}
+		// }
+	}
+
+	if ((is_type_struct(a) || is_type_union(a)) && is_type_comparable(a)) {
+		GB_PANIC("TODO(bill): cg_compare_records");
+		// return cg_compare_records(p, op_kind, left, right, a);
+	}
+
+	if ((is_type_struct(b) || is_type_union(b)) && is_type_comparable(b)) {
+		GB_PANIC("TODO(bill): cg_compare_records");
+		// return cg_compare_records(p, op_kind, left, right, b);
+	}
+
+	if (is_type_string(a)) {
+		if (is_type_cstring(a)) {
+			left  = cg_emit_conv(p, left, t_string);
+			right = cg_emit_conv(p, right, t_string);
+		}
+
+		char const *runtime_procedure = nullptr;
+		switch (op_kind) {
+		case Token_CmpEq: runtime_procedure = "string_eq"; break;
+		case Token_NotEq: runtime_procedure = "string_ne"; break;
+		case Token_Lt:    runtime_procedure = "string_lt"; break;
+		case Token_Gt:    runtime_procedure = "string_gt"; break;
+		case Token_LtEq:  runtime_procedure = "string_le"; break;
+		case Token_GtEq:  runtime_procedure = "string_gt"; break;
+		}
+		GB_ASSERT(runtime_procedure != nullptr);
+
+		GB_PANIC("TODO(bill): cg_emit_runtime_call");
+		// auto args = array_make<lbValue>(permanent_allocator(), 2);
+		// args[0] = left;
+		// args[1] = right;
+		// return cg_emit_runtime_call(p, runtime_procedure, args);
+	}
+
+	if (is_type_complex(a)) {
+		char const *runtime_procedure = "";
+		i64 sz = 8*type_size_of(a);
+		switch (sz) {
+		case 32:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "complex32_eq"; break;
+			case Token_NotEq: runtime_procedure = "complex32_ne"; break;
+			}
+			break;
+		case 64:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "complex64_eq"; break;
+			case Token_NotEq: runtime_procedure = "complex64_ne"; break;
+			}
+			break;
+		case 128:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "complex128_eq"; break;
+			case Token_NotEq: runtime_procedure = "complex128_ne"; break;
+			}
+			break;
+		}
+		GB_ASSERT(runtime_procedure != nullptr);
+
+		GB_PANIC("TODO(bill): cg_emit_runtime_call");
+		// auto args = array_make<lbValue>(permanent_allocator(), 2);
+		// args[0] = left;
+		// args[1] = right;
+		// return lb_emit_runtime_call(p, runtime_procedure, args);
+	}
+
+	if (is_type_quaternion(a)) {
+		char const *runtime_procedure = "";
+		i64 sz = 8*type_size_of(a);
+		switch (sz) {
+		case 64:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "quaternion64_eq"; break;
+			case Token_NotEq: runtime_procedure = "quaternion64_ne"; break;
+			}
+			break;
+		case 128:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "quaternion128_eq"; break;
+			case Token_NotEq: runtime_procedure = "quaternion128_ne"; break;
+			}
+			break;
+		case 256:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "quaternion256_eq"; break;
+			case Token_NotEq: runtime_procedure = "quaternion256_ne"; break;
+			}
+			break;
+		}
+		GB_ASSERT(runtime_procedure != nullptr);
+
+		GB_PANIC("TODO(bill): cg_emit_runtime_call");
+		// auto args = array_make<lbValue>(permanent_allocator(), 2);
+		// args[0] = left;
+		// args[1] = right;
+		// return lb_emit_runtime_call(p, runtime_procedure, args);
+	}
+
+	if (is_type_bit_set(a)) {
+		switch (op_kind) {
+		case Token_Lt:
+		case Token_LtEq:
+		case Token_Gt:
+		case Token_GtEq:
+			{
+				Type *it = bit_set_to_int(a);
+				cgValue lhs = cg_emit_transmute(p, left, it);
+				cgValue rhs = cg_emit_transmute(p, right, it);
+				cgValue res = cg_emit_arith(p, Token_And, lhs, rhs, it);
+				GB_ASSERT(lhs.kind == cgValue_Value);
+				GB_ASSERT(rhs.kind == cgValue_Value);
+				GB_ASSERT(res.kind == cgValue_Value);
+
+				if (op_kind == Token_Lt || op_kind == Token_LtEq) {
+					// (lhs & rhs) == lhs
+					res = cg_value(tb_inst_cmp_eq(p->func, res.node, lhs.node), t_bool);
+				} else if (op_kind == Token_Gt || op_kind == Token_GtEq) {
+					// (lhs & rhs) == rhs
+					res = cg_value(tb_inst_cmp_eq(p->func, res.node, rhs.node), t_bool);
+				}
+
+				// NOTE(bill): Strict subsets
+				if (op_kind == Token_Lt || op_kind == Token_Gt) {
+					// res &~ (lhs == rhs)
+					cgValue eq = cg_value(tb_inst_cmp_eq(p->func, lhs.node, rhs.node), t_bool);
+					res = cg_emit_arith(p, Token_AndNot, res, eq, t_bool);
+				}
+				return res;
+			}
+
+		case Token_CmpEq:
+			GB_ASSERT(left.kind  == cgValue_Value);
+			GB_ASSERT(right.kind == cgValue_Value);
+			return cg_value(tb_inst_cmp_eq(p->func, left.node, right.node), t_bool);
+		case Token_NotEq:
+			GB_ASSERT(left.kind  == cgValue_Value);
+			GB_ASSERT(right.kind == cgValue_Value);
+			return cg_value(tb_inst_cmp_ne(p->func, left.node, right.node), t_bool);
+		}
+	}
+
+	if (op_kind != Token_CmpEq && op_kind != Token_NotEq) {
+		Type *t = left.type;
+		if (is_type_integer(t) && is_type_different_to_arch_endianness(t)) {
+			Type *platform_type = integer_endian_type_to_platform_type(t);
+			cgValue x = cg_emit_byte_swap(p, left, platform_type);
+			cgValue y = cg_emit_byte_swap(p, right, platform_type);
+			left = x;
+			right = y;
+		} else if (is_type_float(t) && is_type_different_to_arch_endianness(t)) {
+			Type *platform_type = integer_endian_type_to_platform_type(t);
+			cgValue x = cg_emit_conv(p, left, platform_type);
+			cgValue y = cg_emit_conv(p, right, platform_type);
+			left = x;
+			right = y;
+		}
+	}
+
+	a = core_type(left.type);
+	b = core_type(right.type);
+
+
+	if (is_type_integer(a) ||
+	    is_type_boolean(a) ||
+	    is_type_pointer(a) ||
+	    is_type_multi_pointer(a) ||
+	    is_type_proc(a) ||
+	    is_type_enum(a) ||
+	    is_type_typeid(a)) {
+	    	TB_Node *lhs = left.node;
+		TB_Node *rhs = right.node;
+		TB_Node *res = nullptr;
+
+		bool is_signed = is_type_integer(left.type) && !is_type_unsigned(left.type);
+		switch (op_kind) {
+		case Token_CmpEq: res = tb_inst_cmp_eq(p->func, lhs, rhs); break;
+		case Token_NotEq: res = tb_inst_cmp_ne(p->func, lhs, rhs); break;
+		case Token_Gt:    res = tb_inst_cmp_igt(p->func, lhs, rhs, is_signed); break;
+		case Token_GtEq:  res = tb_inst_cmp_ige(p->func, lhs, rhs, is_signed); break;
+		case Token_Lt:    res = tb_inst_cmp_ilt(p->func, lhs, rhs, is_signed); break;
+		case Token_LtEq:  res = tb_inst_cmp_ige(p->func, lhs, rhs, is_signed); break;
+		}
+
+		GB_ASSERT(res != nullptr);
+		return cg_value(res, t_bool);
+	} else if (is_type_float(a)) {
+	    	TB_Node *lhs = left.node;
+		TB_Node *rhs = right.node;
+		TB_Node *res = nullptr;
+		switch (op_kind) {
+		case Token_CmpEq: res = tb_inst_cmp_eq(p->func, lhs, rhs);  break;
+		case Token_NotEq: res = tb_inst_cmp_ne(p->func, lhs, rhs);  break;
+		case Token_Gt:    res = tb_inst_cmp_fgt(p->func, lhs, rhs); break;
+		case Token_GtEq:  res = tb_inst_cmp_fge(p->func, lhs, rhs); break;
+		case Token_Lt:    res = tb_inst_cmp_flt(p->func, lhs, rhs); break;
+		case Token_LtEq:  res = tb_inst_cmp_fge(p->func, lhs, rhs); break;
+		}
+		GB_ASSERT(res != nullptr);
+		return cg_value(res, t_bool);
+	} else if (is_type_simd_vector(a)) {
+		GB_PANIC("TODO(bill): #simd vector");
+		// LLVMValueRef mask = nullptr;
+		// Type *elem = base_array_type(a);
+		// if (is_type_float(elem)) {
+		// 	LLVMRealPredicate pred = {};
+		// 	switch (op_kind) {
+		// 	case Token_CmpEq: pred = LLVMRealOEQ; break;
+		// 	case Token_NotEq: pred = LLVMRealONE; break;
+		// 	}
+		// 	mask = LLVMBuildFCmp(p->builder, pred, left.value, right.value, "");
+		// } else {
+		// 	LLVMIntPredicate pred = {};
+		// 	switch (op_kind) {
+		// 	case Token_CmpEq: pred = LLVMIntEQ; break;
+		// 	case Token_NotEq: pred = LLVMIntNE; break;
+		// 	}
+		// 	mask = LLVMBuildICmp(p->builder, pred, left.value, right.value, "");
+		// }
+		// GB_ASSERT_MSG(mask != nullptr, "Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type)));
+
+		// /* NOTE(bill, 2022-05-28):
+		// 	Thanks to Per Vognsen, sign extending <N x i1> to
+		// 	a vector of the same width as the input vector, bit casting to an integer,
+		// 	and then comparing against zero is the better option
+		// 	See: https://lists.llvm.org/pipermail/llvm-dev/2012-September/053046.html
+
+		// 	// Example assuming 128-bit vector
+
+		// 	%1 = <4 x float> ...
+		// 	%2 = <4 x float> ...
+		// 	%3 = fcmp oeq <4 x float> %1, %2
+		// 	%4 = sext <4 x i1> %3 to <4 x i32>
+		// 	%5 = bitcast <4 x i32> %4 to i128
+		// 	%6 = icmp ne i128 %5, 0
+		// 	br i1 %6, label %true1, label %false2
+
+		// 	This will result in 1 cmpps + 1 ptest + 1 br
+		// 	(even without SSE4.1, contrary to what the mail list states, because of pmovmskb)
+
+		// */
+
+		// unsigned count = cast(unsigned)get_array_type_count(a);
+		// unsigned elem_sz = cast(unsigned)(type_size_of(elem)*8);
+		// LLVMTypeRef mask_type = LLVMVectorType(LLVMIntTypeInContext(p->module->ctx, elem_sz), count);
+		// mask = LLVMBuildSExtOrBitCast(p->builder, mask, mask_type, "");
+
+		// LLVMTypeRef mask_int_type = LLVMIntTypeInContext(p->module->ctx, cast(unsigned)(8*type_size_of(a)));
+		// LLVMValueRef mask_int = LLVMBuildBitCast(p->builder, mask, mask_int_type, "");
+		// res.value = LLVMBuildICmp(p->builder, LLVMIntNE, mask_int, LLVMConstNull(LLVMTypeOf(mask_int)), "");
+		// return res;
+	}
+
+	GB_PANIC("Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type)));
+	return {};
+}
+
+gb_internal cgValue cg_emit_comp_against_nil(cgProcedure *p, TokenKind op_kind, cgValue x) {
+	GB_ASSERT(op_kind == Token_CmpEq || op_kind == Token_NotEq);
+	x = cg_flatten_value(p, x);
+	cgValue res = {};
+	Type *t = x.type;
+
+	TB_DataType dt = cg_data_type(t);
+
+	Type *bt = base_type(t);
+	TypeKind type_kind = bt->kind;
+
+	switch (type_kind) {
+	case Type_Basic:
+		switch (bt->Basic.kind) {
+		case Basic_rawptr:
+		case Basic_cstring:
+			GB_ASSERT(x.kind == cgValue_Value);
+			if (op_kind == Token_CmpEq) {
+				return cg_value(tb_inst_cmp_eq(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+			} else if (op_kind == Token_NotEq) {
+				return cg_value(tb_inst_cmp_ne(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+			}
+			break;
+		case Basic_any:
+			{
+				GB_PANIC("TODO(bill): cg_emit_struct_ev");
+				// // TODO(bill): is this correct behaviour for nil comparison for any?
+				// cgValue data = cg_emit_struct_ev(p, x, 0);
+				// cgValue ti   = cg_emit_struct_ev(p, x, 1);
+				// if (op_kind == Token_CmpEq) {
+				// 	LLVMValueRef a =  LLVMBuildIsNull(p->builder, data.value, "");
+				// 	LLVMValueRef b =  LLVMBuildIsNull(p->builder, ti.value, "");
+				// 	res.value = LLVMBuildOr(p->builder, a, b, "");
+				// 	return res;
+				// } else if (op_kind == Token_NotEq) {
+				// 	LLVMValueRef a =  LLVMBuildIsNotNull(p->builder, data.value, "");
+				// 	LLVMValueRef b =  LLVMBuildIsNotNull(p->builder, ti.value, "");
+				// 	res.value = LLVMBuildAnd(p->builder, a, b, "");
+				// 	return res;
+				// }
+			}
+			break;
+		case Basic_typeid:
+			cgValue invalid_typeid = cg_const_value(p, t_typeid, exact_value_i64(0));
+			return cg_emit_comp(p, op_kind, x, invalid_typeid);
+		}
+		break;
+
+	case Type_Enum:
+	case Type_Pointer:
+	case Type_MultiPointer:
+	case Type_Proc:
+	case Type_BitSet:
+		GB_ASSERT(x.kind == cgValue_Value);
+		if (op_kind == Token_CmpEq) {
+			return cg_value(tb_inst_cmp_eq(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+		} else if (op_kind == Token_NotEq) {
+			return cg_value(tb_inst_cmp_ne(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+		}
+		break;
+
+	case Type_Slice:
+		{
+			GB_PANIC("TODO(bill): cg_emit_struct_ev");
+			// cgValue data = cg_emit_struct_ev(p, x, 0);
+			// if (op_kind == Token_CmpEq) {
+			// 	res.value = LLVMBuildIsNull(p->builder, data.value, "");
+			// 	return res;
+			// } else if (op_kind == Token_NotEq) {
+			// 	res.value = LLVMBuildIsNotNull(p->builder, data.value, "");
+			// 	return res;
+			// }
+		}
+		break;
+
+	case Type_DynamicArray:
+		{
+			GB_PANIC("TODO(bill): cg_emit_struct_ev");
+			// cgValue data = cg_emit_struct_ev(p, x, 0);
+			// if (op_kind == Token_CmpEq) {
+			// 	res.value = LLVMBuildIsNull(p->builder, data.value, "");
+			// 	return res;
+			// } else if (op_kind == Token_NotEq) {
+			// 	res.value = LLVMBuildIsNotNull(p->builder, data.value, "");
+			// 	return res;
+			// }
+		}
+		break;
+
+	case Type_Map:
+		{
+			GB_PANIC("TODO(bill): cg_emit_struct_ev");
+			// cgValue data_ptr = cg_emit_struct_ev(p, x, 0);
+
+			// if (op_kind == Token_CmpEq) {
+			// 	res.value = LLVMBuildIsNull(p->builder, data_ptr.value, "");
+			// 	return res;
+			// } else {
+			// 	res.value = LLVMBuildIsNotNull(p->builder, data_ptr.value, "");
+			// 	return res;
+			// }
+		}
+		break;
+
+	case Type_Union:
+		{
+			GB_PANIC("TODO(bill): cg_emit_struct_ev");
+			// if (type_size_of(t) == 0) {
+			// 	if (op_kind == Token_CmpEq) {
+			// 		return cg_const_bool(p->module, t_llvm_bool, true);
+			// 	} else if (op_kind == Token_NotEq) {
+			// 		return cg_const_bool(p->module, t_llvm_bool, false);
+			// 	}
+			// } else if (is_type_union_maybe_pointer(t)) {
+			// 	cgValue tag = cg_emit_transmute(p, x, t_rawptr);
+			// 	return cg_emit_comp_against_nil(p, op_kind, tag);
+			// } else {
+			// 	cgValue tag = cg_emit_union_tag_value(p, x);
+			// 	return cg_emit_comp(p, op_kind, tag, cg_zero(p->module, tag.type));
+			// }
+		}
+		break;
+	case Type_Struct:
+		GB_PANIC("TODO(bill): cg_emit_struct_ev");
+		// if (is_type_soa_struct(t)) {
+		// 	Type *bt = base_type(t);
+		// 	if (bt->Struct.soa_kind == StructSoa_Slice) {
+		// 		LLVMValueRef the_value = {};
+		// 		if (bt->Struct.fields.count == 0) {
+		// 			cgValue len = cg_soa_struct_len(p, x);
+		// 			the_value = len.value;
+		// 		} else {
+		// 			cgValue first_field = cg_emit_struct_ev(p, x, 0);
+		// 			the_value = first_field.value;
+		// 		}
+		// 		if (op_kind == Token_CmpEq) {
+		// 			res.value = LLVMBuildIsNull(p->builder, the_value, "");
+		// 			return res;
+		// 		} else if (op_kind == Token_NotEq) {
+		// 			res.value = LLVMBuildIsNotNull(p->builder, the_value, "");
+		// 			return res;
+		// 		}
+		// 	} else if (bt->Struct.soa_kind == StructSoa_Dynamic) {
+		// 		LLVMValueRef the_value = {};
+		// 		if (bt->Struct.fields.count == 0) {
+		// 			cgValue cap = cg_soa_struct_cap(p, x);
+		// 			the_value = cap.value;
+		// 		} else {
+		// 			cgValue first_field = cg_emit_struct_ev(p, x, 0);
+		// 			the_value = first_field.value;
+		// 		}
+		// 		if (op_kind == Token_CmpEq) {
+		// 			res.value = LLVMBuildIsNull(p->builder, the_value, "");
+		// 			return res;
+		// 		} else if (op_kind == Token_NotEq) {
+		// 			res.value = LLVMBuildIsNotNull(p->builder, the_value, "");
+		// 			return res;
+		// 		}
+		// 	}
+		// } else if (is_type_struct(t) && type_has_nil(t)) {
+		// 	auto args = array_make<cgValue>(permanent_allocator(), 2);
+		// 	cgValue lhs = cg_address_from_load_or_generate_local(p, x);
+		// 	args[0] = cg_emit_conv(p, lhs, t_rawptr);
+		// 	args[1] = cg_const_int(p->module, t_int, type_size_of(t));
+		// 	cgValue val = cg_emit_runtime_call(p, "memory_compare_zero", args);
+		// 	cgValue res = cg_emit_comp(p, op_kind, val, cg_const_int(p->module, t_int, 0));
+		// 	return res;
+		// }
+		break;
+	}
+	GB_PANIC("Unknown handled type: %s -> %s", type_to_string(t), type_to_string(bt));
+	return {};
+}
+
 gb_internal cgValue cg_emit_conv(cgProcedure *p, cgValue value, Type *t) {
 	t = reduce_tuple_to_single_type(t);