From 78ff1a4af5d8d630ae589ed46a7770ca17770a78 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 1 Jan 2025 00:45:42 +0100 Subject: [PATCH] Support experimental `@operator(construct)` operator overload. --- releasenotes.md | 1 + src/compiler/compiler_internal.h | 2 + src/compiler/enums.h | 1 + src/compiler/sema_decls.c | 65 +++- src/compiler/symtab.c | 2 + .../overloading/construct_operator.c3t | 353 ++++++++++++++++++ 6 files changed, 415 insertions(+), 9 deletions(-) create mode 100644 test/test_suite/overloading/construct_operator.c3t diff --git a/releasenotes.md b/releasenotes.md index 78dbbc124..84e480646 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -13,6 +13,7 @@ - Improved `add-project` to take arguments. - Improve error reporting when using type names as the function argument #1750. - Improve ordering of method registration to support adding methods to generic modules with method constraints #1746 +- Support experimental `@operator(construct)` operator overload. ### Fixes - Fix case trying to initialize a `char[*]*` from a String. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 2664f52d1..668c1278e 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -419,6 +419,7 @@ typedef struct VarDecl_ bool no_init : 1; bool no_alias : 1; bool bit_is_expr : 1; + bool is_self : 1; union { Expr *init_expr; @@ -1913,6 +1914,7 @@ extern const char *kw_at_pure; extern const char *kw_at_require; extern const char *kw_at_return; extern const char *kw_at_jump; +extern const char *kw_construct; extern const char *kw_in; extern const char *kw_inout; extern const char *kw_len; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 956e891f3..5c8a6c055 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -912,6 +912,7 @@ typedef enum OVERLOAD_ELEMENT_REF, OVERLOAD_ELEMENT_SET, OVERLOAD_LEN, + OVERLOAD_CONSTRUCT, } OperatorOverload; typedef enum diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 0eb2d4542..042393ee0 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -1162,6 +1162,7 @@ static inline bool sema_analyse_signature(SemaContext *context, Signature *sig, UNREACHABLE } param->var.type_info = type_info_id_new_base(inferred_type, param->span); + param->var.is_self = true; } // Check parameters @@ -1688,6 +1689,31 @@ Decl *sema_find_operator(SemaContext *context, Type *type, OperatorOverload oper return NULL; } +static inline bool sema_analyse_operator_construct(SemaContext *context, Decl *method) +{ + Signature *signature = &method->func_decl.signature; + Decl **params = signature->params; + uint32_t param_count = vec_size(params); + if (param_count && params[0]->var.is_self) + { + RETURN_SEMA_ERROR(method, "'construct' methods cannot have 'self' parameters."); + } + if (!signature->rtype) + { + RETURN_SEMA_ERROR(method, "A 'construct' macro method should always have an explicitly typed return value."); + } + Type *rtype = typeget(signature->rtype)->canonical; + Type *parent = typeget(method->func_decl.type_parent); + if (parent->canonical != rtype && type_get_ptr(parent->canonical) != rtype) + { + RETURN_SEMA_ERROR(type_infoptr(signature->rtype), + "The return type of a 'construct' method must be the method type, or a pointer to it." + " In this case %s or %s was expected.", + type_quoted_error_string(parent), type_quoted_error_string(type_get_ptr(parent))); + } + return true; +} + static inline bool sema_analyse_operator_element_at(SemaContext *context, Decl *method) { @@ -1741,6 +1767,8 @@ static bool sema_check_operator_method_validity(SemaContext *context, Decl *meth { switch (method->operator) { + case OVERLOAD_CONSTRUCT: + return sema_analyse_operator_construct(context, method); case OVERLOAD_ELEMENT_SET: return sema_analyse_operator_element_set(context, method); case OVERLOAD_ELEMENT_AT: @@ -1847,6 +1875,7 @@ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type // See if the operator has already been defined. OperatorOverload operator = method->operator; + if (operator == OVERLOAD_CONSTRUCT) return true; Decl *other = sema_find_operator(context, parent_type, operator); if (other != method) @@ -1913,6 +1942,7 @@ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type break; } return true; + case OVERLOAD_CONSTRUCT: default: UNREACHABLE } @@ -2311,13 +2341,19 @@ static inline bool sema_analyse_method(SemaContext *context, Decl *decl) Decl **params = decl->func_decl.signature.params; bool is_dynamic = decl->func_decl.attr_dynamic; + bool is_constructor = decl->operator == OVERLOAD_CONSTRUCT; + // Ensure it has at least one parameter. - if (!vec_size(params)) RETURN_SEMA_ERROR(decl, "A method must start with an argument of the type " - "it is a method of, e.g. 'fn void %s.%s(%s* self)'.", - type_to_error_string(par_type), decl->name, type_to_error_string(par_type)); + if (!vec_size(params) && !is_constructor) + { + RETURN_SEMA_ERROR(decl, "A method must start with an argument of the type " + "it is a method of, e.g. 'fn void %s.%s(%s* self)', " + "unless it is a 'construct' method,", + type_to_error_string(par_type), decl->name, type_to_error_string(par_type)); + } // Ensure that the first parameter is valid. - if (!sema_is_valid_method_param(context, params[0], par_type, is_dynamic)) return false; + if (!is_constructor && !sema_is_valid_method_param(context, params[0], par_type, is_dynamic)) return false; // Make dynamic checks. if (is_dynamic) @@ -2330,6 +2366,10 @@ static inline bool sema_analyse_method(SemaContext *context, Decl *decl) { RETURN_SEMA_ERROR(decl, "'any' may not implement '@dynamic' methods, only regular methods."); } + if (is_constructor) + { + RETURN_SEMA_ERROR(decl, "A 'construct' method may not be '@dynamic'."); + } // Retrieve the implemented method. Decl *implemented_method = sema_find_interface_for_method(context, par_type, decl); if (!decl_ok(implemented_method)) return false; @@ -2619,6 +2659,11 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_ { case EXPR_IDENTIFIER: if (expr->identifier_expr.path) goto FAILED_OP_TYPE; + if (expr->identifier_expr.ident == kw_construct) + { + decl->operator = OVERLOAD_CONSTRUCT; + break; + } if (expr->identifier_expr.ident != kw_len) goto FAILED_OP_TYPE; decl->operator = OVERLOAD_LEN; break; @@ -3579,16 +3624,18 @@ static bool sema_analyse_macro_method(SemaContext *context, Decl *decl) ASSERT0(parent_type_info->resolve_status == RESOLVE_DONE); Type *parent_type = parent_type_info->type->canonical; + bool is_constructor = decl->operator == OVERLOAD_CONSTRUCT; + // Check the first argument. - Decl *first_param = decl->func_decl.signature.params[0]; - if (!first_param) + Decl *first_param = is_constructor ? NULL : decl->func_decl.signature.params[0]; + if (!is_constructor && !first_param) { RETURN_SEMA_ERROR(decl, "The first parameter to this method must be of type '%s'.", type_to_error_string(parent_type)); - return false; } - if (!sema_is_valid_method_param(context, first_param, parent_type, false)) return false; - if (first_param->var.kind != VARDECL_PARAM_EXPR && first_param->var.kind != VARDECL_PARAM_CT && first_param->var.kind != VARDECL_PARAM_REF && first_param->var.kind != VARDECL_PARAM) + if (!is_constructor && !sema_is_valid_method_param(context, first_param, parent_type, false)) return false; + + if (!is_constructor && first_param->var.kind != VARDECL_PARAM_EXPR && first_param->var.kind != VARDECL_PARAM_CT && first_param->var.kind != VARDECL_PARAM_REF && first_param->var.kind != VARDECL_PARAM) { RETURN_SEMA_ERROR(first_param, "The first parameter must be a compile time, regular or ref (&) type."); } diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index a94aebe7b..10eed2a1e 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -47,6 +47,7 @@ const char *kw_at_pure; const char *kw_at_require; const char *kw_at_return; const char *kw_at_jump; +const char *kw_construct; const char *kw_in; const char *kw_inout; const char *kw_len; @@ -133,6 +134,7 @@ void symtab_init(uint32_t capacity) kw_IoError = KW_DEF("IoError"); type = TOKEN_IDENT; + kw_construct = KW_DEF("construct"); kw_in = KW_DEF("in"); kw_inout = KW_DEF("inout"); kw_libc = KW_DEF("libc"); diff --git a/test/test_suite/overloading/construct_operator.c3t b/test/test_suite/overloading/construct_operator.c3t new file mode 100644 index 000000000..6b08b0424 --- /dev/null +++ b/test/test_suite/overloading/construct_operator.c3t @@ -0,0 +1,353 @@ +// #target: macos-x64 +module test; +import std; +struct Foo +{ + int a; +} + +macro Foo Foo.baz(int y) @operator(construct) +{ + return { 2 + y } ; +} +fn Foo Foo.new(int x) @operator(construct) +{ + return { 23 }; +} + +fn int! x() { return 1; } +fn void main() +{ + io::printn(Foo.new(123)); + io::printn(Foo.baz(123)); +} +/* #expect: test.ll + + +define void @test.main() #0 { +entry: + %result = alloca %Foo, align 4 + %x = alloca %Foo, align 4 + %x1 = alloca %Foo, align 4 + %len = alloca i64, align 8 + %error_var = alloca i64, align 8 + %x2 = alloca %Foo, align 4 + %formatter = alloca %Formatter, align 8 + %taddr = alloca %any, align 8 + %value = alloca %Foo, align 4 + %total = alloca i64, align 8 + %error_var5 = alloca i64, align 8 + %retparam = alloca i64, align 8 + %error_var6 = alloca i64, align 8 + %varargslots = alloca [1 x %any], align 16 + %taddr7 = alloca %"char[]", align 8 + %retparam8 = alloca i64, align 8 + %error_var14 = alloca i64, align 8 + %varargslots15 = alloca [1 x %any], align 16 + %retparam16 = alloca i64, align 8 + %retparam23 = alloca i64, align 8 + %error_var30 = alloca i64, align 8 + %error_var36 = alloca i64, align 8 + %literal = alloca %Foo, align 4 + %x43 = alloca %Foo, align 4 + %x44 = alloca %Foo, align 4 + %len45 = alloca i64, align 8 + %error_var46 = alloca i64, align 8 + %x47 = alloca %Foo, align 4 + %formatter49 = alloca %Formatter, align 8 + %taddr50 = alloca %any, align 8 + %value51 = alloca %Foo, align 4 + %total53 = alloca i64, align 8 + %error_var54 = alloca i64, align 8 + %retparam55 = alloca i64, align 8 + %error_var61 = alloca i64, align 8 + %varargslots62 = alloca [1 x %any], align 16 + %taddr63 = alloca %"char[]", align 8 + %retparam64 = alloca i64, align 8 + %error_var71 = alloca i64, align 8 + %varargslots72 = alloca [1 x %any], align 16 + %retparam73 = alloca i64, align 8 + %retparam80 = alloca i64, align 8 + %error_var87 = alloca i64, align 8 + %error_var93 = alloca i64, align 8 + %0 = call i32 @test.Foo.new(i32 123) + store i32 %0, ptr %result, align 4 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x, ptr align 4 %result, i32 4, i1 false) + %1 = call ptr @std.io.stdout() + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x1, ptr align 4 %x, i32 4, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x2, ptr align 4 %x1, i32 4, i1 false) + call void @llvm.memset.p0.i64(ptr align 8 %formatter, i8 0, i64 48, i1 false) + %2 = insertvalue %any undef, ptr %1, 0 + %3 = insertvalue %any %2, i64 ptrtoint (ptr @"$ct.std.io.File" to i64), 1 + store %any %3, ptr %taddr, align 8 + call void @std.io.Formatter.init(ptr %formatter, ptr @std.io.out_putstream_fn, ptr %taddr) + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %value, ptr align 4 %x2, i32 4, i1 false) + %4 = call i64 @std.io.Formatter.print(ptr %retparam, ptr %formatter, ptr @.str, i64 2) + %not_err = icmp eq i64 %4, 0 + %5 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %5, label %after_check, label %assign_optional + +assign_optional: ; preds = %entry + store i64 %4, ptr %error_var5, align 8 + br label %guard_block + +after_check: ; preds = %entry + br label %noerr_block + +guard_block: ; preds = %assign_optional + %6 = load i64, ptr %error_var5, align 8 + store i64 %6, ptr %error_var, align 8 + br label %guard_block28 + +noerr_block: ; preds = %after_check + %7 = load i64, ptr %retparam, align 8 + store i64 %7, ptr %total, align 8 + %8 = load i64, ptr %total, align 8 + store %"char[]" { ptr @.str.2, i64 1 }, ptr %taddr7, align 8 + %9 = insertvalue %any undef, ptr %taddr7, 0 + %10 = insertvalue %any %9, i64 ptrtoint (ptr @"$ct.String" to i64), 1 + store %any %10, ptr %varargslots, align 16 + %11 = call i64 @std.io.Formatter.printf(ptr %retparam8, ptr %formatter, ptr @.str.1, i64 4, ptr %varargslots, i64 1) + %not_err9 = icmp eq i64 %11, 0 + %12 = call i1 @llvm.expect.i1(i1 %not_err9, i1 true) + br i1 %12, label %after_check11, label %assign_optional10 + +assign_optional10: ; preds = %noerr_block + store i64 %11, ptr %error_var6, align 8 + br label %guard_block12 + +after_check11: ; preds = %noerr_block + br label %noerr_block13 + +guard_block12: ; preds = %assign_optional10 + %13 = load i64, ptr %error_var6, align 8 + store i64 %13, ptr %error_var, align 8 + br label %guard_block28 + +noerr_block13: ; preds = %after_check11 + %14 = load i64, ptr %retparam8, align 8 + %add = add i64 %8, %14 + store i64 %add, ptr %total, align 8 + %15 = load i64, ptr %total, align 8 + %16 = insertvalue %any undef, ptr %value, 0 + %17 = insertvalue %any %16, i64 ptrtoint (ptr @"$ct.int" to i64), 1 + store %any %17, ptr %varargslots15, align 16 + %18 = call i64 @std.io.Formatter.printf(ptr %retparam16, ptr %formatter, ptr @.str.3, i64 2, ptr %varargslots15, i64 1) + %not_err17 = icmp eq i64 %18, 0 + %19 = call i1 @llvm.expect.i1(i1 %not_err17, i1 true) + br i1 %19, label %after_check19, label %assign_optional18 + +assign_optional18: ; preds = %noerr_block13 + store i64 %18, ptr %error_var14, align 8 + br label %guard_block20 + +after_check19: ; preds = %noerr_block13 + br label %noerr_block21 + +guard_block20: ; preds = %assign_optional18 + %20 = load i64, ptr %error_var14, align 8 + store i64 %20, ptr %error_var, align 8 + br label %guard_block28 + +noerr_block21: ; preds = %after_check19 + %21 = load i64, ptr %retparam16, align 8 + %add22 = add i64 %15, %21 + store i64 %add22, ptr %total, align 8 + %22 = load i64, ptr %total, align 8 + %23 = call i64 @std.io.Formatter.print(ptr %retparam23, ptr %formatter, ptr @.str.4, i64 2) + %not_err24 = icmp eq i64 %23, 0 + %24 = call i1 @llvm.expect.i1(i1 %not_err24, i1 true) + br i1 %24, label %after_check26, label %assign_optional25 + +assign_optional25: ; preds = %noerr_block21 + store i64 %23, ptr %error_var, align 8 + br label %guard_block28 + +after_check26: ; preds = %noerr_block21 + %25 = load i64, ptr %retparam23, align 8 + %add27 = add i64 %22, %25 + br label %noerr_block29 + +guard_block28: ; preds = %assign_optional25, %guard_block20, %guard_block12, %guard_block + br label %voiderr + +noerr_block29: ; preds = %after_check26 + store i64 %add27, ptr %len, align 8 + %26 = call i64 @std.io.File.write_byte(ptr %1, i8 zeroext 10) + %not_err31 = icmp eq i64 %26, 0 + %27 = call i1 @llvm.expect.i1(i1 %not_err31, i1 true) + br i1 %27, label %after_check33, label %assign_optional32 + +assign_optional32: ; preds = %noerr_block29 + store i64 %26, ptr %error_var30, align 8 + br label %guard_block34 + +after_check33: ; preds = %noerr_block29 + br label %noerr_block35 + +guard_block34: ; preds = %assign_optional32 + br label %voiderr + +noerr_block35: ; preds = %after_check33 + %28 = call i64 @std.io.File.flush(ptr %1) + %not_err37 = icmp eq i64 %28, 0 + %29 = call i1 @llvm.expect.i1(i1 %not_err37, i1 true) + br i1 %29, label %after_check39, label %assign_optional38 + +assign_optional38: ; preds = %noerr_block35 + store i64 %28, ptr %error_var36, align 8 + br label %guard_block40 + +after_check39: ; preds = %noerr_block35 + br label %noerr_block41 + +guard_block40: ; preds = %assign_optional38 + br label %voiderr + +noerr_block41: ; preds = %after_check39 + %30 = load i64, ptr %len, align 8 + %add42 = add i64 %30, 1 + br label %voiderr + +voiderr: ; preds = %noerr_block41, %guard_block40, %guard_block34, %guard_block28 + store i32 125, ptr %literal, align 4 + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x43, ptr align 4 %literal, i32 4, i1 false) + %31 = call ptr @std.io.stdout() + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x44, ptr align 4 %x43, i32 4, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %x47, ptr align 4 %x44, i32 4, i1 false) + call void @llvm.memset.p0.i64(ptr align 8 %formatter49, i8 0, i64 48, i1 false) + %32 = insertvalue %any undef, ptr %31, 0 + %33 = insertvalue %any %32, i64 ptrtoint (ptr @"$ct.std.io.File" to i64), 1 + store %any %33, ptr %taddr50, align 8 + call void @std.io.Formatter.init(ptr %formatter49, ptr @std.io.out_putstream_fn, ptr %taddr50) + call void @llvm.memcpy.p0.p0.i32(ptr align 4 %value51, ptr align 4 %x47, i32 4, i1 false) + %34 = call i64 @std.io.Formatter.print(ptr %retparam55, ptr %formatter49, ptr @.str.5, i64 2) + %not_err56 = icmp eq i64 %34, 0 + %35 = call i1 @llvm.expect.i1(i1 %not_err56, i1 true) + br i1 %35, label %after_check58, label %assign_optional57 + +assign_optional57: ; preds = %voiderr + store i64 %34, ptr %error_var54, align 8 + br label %guard_block59 + +after_check58: ; preds = %voiderr + br label %noerr_block60 + +guard_block59: ; preds = %assign_optional57 + %36 = load i64, ptr %error_var54, align 8 + store i64 %36, ptr %error_var46, align 8 + br label %guard_block85 + +noerr_block60: ; preds = %after_check58 + %37 = load i64, ptr %retparam55, align 8 + store i64 %37, ptr %total53, align 8 + %38 = load i64, ptr %total53, align 8 + store %"char[]" { ptr @.str.7, i64 1 }, ptr %taddr63, align 8 + %39 = insertvalue %any undef, ptr %taddr63, 0 + %40 = insertvalue %any %39, i64 ptrtoint (ptr @"$ct.String" to i64), 1 + store %any %40, ptr %varargslots62, align 16 + %41 = call i64 @std.io.Formatter.printf(ptr %retparam64, ptr %formatter49, ptr @.str.6, i64 4, ptr %varargslots62, i64 1) + %not_err65 = icmp eq i64 %41, 0 + %42 = call i1 @llvm.expect.i1(i1 %not_err65, i1 true) + br i1 %42, label %after_check67, label %assign_optional66 + +assign_optional66: ; preds = %noerr_block60 + store i64 %41, ptr %error_var61, align 8 + br label %guard_block68 + +after_check67: ; preds = %noerr_block60 + br label %noerr_block69 + +guard_block68: ; preds = %assign_optional66 + %43 = load i64, ptr %error_var61, align 8 + store i64 %43, ptr %error_var46, align 8 + br label %guard_block85 + +noerr_block69: ; preds = %after_check67 + %44 = load i64, ptr %retparam64, align 8 + %add70 = add i64 %38, %44 + store i64 %add70, ptr %total53, align 8 + %45 = load i64, ptr %total53, align 8 + %46 = insertvalue %any undef, ptr %value51, 0 + %47 = insertvalue %any %46, i64 ptrtoint (ptr @"$ct.int" to i64), 1 + store %any %47, ptr %varargslots72, align 16 + %48 = call i64 @std.io.Formatter.printf(ptr %retparam73, ptr %formatter49, ptr @.str.8, i64 2, ptr %varargslots72, i64 1) + %not_err74 = icmp eq i64 %48, 0 + %49 = call i1 @llvm.expect.i1(i1 %not_err74, i1 true) + br i1 %49, label %after_check76, label %assign_optional75 + +assign_optional75: ; preds = %noerr_block69 + store i64 %48, ptr %error_var71, align 8 + br label %guard_block77 + +after_check76: ; preds = %noerr_block69 + br label %noerr_block78 + +guard_block77: ; preds = %assign_optional75 + %50 = load i64, ptr %error_var71, align 8 + store i64 %50, ptr %error_var46, align 8 + br label %guard_block85 + +noerr_block78: ; preds = %after_check76 + %51 = load i64, ptr %retparam73, align 8 + %add79 = add i64 %45, %51 + store i64 %add79, ptr %total53, align 8 + %52 = load i64, ptr %total53, align 8 + %53 = call i64 @std.io.Formatter.print(ptr %retparam80, ptr %formatter49, ptr @.str.9, i64 2) + %not_err81 = icmp eq i64 %53, 0 + %54 = call i1 @llvm.expect.i1(i1 %not_err81, i1 true) + br i1 %54, label %after_check83, label %assign_optional82 + +assign_optional82: ; preds = %noerr_block78 + store i64 %53, ptr %error_var46, align 8 + br label %guard_block85 + +after_check83: ; preds = %noerr_block78 + %55 = load i64, ptr %retparam80, align 8 + %add84 = add i64 %52, %55 + br label %noerr_block86 + +guard_block85: ; preds = %assign_optional82, %guard_block77, %guard_block68, %guard_block59 + br label %voiderr100 + +noerr_block86: ; preds = %after_check83 + store i64 %add84, ptr %len45, align 8 + %56 = call i64 @std.io.File.write_byte(ptr %31, i8 zeroext 10) + %not_err88 = icmp eq i64 %56, 0 + %57 = call i1 @llvm.expect.i1(i1 %not_err88, i1 true) + br i1 %57, label %after_check90, label %assign_optional89 + +assign_optional89: ; preds = %noerr_block86 + store i64 %56, ptr %error_var87, align 8 + br label %guard_block91 + +after_check90: ; preds = %noerr_block86 + br label %noerr_block92 + +guard_block91: ; preds = %assign_optional89 + br label %voiderr100 + +noerr_block92: ; preds = %after_check90 + %58 = call i64 @std.io.File.flush(ptr %31) + %not_err94 = icmp eq i64 %58, 0 + %59 = call i1 @llvm.expect.i1(i1 %not_err94, i1 true) + br i1 %59, label %after_check96, label %assign_optional95 + +assign_optional95: ; preds = %noerr_block92 + store i64 %58, ptr %error_var93, align 8 + br label %guard_block97 + +after_check96: ; preds = %noerr_block92 + br label %noerr_block98 + +guard_block97: ; preds = %assign_optional95 + br label %voiderr100 + +noerr_block98: ; preds = %after_check96 + %60 = load i64, ptr %len45, align 8 + %add99 = add i64 %60, 1 + br label %voiderr100 + +voiderr100: ; preds = %noerr_block98, %guard_block97, %guard_block91, %guard_block85 + ret void +}