Comparing slices and arrays of user-defined types that implement == operator now works #2486.

This commit is contained in:
Christoffer Lerno
2025-09-19 11:21:29 +02:00
parent 12eea4a98d
commit 3345e70c63
9 changed files with 483 additions and 40 deletions

View File

@@ -9,6 +9,7 @@
- Infer generic parameters lhs -> rhs: `List{int} x = list::NOHEAP`.
- Unify generic and regular module namespace.
- `env::PROJECT_VERSION` now returns the version in project.json.
- Comparing slices and arrays of user-defined types that implement == operator now works #2486.
### Fixes
- Compiler assert with var x @noinit = 0 #2452

View File

@@ -855,6 +855,7 @@ typedef struct
{
ExprId expr;
SubscriptIndex index;
bool no_check;
} ExprSubscript;
typedef struct
@@ -2032,6 +2033,13 @@ ARENA_DEF(expr, Expr)
ARENA_DEF(decl, Decl)
ARENA_DEF(type_info, TypeInfo)
INLINE Ast *ast_new(AstKind kind, SourceSpan span)
{
Ast *ast = ast_calloc();
ast->ast_kind = kind;
ast->span = span;
return ast;
}
INLINE TypeInfo *vartype(Decl *var)
{
@@ -2315,16 +2323,18 @@ Expr *expr_new_const_string(SourceSpan span, const char *string);
Expr *expr_new_const_null(SourceSpan span, Type *type);
Expr *expr_new_const_initializer(SourceSpan span, Type *type, ConstInitializer *initializer);
Expr *expr_new_expr_list_resolved(SourceSpan span, Type *type, Expr **expressions);
Expr *expr_new_binary(SourceSpan span, Expr *left, Expr *right, BinaryOp op);
Expr *expr_new_cond(Expr *expr);
const char *expr_kind_to_string(ExprKind kind);
bool expr_is_simple(Expr *expr, bool to_float);
bool expr_is_pure(Expr *expr);
bool expr_is_runtime_const(Expr *expr);
Expr *expr_generate_decl(Decl *decl, Expr *assign);
Expr *expr_new_two(Expr *first, Expr *second);
void expr_rewrite_two(Expr *original, Expr *first, Expr *second);
void expr_insert_addr(Expr *original);
bool sema_expr_rewrite_insert_deref(SemaContext *context, Expr *original);
Expr *expr_generate_decl(Decl *decl, Expr *assign);
Expr *expr_generated_local(Expr *assign, Decl **decl_ref);
Expr *expr_variable(Decl *decl);
Expr *expr_negate_expr(Expr *expr);
bool expr_may_addr(Expr *expr);

View File

@@ -3,6 +3,7 @@
// a copy of which can be found in the LICENSE file.
#include <iso646.h>
#include <math.h>
#include "compiler_internal.h"
@@ -592,6 +593,16 @@ void expr_insert_addr(Expr *original)
original->unary_expr.expr = inner;
}
Expr *expr_generated_local(Expr *assign, Decl **decl_ref)
{
Decl *decl = decl_new_generated_var(assign->type, VARDECL_LOCAL, assign->span);
Expr *expr_decl = expr_new(EXPR_DECL, decl->span);
expr_decl->decl_expr = decl;
decl->var.init_expr = assign;
*decl_ref = decl;
return expr_decl;
}
Expr *expr_generate_decl(Decl *decl, Expr *assign)
{
ASSERT(decl->decl_kind == DECL_VAR);
@@ -1006,6 +1017,23 @@ bool expr_is_simple(Expr *expr, bool to_float)
UNREACHABLE
}
Expr *expr_new_binary(SourceSpan span, Expr *left, Expr *right, BinaryOp op)
{
Expr *expr = expr_calloc();
expr->expr_kind = EXPR_BINARY;
expr->span = span;
expr->binary_expr.operator = op;
expr->binary_expr.left = exprid(left);
expr->binary_expr.right = exprid(right);
return expr;
}
Expr *expr_new_cond(Expr *expr)
{
Expr *cond = expr_new(EXPR_COND, expr->span);
vec_add(cond->cond_expr, expr);
return cond;
}
Expr *expr_new(ExprKind kind, SourceSpan start)
{

View File

@@ -711,6 +711,9 @@ static inline void llvm_emit_subscript_addr(GenContext *c, BEValue *value, Expr
Expr *parent_expr = exprptr(expr->subscript_expr.expr);
Expr *index_expr = exprptr(expr->subscript_expr.index.expr);
Type *parent_type = type_lowering(parent_expr->type);
bool is_safe = !expr->subscript_expr.no_check && safe_mode_enabled();
// First, get thing being subscripted.
llvm_emit_expr(c, value, parent_expr);
BEValue len = { .value = NULL };
@@ -721,7 +724,7 @@ static inline void llvm_emit_subscript_addr(GenContext *c, BEValue *value, Expr
bool start_from_end = expr->subscript_expr.index.start_from_end;
if (parent_type_kind == TYPE_SLICE)
{
needs_len = (safe_mode_enabled() && !llvm_is_global_eval(c)) || start_from_end;
needs_len = (is_safe && !llvm_is_global_eval(c)) || start_from_end;
if (needs_len)
{
if (LLVMIsAGlobalVariable(value->value) && llvm_is_global_eval(c))
@@ -740,7 +743,7 @@ static inline void llvm_emit_subscript_addr(GenContext *c, BEValue *value, Expr
{
// From back should always be folded.
ASSERT(!expr_is_const(expr) || !start_from_end);
needs_len = (safe_mode_enabled() && !expr_is_const(expr)) || start_from_end;
needs_len = (is_safe && !expr_is_const(expr)) || start_from_end;
if (needs_len)
{
llvm_value_set_int(c, &len, type_isz, value->type->array.len);
@@ -760,7 +763,7 @@ static inline void llvm_emit_subscript_addr(GenContext *c, BEValue *value, Expr
ASSERT(needs_len);
index.value = LLVMBuildNUWSub(c->builder, llvm_zext_trunc(c, len.value, llvm_get_type(c, index.type)), index.value, "");
}
if (needs_len && safe_mode_enabled() && !llvm_is_global_eval(c))
if (needs_len && is_safe && !llvm_is_global_eval(c))
{
llvm_emit_array_bounds_check(c, &index, len.value, index_expr->span);
}
@@ -6230,15 +6233,17 @@ DONE:
static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr *expr)
{
DebugScope *old_inline_location = c->debug.block_stack;
DebugScope updated;
if (llvm_use_debug(c))
DebugScope updated_val;
DebugScope *inline_location = old_inline_location;
Decl *macro = expr->macro_block.macro;
if (llvm_use_debug(c) && macro)
{
SourceSpan span = expr->span;
Decl *macro = expr->macro_block.macro;
LLVMMetadataRef macro_def = llvm_debug_create_macro(c, macro);
LLVMMetadataRef loc = llvm_create_debug_location(c, span);
updated = (DebugScope) { .lexical_block = macro_def, .inline_loc = loc, .outline_loc = old_inline_location };
updated_val = (DebugScope) { .lexical_block = macro_def, .inline_loc = loc, .outline_loc = old_inline_location };
inline_location = &updated_val;
}
FOREACH(Decl *, val, expr->macro_block.params)
{
@@ -6273,7 +6278,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr
llvm_emit_expr(c, &value, init_expr);
if (llvm_value_is_addr(&value) || val->var.is_written || val->var.is_addr || llvm_use_accurate_debug_info(c))
{
c->debug.block_stack = &updated;
c->debug.block_stack = inline_location;
llvm_emit_and_set_decl_alloca(c, val);
llvm_store_decl(c, val, &value);
continue;
@@ -6282,7 +6287,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr
val->backend_value = value.value;
}
c->debug.block_stack = &updated;
c->debug.block_stack = inline_location;
llvm_emit_return_block(c, be_value, expr->type, expr->macro_block.first_stmt, expr->macro_block.block_exit);
bool is_unreachable = expr->macro_block.is_noreturn && c->current_block;
if (is_unreachable)

View File

@@ -918,10 +918,7 @@ static Expr *parse_orelse(ParseContext *c, Expr *left_side, SourceSpan lhs_start
// Assignment operators have precedence right -> left.
ASSIGN_EXPR_OR_RET(right_side, parse_precedence(c, PREC_TERNARY), poisoned_expr);
Expr *expr = expr_new(EXPR_BINARY, lhs_start);
expr->binary_expr.operator = BINARYOP_ELSE;
expr->binary_expr.left = exprid(left_side);
expr->binary_expr.right = exprid(right_side);
Expr *expr = expr_new_binary(lhs_start, left_side, right_side, BINARYOP_ELSE);
RANGE_EXTEND_PREV(expr);
return expr;
@@ -947,10 +944,7 @@ static Expr *parse_binary(ParseContext *c, Expr *left_side, SourceSpan start)
ASSIGN_EXPR_OR_RET(right_side, parse_precedence(c, rules[operator_type].precedence + 1), poisoned_expr);
}
Expr *expr = expr_new(EXPR_BINARY, start);
expr->binary_expr.operator = binaryop_from_token(operator_type);
expr->binary_expr.left = exprid(left_side);
expr->binary_expr.right = exprid(right_side);
Expr *expr = expr_new_binary(start, left_side, right_side, binaryop_from_token(operator_type));
RANGE_EXTEND_PREV(expr);
return expr;

View File

@@ -5735,20 +5735,18 @@ static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr,
expr_rewrite_const_bool(expr, type_bool, type_is_ordered(flat));
return true;
case TYPE_PROPERTY_IS_EQ:
if (type_is_comparable(flat))
switch (sema_type_can_check_equality_with_overload(context, flat))
{
expr_rewrite_const_bool(expr, type_bool, type_is_comparable(flat));
case BOOL_ERR:
return false;
case BOOL_TRUE:
expr_rewrite_const_bool(expr, type_bool, true);
return true;
}
if (type_is_user_defined(type))
{
BoolErr res = sema_type_has_equality_overload(context, type);
if (res == BOOL_ERR) return false;
expr_rewrite_const_bool(expr, type_bool, res == BOOL_TRUE);
return true;
}
case BOOL_FALSE:
expr_rewrite_const_bool(expr, type_bool, false);
return true;
}
UNREACHABLE
case TYPE_PROPERTY_IS_SUBSTRUCT:
expr_rewrite_const_bool(expr, type_bool, type_is_substruct(flat));
return true;
@@ -8211,6 +8209,179 @@ BoolErr sema_type_has_equality_overload(SemaContext *context, CanonicalType *typ
}
UNREACHABLE
}
BoolErr sema_type_can_check_equality_with_overload(SemaContext *context, Type *type)
{
RETRY:
switch (type->type_kind)
{
case TYPE_INFERRED_VECTOR:
case TYPE_INFERRED_ARRAY:
case TYPE_POISONED:
UNREACHABLE
case TYPE_VOID:
case TYPE_FLEXIBLE_ARRAY:
case TYPE_OPTIONAL:
case TYPE_MEMBER:
case TYPE_UNTYPED_LIST:
return false;
case TYPE_UNION:
case TYPE_STRUCT:
if (type->decl->attr_compact && compiler.build.old_compact_eq) return true;
return sema_type_has_equality_overload(context, type);
case TYPE_BITSTRUCT:
return true;
case TYPE_TYPEDEF:
type = type->canonical;
goto RETRY;
case TYPE_SLICE:
case TYPE_ARRAY:
// Arrays are comparable if elements are
type = type->array.base;
goto RETRY;
case TYPE_DISTINCT:
case TYPE_CONST_ENUM:
if (sema_type_has_equality_overload(context, type)) return true;
type = type_inline(type);
goto RETRY;
case TYPE_BOOL:
case ALL_INTS:
case ALL_FLOATS:
case TYPE_ANY:
case TYPE_INTERFACE:
case TYPE_ANYFAULT:
case TYPE_TYPEID:
case TYPE_POINTER:
case TYPE_ENUM:
case TYPE_FUNC_PTR:
case TYPE_FUNC_RAW:
case TYPE_TYPEINFO:
case TYPE_VECTOR:
case TYPE_WILDCARD:
return true;
}
UNREACHABLE
}
INLINE Decl *ast_append_generated_local(Ast **ast_current_ref, Expr *init)
{
Ast *ast = ast_new(AST_DECLARE_STMT, init->span);
Decl *var = decl_new_generated_var(init->type, VARDECL_LOCAL, init->span);
assert(init->resolve_status == RESOLVE_DONE);
var->var.init_expr = init;
ast->declare_stmt = var;
(*ast_current_ref)->next = astid(ast);
*ast_current_ref = ast;
return var;
}
static inline bool sema_rewrite_expr_as_macro_block(SemaContext *context, Expr *expr, AstId start)
{
Type *old_expected_block = context->expected_block_type;
BlockExit **old_exit_ref = context->block_exit_ref;
context->expected_block_type = type_bool;
BlockExit** block_exit_ref = CALLOCS(BlockExit*);
context->block_exit_ref = block_exit_ref;
bool success;
Ast *compound_stmt = ast_new(AST_COMPOUND_STMT, expr->span);
compound_stmt->compound_stmt.first_stmt = start;
SCOPE_START_WITH_FLAGS(SCOPE_MACRO)
success = sema_analyse_stmt_chain(context, compound_stmt);
SCOPE_END;
context->expected_block_type = old_expected_block;
context->block_exit_ref = old_exit_ref;
if (!success) return false;
expr->expr_kind = EXPR_MACRO_BLOCK;
expr->resolve_status = RESOLVE_DONE;
expr->type = type_bool;
expr->macro_block = (ExprMacroBlock) {
.first_stmt = astid(compound_stmt),
.block_exit = block_exit_ref
};
return true;
}
static bool sema_rewrite_slice_comparison(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *max)
{
Ast dummy;
Ast *current = &dummy;
Decl *left_var = left->expr_kind == EXPR_IDENTIFIER ? left->ident_expr : ast_append_generated_local(&current, left);
Decl *right_var = right->expr_kind == EXPR_IDENTIFIER ? right->ident_expr : ast_append_generated_local(&current, right);
Decl *len_var_left = NULL;
ArraySize len = 0;
SourceSpan default_span = expr->span;
if (max->type_kind == TYPE_ARRAY)
{
len = max->array.len;
}
else
{
Expr *len_left = expr_new(EXPR_SLICE_LEN, left->span);
Expr *len_right = expr_new(EXPR_SLICE_LEN, right->span);
len_left->inner_expr = expr_variable(left_var);
len_right->inner_expr = expr_variable(right_var);
len_left->type = type_usz;
len_right->type = type_usz;
if (!sema_analyse_expr(context, len_left)) return false;
if (!sema_analyse_expr(context, len_right)) return false;
len_var_left = ast_append_generated_local(&current, len_left);
Ast *ast_if = ast_new(AST_IF_STMT, default_span);
Expr *expr_comparison = expr_new_binary(default_span, expr_variable(len_var_left), len_right, BINARYOP_NE);
Ast *ast_then = ast_new(AST_RETURN_STMT, default_span);
ast_then->return_stmt.expr = expr_new_const_bool(default_span, type_bool, false);
ast_if->if_stmt = (AstIfStmt) {
.cond = exprid(expr_new_cond(expr_comparison)),
.then_body = astid(ast_then),
};
current->next = astid(ast_if);
current = ast_if;
}
Decl *index = ast_append_generated_local(&current, expr_new_const_int(default_span, type_usz, 0));
Ast *ast = ast_new(AST_FOR_STMT, default_span);
Expr *cond_expr;
if (len > 0)
{
cond_expr = expr_new_binary(default_span, expr_variable(index), expr_new_const_int(default_span, type_usz, len), BINARYOP_LT);
}
else
{
cond_expr = expr_new_binary(default_span, expr_variable(index), expr_variable(len_var_left), BINARYOP_LT);
}
ast->for_stmt.cond = exprid(expr_new_cond(cond_expr));
Expr *update = expr_new(EXPR_UNARY, default_span);
update->unary_expr.expr = expr_variable(index);
update->unary_expr.operator = UNARYOP_INC;
update->unary_expr.no_wrap = true;
ast->for_stmt.incr = exprid(update);
Expr *left_check = expr_new(EXPR_SUBSCRIPT, default_span);
left_check->subscript_expr.expr = exprid(expr_variable(left_var));
left_check->subscript_expr.index.expr = exprid(expr_variable(index));
left_check->subscript_expr.no_check = true;
Expr *right_check = expr_new(EXPR_SUBSCRIPT, default_span);
right_check->subscript_expr.expr = exprid(expr_variable(right_var));
right_check->subscript_expr.index.expr = exprid(expr_variable(index));
right_check->subscript_expr.no_check = true;
Expr *expr_comparison = expr_new_binary(default_span, left_check, right_check, BINARYOP_NE);
Ast *ast_then = ast_new(AST_RETURN_STMT, default_span);
ast_then->return_stmt.expr = expr_new_const_bool(default_span, type_bool, false);
Ast *ast_if = ast_new(AST_IF_STMT, default_span);
ast_if->if_stmt = (AstIfStmt) {
.cond = exprid(expr_new_cond(expr_comparison)),
.then_body = astid(ast_then),
};
ast->for_stmt.body = astid(ast_if);
current->next = astid(ast);
current = ast;
Ast *ast_after = ast_new(AST_RETURN_STMT, default_span);
ast_after->return_stmt.expr = expr_new_const_bool(default_span, type_bool, true);
current->next = astid(ast_after);
return sema_rewrite_expr_as_macro_block(context, expr, dummy.next);
}
/**
* Analyze a == b, a != b, a > b, a < b, a >= b, a <= b
* @return
@@ -8311,12 +8482,6 @@ NEXT:
{
RETURN_SEMA_ERROR(expr, "Both sides are untyped and cannot be compared. Please cast one or both sides to a type, e.g. (Foo){ 1, 2 } == { 1, 2 }.");
}
if (!type_is_comparable(max))
{
CHECK_ON_DEFINED(failed_ref);
RETURN_SEMA_ERROR(expr, "%s does not support comparisons.",
type_quoted_error_string(left->type));
}
if (!is_equality_type_op)
{
@@ -8327,6 +8492,7 @@ NEXT:
"cannot be ordered, did you make a mistake?",
type_quoted_error_string(left->type));
}
if (type_is_pointer_type(max))
{
@@ -8344,6 +8510,26 @@ NEXT:
bool success = cast_explicit_checkable(context, left, max, failed_ref) && cast_explicit_checkable(context, right, max, failed_ref);
ASSERT_SPAN(expr, success);
if (!type_is_comparable(max))
{
if (is_equality_type_op && (max->type_kind == TYPE_SLICE || max->type_kind == TYPE_ARRAY))
{
Type *base = max->array.base;
switch (sema_type_has_equality_overload(context, base))
{
case BOOL_ERR:
return false;
case BOOL_FALSE:
break;
case BOOL_TRUE:
return sema_rewrite_slice_comparison(context, expr, left, right, max);
}
}
CHECK_ON_DEFINED(failed_ref);
RETURN_SEMA_ERROR(expr, "%s does not support comparisons.",
type_quoted_error_string(left->type));
}
DONE:

View File

@@ -100,6 +100,7 @@ bool sema_analyse_ct_expr(SemaContext *context, Expr *expr);
Decl *sema_find_typed_operator(SemaContext *context, OperatorOverload operator_overload, SourceSpan span, Expr *lhs, Expr *rhs, bool *reverse);
OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overload_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref);
BoolErr sema_type_has_equality_overload(SemaContext *context, Type *type);
BoolErr sema_type_can_check_equality_with_overload(SemaContext *context, Type *type);
Decl *sema_find_untyped_operator(Type *type, OperatorOverload operator_overload, Decl *skipped);
bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *method_decl, Expr *parent, Expr **arguments, bool reverse_overload);
bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr);
@@ -136,6 +137,24 @@ bool sema_expr_analyse_ct_concat(SemaContext *context, Expr *concat_expr, Expr *
bool sema_analyse_const_enum_constant_val(SemaContext *context, Decl *decl);
bool sema_analyse_attributes(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, bool *erase_decl);
INLINE bool sema_analyse_stmt_chain(SemaContext *context, Ast *statement)
{
if (!ast_ok(statement)) return false;
AstId current = astid(statement);
Ast *ast = NULL;
bool all_ok = true;
while (current)
{
ast = ast_next(&current);
if (!sema_analyse_statement(context, ast))
{
ast_poison(ast);
all_ok = false;
}
}
return all_ok;
}
INLINE bool sema_analyse_func_macro(SemaContext *context, Decl *decl, AttributeDomain domain, bool *erase_decl)
{
assert((domain & CALLABLE_TYPE) == domain);

View File

@@ -499,6 +499,7 @@ static inline bool sema_check_return_matches_opt_returns(SemaContext *context, E
static bool sema_analyse_macro_constant_ensures(SemaContext *context, Expr *ret_expr)
{
if (!context->current_macro) return true;
ASSERT(context->current_macro);
// This is a per return check, so we don't do it if the return expression is missing,
// or if it is optional, or obviously - if there are no '@ensure'.
@@ -1823,11 +1824,8 @@ SKIP_OVERLOAD:;
if (is_reverse)
{
// Create __idx$ > 0
cond = expr_new(EXPR_BINARY, idx_decl->span);
cond->binary_expr.operator = BINARYOP_GT;
cond->binary_expr.left = exprid(expr_variable(idx_decl));
Expr *rhs = expr_new_const_int(enumerator->span, index_type, 0);
cond->binary_expr.right = exprid(rhs);
cond = expr_new_binary(idx_decl->span, expr_variable(idx_decl), rhs, BINARYOP_GT);
// Create --__idx$
Expr *dec = expr_new(EXPR_UNARY, idx_decl->span);

View File

@@ -0,0 +1,202 @@
// #target: macos-x64
module test;
import std;
import std::core::env;
struct Foo
{
int a;
}
fn bool Foo.eq(self, Foo f) @operator(==)
{
return self.a == f.a;
}
fn Foo[2] get()
{
return { { 2 }, { 5 }};
}
fn void main()
{
Foo f = { 2 };
Foo h = { 2 };
Foo[] g = { f, h };
Foo[] z = { f, f };
bool test = g == z;
Foo[2] g2 = { f, h };
Foo[2] z2 = { f, f };
bool test2 = g2 == z2;
bool test3 = g2 == get();
}
/* #expect: test.ll
define void @test.main() #0 {
entry:
%f = alloca %Foo, align 4
%h = alloca %Foo, align 4
%g = alloca %"Foo[]", align 8
%literal = alloca [2 x %Foo], align 4
%z = alloca %"Foo[]", align 8
%literal1 = alloca [2 x %Foo], align 4
%test = alloca i8, align 1
%blockret = alloca i8, align 1
%.anon = alloca i64, align 8
%g2 = alloca [2 x %Foo], align 4
%z2 = alloca [2 x %Foo], align 4
%test2 = alloca i8, align 1
%blockret9 = alloca i8, align 1
%.anon10 = alloca i64, align 8
%test3 = alloca i8, align 1
%blockret21 = alloca i8, align 1
%.anon22 = alloca [2 x %Foo], align 4
%result = alloca [2 x %Foo], align 4
%.anon23 = alloca i64, align 8
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %f, ptr align 4 @.__const.1, i32 4, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %h, ptr align 4 @.__const.2, i32 4, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal, ptr align 4 %f, i32 4, i1 false)
%ptradd = getelementptr inbounds i8, ptr %literal, i64 4
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %ptradd, ptr align 4 %h, i32 4, i1 false)
%0 = insertvalue %"Foo[]" undef, ptr %literal, 0
%1 = insertvalue %"Foo[]" %0, i64 2, 1
store %"Foo[]" %1, ptr %g, align 8
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %literal1, ptr align 4 %f, i32 4, i1 false)
%ptradd2 = getelementptr inbounds i8, ptr %literal1, i64 4
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %ptradd2, ptr align 4 %f, i32 4, i1 false)
%2 = insertvalue %"Foo[]" undef, ptr %literal1, 0
%3 = insertvalue %"Foo[]" %2, i64 2, 1
store %"Foo[]" %3, ptr %z, align 8
%ptradd3 = getelementptr inbounds i8, ptr %g, i64 8
%4 = load i64, ptr %ptradd3, align 8
%ptradd4 = getelementptr inbounds i8, ptr %z, i64 8
%5 = load i64, ptr %ptradd4, align 8
%neq = icmp ne i64 %4, %5
br i1 %neq, label %if.then, label %if.exit
if.then: ; preds = %entry
store i8 0, ptr %blockret, align 1
br label %expr_block.exit
if.exit: ; preds = %entry
store i64 0, ptr %.anon, align 8
br label %loop.cond
loop.cond: ; preds = %if.exit6, %if.exit
%6 = load i64, ptr %.anon, align 8
%lt = icmp ult i64 %6, %4
br i1 %lt, label %loop.body, label %loop.exit
loop.body: ; preds = %loop.cond
%7 = load ptr, ptr %g, align 8
%8 = load i64, ptr %.anon, align 8
%ptroffset = getelementptr inbounds [4 x i8], ptr %7, i64 %8
%9 = load ptr, ptr %z, align 8
%10 = load i64, ptr %.anon, align 8
%ptroffset5 = getelementptr inbounds [4 x i8], ptr %9, i64 %10
%11 = load i32, ptr %ptroffset, align 4
%12 = load i32, ptr %ptroffset5, align 4
%13 = call i8 @test.Foo.eq(i32 %11, i32 %12)
%14 = trunc i8 %13 to i1
br i1 %14, label %if.exit6, label %if.else
if.else: ; preds = %loop.body
store i8 0, ptr %blockret, align 1
br label %expr_block.exit
if.exit6: ; preds = %loop.body
%15 = load i64, ptr %.anon, align 8
%addnuw = add nuw i64 %15, 1
store i64 %addnuw, ptr %.anon, align 8
br label %loop.cond
loop.exit: ; preds = %loop.cond
store i8 1, ptr %blockret, align 1
br label %expr_block.exit
expr_block.exit: ; preds = %loop.exit, %if.else, %if.then
%16 = load i8, ptr %blockret, align 1
store i8 %16, ptr %test, align 1
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %g2, ptr align 4 %f, i32 4, i1 false)
%ptradd7 = getelementptr inbounds i8, ptr %g2, i64 4
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %ptradd7, ptr align 4 %h, i32 4, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %z2, ptr align 4 %f, i32 4, i1 false)
%ptradd8 = getelementptr inbounds i8, ptr %z2, i64 4
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %ptradd8, ptr align 4 %f, i32 4, i1 false)
store i64 0, ptr %.anon10, align 8
br label %loop.cond11
loop.cond11: ; preds = %if.exit17, %expr_block.exit
%17 = load i64, ptr %.anon10, align 8
%lt12 = icmp ult i64 %17, 2
br i1 %lt12, label %loop.body13, label %loop.exit19
loop.body13: ; preds = %loop.cond11
%18 = load i64, ptr %.anon10, align 8
%ptroffset14 = getelementptr inbounds [4 x i8], ptr %g2, i64 %18
%19 = load i64, ptr %.anon10, align 8
%ptroffset15 = getelementptr inbounds [4 x i8], ptr %z2, i64 %19
%20 = load i32, ptr %ptroffset14, align 4
%21 = load i32, ptr %ptroffset15, align 4
%22 = call i8 @test.Foo.eq(i32 %20, i32 %21)
%23 = trunc i8 %22 to i1
br i1 %23, label %if.exit17, label %if.else16
if.else16: ; preds = %loop.body13
store i8 0, ptr %blockret9, align 1
br label %expr_block.exit20
if.exit17: ; preds = %loop.body13
%24 = load i64, ptr %.anon10, align 8
%addnuw18 = add nuw i64 %24, 1
store i64 %addnuw18, ptr %.anon10, align 8
br label %loop.cond11
loop.exit19: ; preds = %loop.cond11
store i8 1, ptr %blockret9, align 1
br label %expr_block.exit20
expr_block.exit20: ; preds = %loop.exit19, %if.else16
%25 = load i8, ptr %blockret9, align 1
store i8 %25, ptr %test2, align 1
%26 = call i64 @test.get()
store i64 %26, ptr %result, align 4
call void @llvm.memcpy.p0.p0.i32(ptr align 4 %.anon22, ptr align 4 %result, i32 8, i1 false)
store i64 0, ptr %.anon23, align 8
br label %loop.cond24
loop.cond24: ; preds = %if.exit30, %expr_block.exit20
%27 = load i64, ptr %.anon23, align 8
%lt25 = icmp ult i64 %27, 2
br i1 %lt25, label %loop.body26, label %loop.exit32
loop.body26: ; preds = %loop.cond24
%28 = load i64, ptr %.anon23, align 8
%ptroffset27 = getelementptr inbounds [4 x i8], ptr %g2, i64 %28
%29 = load i64, ptr %.anon23, align 8
%ptroffset28 = getelementptr inbounds [4 x i8], ptr %.anon22, i64 %29
%30 = load i32, ptr %ptroffset27, align 4
%31 = load i32, ptr %ptroffset28, align 4
%32 = call i8 @test.Foo.eq(i32 %30, i32 %31)
%33 = trunc i8 %32 to i1
br i1 %33, label %if.exit30, label %if.else29
if.else29: ; preds = %loop.body26
store i8 0, ptr %blockret21, align 1
br label %expr_block.exit33
if.exit30: ; preds = %loop.body26
%34 = load i64, ptr %.anon23, align 8
%addnuw31 = add nuw i64 %34, 1
store i64 %addnuw31, ptr %.anon23, align 8
br label %loop.cond24
loop.exit32: ; preds = %loop.cond24
store i8 1, ptr %blockret21, align 1
br label %expr_block.exit33
expr_block.exit33: ; preds = %loop.exit32, %if.else29
%35 = load i8, ptr %blockret21, align 1
store i8 %35, ptr %test3, align 1
ret void
}