diff --git a/releasenotes.md b/releasenotes.md index ce31ea4ee..76d8d532b 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -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 diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 25609c55c..b25e90bdf 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -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); diff --git a/src/compiler/expr.c b/src/compiler/expr.c index f09f8b67f..9c404fc20 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -3,6 +3,7 @@ // a copy of which can be found in the LICENSE file. #include +#include #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) { diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index ad1bcd551..ade14fe1f 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -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) diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 2e2ee933c..5a14be1d1 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -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; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 29a6a2ffb..87761102f 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -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)); - return true; + case BOOL_ERR: + return false; + case BOOL_TRUE: + expr_rewrite_const_bool(expr, type_bool, true); + return true; + case BOOL_FALSE: + expr_rewrite_const_bool(expr, type_bool, false); + 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; - } - 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(¤t, left); + Decl *right_var = right->expr_kind == EXPR_IDENTIFIER ? right->ident_expr : ast_append_generated_local(¤t, 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(¤t, 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(¤t, 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: diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index b0909330e..a286b0fe8 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -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(¤t); + 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); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 545fd003a..58364a790 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -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); diff --git a/test/test_suite/expressions/overloaded_slice_comparison.c3t b/test/test_suite/expressions/overloaded_slice_comparison.c3t new file mode 100644 index 000000000..e97326a69 --- /dev/null +++ b/test/test_suite/expressions/overloaded_slice_comparison.c3t @@ -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 +}