From 22151a0a03c47ecfed9a08aab8c7828a0b4d720c Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 28 Aug 2024 10:41:59 +0200 Subject: [PATCH] Fix bug with `defer (catch err)` when used together with regular defer. --- releasenotes.md | 1 + src/compiler/llvm_codegen_expr.c | 5 +- src/compiler/llvm_codegen_internal.h | 2 + src/compiler/llvm_codegen_stmt.c | 12 +- test/test_suite/defer/defer_catch_mix.c3t | 384 ++++++++++++++++++++++ 5 files changed, 396 insertions(+), 8 deletions(-) create mode 100644 test/test_suite/defer/defer_catch_mix.c3t diff --git a/releasenotes.md b/releasenotes.md index 388b97bb4..fe38e4aa4 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -91,6 +91,7 @@ - Fixes to wasm nolibc in the standard library. - Fixed int128 div/mod. - Fix WASM memory init priority. +- Fix bug with `defer (catch err)` when used together with regular defer. ### Stdlib changes diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index ba15b62e9..b0b7eb93d 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -5,6 +5,7 @@ #include "llvm_codegen_internal.h" #include + static LLVMValueRef llvm_emit_coerce_alignment(GenContext *c, BEValue *be_value, LLVMTypeRef coerce_type, AlignSize target_alignment, AlignSize *resulting_alignment); static bool bitstruct_requires_bitswap(Decl *decl); static inline LLVMValueRef llvm_const_high_bitmask(GenContext *c, LLVMTypeRef type, int type_bits, int high_bits); @@ -4516,9 +4517,9 @@ static inline void llvm_emit_rethrow_expr(GenContext *c, BEValue *be_value, Expr // Ensure we are on a branch that is non-empty. if (llvm_emit_check_block_branch(c)) { - c->defer_error_var = error_var; + PUSH_DEFER_ERROR(error_var); llvm_emit_statement_chain(c, expr->rethrow_expr.cleanup); - c->defer_error_var = NULL; + POP_DEFER_ERROR(); BEValue value; llvm_value_set_address_abi_aligned(&value, error_var, type_anyfault); if (expr->rethrow_expr.in_block) diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index d05d813b7..158db96db 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -569,6 +569,8 @@ void llvm_emit_debug_local_var(GenContext *c, Decl *var); #define UWTABLE (compiler.build.arch_os_target == MACOS_AARCH64 ? 1 : 2) #define EMIT_LOC(c, x) do { if (c->debug.builder) llvm_emit_debug_location(c, x->span); } while (0) #define EMIT_SPAN(c, x) do { if (c->debug.builder) llvm_emit_debug_location(c, x); } while (0) +#define PUSH_DEFER_ERROR(val__) LLVMValueRef def_err__ = c->defer_error_var; c->defer_error_var = val__ +#define POP_DEFER_ERROR() c->defer_error_var = def_err__ LLVMAtomicOrdering llvm_atomic_ordering(Atomicity atomicity); diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index 30b68fc0d..e3cb5d7f9 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -225,9 +225,9 @@ static inline void llvm_emit_return(GenContext *c, Ast *ast) llvm_value_rvalue(c, &be_value); LLVMValueRef error_out = llvm_emit_alloca_aligned(c, type_anyfault, "reterr"); llvm_store_to_ptr(c, error_out, &be_value); - c->defer_error_var = error_out; + PUSH_DEFER_ERROR(error_out); llvm_emit_statement_chain(c, ast->return_stmt.cleanup_fail); - c->defer_error_var = NULL; + POP_DEFER_ERROR(); } llvm_emit_return_abi(c, NULL, &be_value); return; @@ -276,9 +276,9 @@ static inline void llvm_emit_return(GenContext *c, Ast *ast) if (error_return_block && LLVMGetFirstUse(LLVMBasicBlockAsValue(error_return_block))) { llvm_emit_block(c, error_return_block); - c->defer_error_var = error_out; + PUSH_DEFER_ERROR(error_out); llvm_emit_statement_chain(c, ast->return_stmt.cleanup_fail); - c->defer_error_var = NULL; + POP_DEFER_ERROR(); BEValue value; llvm_value_set_address_abi_aligned(&value, error_out, type_anyfault); llvm_emit_return_abi(c, NULL, &value); @@ -325,9 +325,9 @@ static inline void llvm_emit_block_exit_return(GenContext *c, Ast *ast) { llvm_emit_br(c, exit->block_return_exit); llvm_emit_block(c, err_cleanup_block); - c->defer_error_var = exit->block_error_var; + PUSH_DEFER_ERROR(exit->block_error_var); llvm_emit_statement_chain(c, err_cleanup); - c->defer_error_var = NULL; + POP_DEFER_ERROR(); llvm_emit_jmp(c, exit->block_optional_exit); } else diff --git a/test/test_suite/defer/defer_catch_mix.c3t b/test/test_suite/defer/defer_catch_mix.c3t new file mode 100644 index 000000000..9b7d27c86 --- /dev/null +++ b/test/test_suite/defer/defer_catch_mix.c3t @@ -0,0 +1,384 @@ +// #target: macos-aarch64 +module test; +import std::io; + +fn char[]! fileReader(String filename, char[] buffer) +{ + io::File! file = io::file::open(filename, "r"); + defer (catch foo) io::printn("something on fail with the file"); + defer io::printn("always close the file"); + + file.read(buffer)!; + return buffer; +} + +fn void! main() +{ + char[] !buffer = mem::new_array(char, 12); + buffer = fileReader("not_found.txt", buffer); + return; +} + +/* #expect: test.ll + +define i64 @test.fileReader(ptr %0, [2 x i64] %1, [2 x i64] %2) #0 { +entry: + %filename = alloca %"char[]", align 8 + %buffer = alloca %"char[]", align 8 + %file = alloca %File, align 8 + %file.f = alloca i64, align 8 + %retparam = alloca %File, align 8 + %taddr = alloca %"char[]", align 8 + %error_var = alloca i64, align 8 + %retparam4 = alloca i64, align 8 + %len = alloca i64, align 8 + %error_var8 = alloca i64, align 8 + %retparam10 = alloca i64, align 8 + %taddr11 = alloca %"char[]", align 8 + %error_var16 = alloca i64, align 8 + %error_var22 = alloca i64, align 8 + %foo = alloca i64, align 8 + %len28 = alloca i64, align 8 + %error_var29 = alloca i64, align 8 + %retparam31 = alloca i64, align 8 + %taddr32 = alloca %"char[]", align 8 + %error_var38 = alloca i64, align 8 + %error_var44 = alloca i64, align 8 + %reterr = alloca i64, align 8 + %len53 = alloca i64, align 8 + %error_var54 = alloca i64, align 8 + %retparam56 = alloca i64, align 8 + %taddr57 = alloca %"char[]", align 8 + %error_var63 = alloca i64, align 8 + %error_var69 = alloca i64, align 8 + store [2 x i64] %1, ptr %filename, align 8 + store [2 x i64] %2, ptr %buffer, align 8 + %3 = load [2 x i64], ptr %filename, align 8 + store %"char[]" { ptr @.str, i64 1 }, ptr %taddr, align 8 + %4 = load [2 x i64], ptr %taddr, align 8 + %5 = call i64 @std.io.file.open(ptr %retparam, [2 x i64] %3, [2 x i64] %4) + %not_err = icmp eq i64 %5, 0 + %6 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %6, label %after_check, label %assign_optional +assign_optional: ; preds = %entry + store i64 %5, ptr %file.f, align 8 + br label %after_assign +after_check: ; preds = %entry + call void @llvm.memcpy.p0.p0.i32(ptr align 8 %file, ptr align 8 %retparam, i32 8, i1 false) + store i64 0, ptr %file.f, align 8 + br label %after_assign +after_assign: ; preds = %after_check, %assign_optional + %optval = load i64, ptr %file.f, align 8 + %not_err1 = icmp eq i64 %optval, 0 + %7 = call i1 @llvm.expect.i1(i1 %not_err1, i1 true) + br i1 %7, label %after_check3, label %assign_optional2 +assign_optional2: ; preds = %after_assign + store i64 %optval, ptr %error_var, align 8 + br label %guard_block +after_check3: ; preds = %after_assign + %8 = load [2 x i64], ptr %buffer, align 8 + %9 = call i64 @std.io.File.read(ptr %retparam4, ptr %file, [2 x i64] %8) + %not_err5 = icmp eq i64 %9, 0 + %10 = call i1 @llvm.expect.i1(i1 %not_err5, i1 true) + br i1 %10, label %after_check7, label %assign_optional6 +assign_optional6: ; preds = %after_check3 + store i64 %9, ptr %error_var, align 8 + br label %guard_block +after_check7: ; preds = %after_check3 + br label %noerr_block52 +guard_block: ; preds = %assign_optional6, %assign_optional2 + %11 = call ptr @std.io.stdout() + store %"char[]" { ptr @.str.1, i64 21 }, ptr %taddr11, align 8 + %12 = load [2 x i64], ptr %taddr11, align 8 + %13 = call i64 @std.io.File.write(ptr %retparam10, ptr %11, [2 x i64] %12) + %not_err12 = icmp eq i64 %13, 0 + %14 = call i1 @llvm.expect.i1(i1 %not_err12, i1 true) + br i1 %14, label %after_check14, label %assign_optional13 +assign_optional13: ; preds = %guard_block + store i64 %13, ptr %error_var8, align 8 + br label %guard_block15 +after_check14: ; preds = %guard_block + br label %noerr_block +guard_block15: ; preds = %assign_optional13 + br label %voiderr +noerr_block: ; preds = %after_check14 + %15 = load i64, ptr %retparam10, align 8 + store i64 %15, ptr %len, align 8 + %16 = call i64 @std.io.File.write_byte(ptr %11, i8 10) + %not_err17 = icmp eq i64 %16, 0 + %17 = call i1 @llvm.expect.i1(i1 %not_err17, i1 true) + br i1 %17, label %after_check19, label %assign_optional18 +assign_optional18: ; preds = %noerr_block + store i64 %16, ptr %error_var16, align 8 + br label %guard_block20 +after_check19: ; preds = %noerr_block + br label %noerr_block21 +guard_block20: ; preds = %assign_optional18 + br label %voiderr +noerr_block21: ; preds = %after_check19 + %18 = call i64 @std.io.File.flush(ptr %11) + %not_err23 = icmp eq i64 %18, 0 + %19 = call i1 @llvm.expect.i1(i1 %not_err23, i1 true) + br i1 %19, label %after_check25, label %assign_optional24 +assign_optional24: ; preds = %noerr_block21 + store i64 %18, ptr %error_var22, align 8 + br label %guard_block26 +after_check25: ; preds = %noerr_block21 + br label %noerr_block27 +guard_block26: ; preds = %assign_optional24 + br label %voiderr +noerr_block27: ; preds = %after_check25 + %20 = load i64, ptr %len, align 8 + %add = add i64 %20, 1 + br label %voiderr +voiderr: ; preds = %noerr_block27, %guard_block26, %guard_block20, %guard_block15 + %21 = load i64, ptr %error_var, align 8 + store i64 %21, ptr %foo, align 8 + %22 = call ptr @std.io.stdout() + store %"char[]" { ptr @.str.2, i64 31 }, ptr %taddr32, align 8 + %23 = load [2 x i64], ptr %taddr32, align 8 + %24 = call i64 @std.io.File.write(ptr %retparam31, ptr %22, [2 x i64] %23) + %not_err33 = icmp eq i64 %24, 0 + %25 = call i1 @llvm.expect.i1(i1 %not_err33, i1 true) + br i1 %25, label %after_check35, label %assign_optional34 +assign_optional34: ; preds = %voiderr + store i64 %24, ptr %error_var29, align 8 + br label %guard_block36 +after_check35: ; preds = %voiderr + br label %noerr_block37 +guard_block36: ; preds = %assign_optional34 + br label %voiderr51 +noerr_block37: ; preds = %after_check35 + %26 = load i64, ptr %retparam31, align 8 + store i64 %26, ptr %len28, align 8 + %27 = call i64 @std.io.File.write_byte(ptr %22, i8 10) + %not_err39 = icmp eq i64 %27, 0 + %28 = call i1 @llvm.expect.i1(i1 %not_err39, i1 true) + br i1 %28, label %after_check41, label %assign_optional40 +assign_optional40: ; preds = %noerr_block37 + store i64 %27, ptr %error_var38, align 8 + br label %guard_block42 +after_check41: ; preds = %noerr_block37 + br label %noerr_block43 +guard_block42: ; preds = %assign_optional40 + br label %voiderr51 +noerr_block43: ; preds = %after_check41 + %29 = call i64 @std.io.File.flush(ptr %22) + %not_err45 = icmp eq i64 %29, 0 + %30 = call i1 @llvm.expect.i1(i1 %not_err45, i1 true) + br i1 %30, label %after_check47, label %assign_optional46 +assign_optional46: ; preds = %noerr_block43 + store i64 %29, ptr %error_var44, align 8 + br label %guard_block48 +after_check47: ; preds = %noerr_block43 + br label %noerr_block49 +guard_block48: ; preds = %assign_optional46 + br label %voiderr51 +noerr_block49: ; preds = %after_check47 + %31 = load i64, ptr %len28, align 8 + %add50 = add i64 %31, 1 + br label %voiderr51 +voiderr51: ; preds = %noerr_block49, %guard_block48, %guard_block42, %guard_block36 + %32 = load i64, ptr %error_var, align 8 + ret i64 %32 +noerr_block52: ; preds = %after_check7 + %33 = load %"char[]", ptr %buffer, align 8 + %34 = call ptr @std.io.stdout() + store %"char[]" { ptr @.str.3, i64 21 }, ptr %taddr57, align 8 + %35 = load [2 x i64], ptr %taddr57, align 8 + %36 = call i64 @std.io.File.write(ptr %retparam56, ptr %34, [2 x i64] %35) + %not_err58 = icmp eq i64 %36, 0 + %37 = call i1 @llvm.expect.i1(i1 %not_err58, i1 true) + br i1 %37, label %after_check60, label %assign_optional59 +assign_optional59: ; preds = %noerr_block52 + store i64 %36, ptr %error_var54, align 8 + br label %guard_block61 +after_check60: ; preds = %noerr_block52 + br label %noerr_block62 +guard_block61: ; preds = %assign_optional59 + br label %voiderr76 +noerr_block62: ; preds = %after_check60 + %38 = load i64, ptr %retparam56, align 8 + store i64 %38, ptr %len53, align 8 + %39 = call i64 @std.io.File.write_byte(ptr %34, i8 10) + %not_err64 = icmp eq i64 %39, 0 + %40 = call i1 @llvm.expect.i1(i1 %not_err64, i1 true) + br i1 %40, label %after_check66, label %assign_optional65 +assign_optional65: ; preds = %noerr_block62 + store i64 %39, ptr %error_var63, align 8 + br label %guard_block67 +after_check66: ; preds = %noerr_block62 + br label %noerr_block68 +guard_block67: ; preds = %assign_optional65 + br label %voiderr76 +noerr_block68: ; preds = %after_check66 + %41 = call i64 @std.io.File.flush(ptr %34) + %not_err70 = icmp eq i64 %41, 0 + %42 = call i1 @llvm.expect.i1(i1 %not_err70, i1 true) + br i1 %42, label %after_check72, label %assign_optional71 +assign_optional71: ; preds = %noerr_block68 + store i64 %41, ptr %error_var69, align 8 + br label %guard_block73 +after_check72: ; preds = %noerr_block68 + br label %noerr_block74 +guard_block73: ; preds = %assign_optional71 + br label %voiderr76 +noerr_block74: ; preds = %after_check72 + %43 = load i64, ptr %len53, align 8 + %add75 = add i64 %43, 1 + br label %voiderr76 +voiderr76: ; preds = %noerr_block74, %guard_block73, %guard_block67, %guard_block61 + store %"char[]" %33, ptr %0, align 8 + ret i64 0 +} +; Function Attrs: nounwind uwtable(sync) +define i64 @test.main() #0 { +entry: + %buffer = alloca %"char[]", align 8 + %buffer.f = alloca i64, align 8 + %allocator = alloca %any, align 8 + %error_var = alloca i64, align 8 + %allocator1 = alloca %any, align 8 + %allocator2 = alloca %any, align 8 + %.inlinecache = alloca ptr, align 8 + %.cachedtype = alloca ptr, align 8 + %taddr = alloca %"char[]", align 8 + %taddr4 = alloca %"char[]", align 8 + %taddr5 = alloca %"char[]", align 8 + %retparam = alloca ptr, align 8 + %taddr6 = alloca ptr, align 8 + %taddr7 = alloca %"char[]", align 8 + %taddr8 = alloca %"char[]", align 8 + %taddr9 = alloca %"char[]", align 8 + %varargslots = alloca [1 x %any], align 8 + %taddr10 = alloca %"any[]", align 8 + %retparam14 = alloca %"char[]", align 8 + %taddr15 = alloca %"char[]", align 8 + %reterr = alloca i64, align 8 + store ptr null, ptr %.cachedtype, align 8 + call void @llvm.memcpy.p0.p0.i32(ptr align 8 %allocator, ptr align 8 @std.core.mem.allocator.thread_allocator, i32 16, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 8 %allocator1, ptr align 8 %allocator, i32 16, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 8 %allocator2, ptr align 8 %allocator1, i32 16, i1 false) + br label %if.exit +if.exit: ; preds = %entry + %ptradd = getelementptr inbounds i8, ptr %allocator2, i64 8 + %0 = load i64, ptr %ptradd, align 8 + %1 = inttoptr i64 %0 to ptr + %type = load ptr, ptr %.cachedtype, align 8 + %2 = icmp eq ptr %1, %type + br i1 %2, label %cache_hit, label %cache_miss +cache_miss: ; preds = %if.exit + %ptradd3 = getelementptr inbounds i8, ptr %1, i64 16 + %3 = load ptr, ptr %ptradd3, align 8 + %4 = call ptr @.dyn_search(ptr %3, ptr @"$sel.acquire") + store ptr %4, ptr %.inlinecache, align 8 + store ptr %1, ptr %.cachedtype, align 8 + br label %5 +cache_hit: ; preds = %if.exit + %cache_hit_fn = load ptr, ptr %.inlinecache, align 8 + br label %5 +5: ; preds = %cache_hit, %cache_miss + %fn_phi = phi ptr [ %cache_hit_fn, %cache_hit ], [ %4, %cache_miss ] + %6 = icmp eq ptr %fn_phi, null + br i1 %6, label %missing_function, label %match +missing_function: ; preds = %5 + store %"char[]" { ptr @.panic_msg, i64 44 }, ptr %taddr, align 8 + %7 = load [2 x i64], ptr %taddr, align 8 + store %"char[]" { ptr @.file, i64 16 }, ptr %taddr4, align 8 + %8 = load [2 x i64], ptr %taddr4, align 8 + store %"char[]" { ptr @.func, i64 4 }, ptr %taddr5, align 8 + %9 = load [2 x i64], ptr %taddr5, align 8 + %10 = load ptr, ptr @std.core.builtin.panic, align 8 + call void %10([2 x i64] %7, [2 x i64] %8, [2 x i64] %9, i32 80) + unreachable +match: ; preds = %5 + %11 = load ptr, ptr %allocator2, align 8 + %12 = call i64 %fn_phi(ptr %retparam, ptr %11, i64 12, i32 1, i64 0) + %not_err = icmp eq i64 %12, 0 + %13 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %13, label %after_check, label %assign_optional +assign_optional: ; preds = %match + store i64 %12, ptr %error_var, align 8 + br label %panic_block +after_check: ; preds = %match + %14 = load ptr, ptr %retparam, align 8 + store ptr %14, ptr %taddr6, align 8 + %15 = load ptr, ptr %taddr6, align 8 + %16 = insertvalue %"char[]" undef, ptr %15, 0 + %17 = insertvalue %"char[]" %16, i64 12, 1 + br label %noerr_block +panic_block: ; preds = %assign_optional + %18 = insertvalue %any undef, ptr %error_var, 0 + %19 = insertvalue %any %18, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 + store %"char[]" { ptr @.panic_msg.4, i64 36 }, ptr %taddr7, align 8 + %20 = load [2 x i64], ptr %taddr7, align 8 + store %"char[]" { ptr @.file, i64 16 }, ptr %taddr8, align 8 + %21 = load [2 x i64], ptr %taddr8, align 8 + store %"char[]" { ptr @.func, i64 4 }, ptr %taddr9, align 8 + %22 = load [2 x i64], ptr %taddr9, align 8 + store %any %19, ptr %varargslots, align 8 + %23 = insertvalue %"any[]" undef, ptr %varargslots, 0 + %"$$temp" = insertvalue %"any[]" %23, i64 1, 1 + store %"any[]" %"$$temp", ptr %taddr10, align 8 + %24 = load [2 x i64], ptr %taddr10, align 8 + call void @std.core.builtin.panicf([2 x i64] %20, [2 x i64] %21, [2 x i64] %22, i32 244, [2 x i64] %24) + unreachable +noerr_block: ; preds = %after_check + store %"char[]" %17, ptr %buffer, align 8 + store i64 0, ptr %buffer.f, align 8 + %optval = load i64, ptr %buffer.f, align 8 + %not_err11 = icmp eq i64 %optval, 0 + %25 = call i1 @llvm.expect.i1(i1 %not_err11, i1 true) + br i1 %25, label %after_check13, label %assign_optional12 +assign_optional12: ; preds = %noerr_block + store i64 %optval, ptr %buffer.f, align 8 + br label %after_assign +after_check13: ; preds = %noerr_block + store %"char[]" { ptr @.str.5, i64 13 }, ptr %taddr15, align 8 + %26 = load [2 x i64], ptr %taddr15, align 8 + %27 = load [2 x i64], ptr %buffer, align 8 + %28 = call i64 @test.fileReader(ptr %retparam14, [2 x i64] %26, [2 x i64] %27) + %not_err16 = icmp eq i64 %28, 0 + %29 = call i1 @llvm.expect.i1(i1 %not_err16, i1 true) + br i1 %29, label %after_check18, label %assign_optional17 +assign_optional17: ; preds = %after_check13 + store i64 %28, ptr %buffer.f, align 8 + br label %after_assign +after_check18: ; preds = %after_check13 + call void @llvm.memcpy.p0.p0.i32(ptr align 8 %buffer, ptr align 8 %retparam14, i32 16, i1 false) + store i64 0, ptr %buffer.f, align 8 + br label %after_assign +after_assign: ; preds = %after_check18, %assign_optional17, %assign_optional12 + ret i64 0 +} +; Function Attrs: nounwind uwtable(sync) +define i32 @main(i32 %0, ptr %1) #0 { +entry: + %blockret = alloca i32, align 4 + %temp_err = alloca i64, align 8 + br label %testblock +testblock: ; preds = %entry + %2 = call i64 @test.main() + %not_err = icmp eq i64 %2, 0 + %3 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %3, label %after_check, label %assign_optional +assign_optional: ; preds = %testblock + store i64 %2, ptr %temp_err, align 8 + br label %end_block +after_check: ; preds = %testblock + store i64 0, ptr %temp_err, align 8 + br label %end_block +end_block: ; preds = %after_check, %assign_optional + %4 = load i64, ptr %temp_err, align 8 + %neq = icmp ne i64 %4, 0 + br i1 %neq, label %if.then, label %if.exit +if.then: ; preds = %end_block + store i32 1, ptr %blockret, align 4 + br label %expr_block.exit +if.exit: ; preds = %end_block + store i32 0, ptr %blockret, align 4 + br label %expr_block.exit +expr_block.exit: ; preds = %if.exit, %if.then + %5 = load i32, ptr %blockret, align 4 + ret i32 %5 +}