diff --git a/releasenotes.md b/releasenotes.md index c24e9f6af..5ff301bc2 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -87,6 +87,7 @@ - Lambdas now properly follow its attributes #2346. - Not setting android-ndk resulted in a "set ndk-path" error. - Lambda deduplication would be incorrect when generated at the global scope. +- Allow accessing parameters in a naked function, just disallow `return`, this fixes #1955. ### Stdlib changes - Improve contract for readline. #2280 diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index f1cae1ba4..2bd2a02ef 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1714,6 +1714,7 @@ typedef struct bool ensures : 1; bool pure : 1; bool in_no_eval : 1; + bool is_naked_fn : 1; bool ignore_deprecation : 1; SourceSpan in_if_resolution; Decl **opt_returns; diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index 8a0b41995..7df8fa776 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -10,7 +10,7 @@ static inline void llvm_emit_return_value(GenContext *context, LLVMValueRef valu static void llvm_expand_from_args(GenContext *c, Type *type, LLVMValueRef ref, unsigned *index, AlignSize alignment); static inline void llvm_process_parameter_value(GenContext *c, Decl *decl, ABIArgInfo *info, unsigned *index); static inline void llvm_emit_func_parameter(GenContext *context, Decl *decl, ABIArgInfo *abi_info, unsigned *index, unsigned real_index); -static inline void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *prototype, Signature *signature, Ast *body, Decl *decl); +static inline void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *prototype, Signature *signature, Ast *body, Decl *decl, bool is_naked); /** @@ -431,13 +431,13 @@ void llvm_emit_function_body(GenContext *c, Decl *decl) llvm_emit_body(c, decl->backend_ref, type_get_resolved_prototype(decl->type), - decl->func_decl.attr_naked ? NULL : &decl->func_decl.signature, - astptr(decl->func_decl.body), decl); + &decl->func_decl.signature, + astptr(decl->func_decl.body), decl, decl->func_decl.attr_naked); } void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *prototype, Signature *signature, Ast *body, - Decl *decl) + Decl *decl, bool is_naked) { ASSERT(prototype && function && body); // Signature is NULL if the function is naked. @@ -494,13 +494,10 @@ void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *pro } - if (signature) + // Generate LLVMValueRef's for all parameters, so we can use them as local vars in code + FOREACH_IDX(i, Decl *, param, signature->params) { - // Generate LLVMValueRef's for all parameters, so we can use them as local vars in code - FOREACH_IDX(i, Decl *, param, signature->params) - { - llvm_emit_func_parameter(c, param, prototype->abi_args[i], &arg, i); - } + llvm_emit_func_parameter(c, param, prototype->abi_args[i], &arg, i); } LLVMSetCurrentDebugLocation2(c->builder, NULL); @@ -516,7 +513,7 @@ void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *pro // Insert a return (and defer) if needed. if (c->current_block && !LLVMGetBasicBlockTerminator(c->current_block)) { - if (signature) + if (!is_naked) { llvm_emit_return_implicit(c); } diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 9dd66406d..0880c4c17 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -688,6 +688,10 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement // 1. We mark that the current scope ends with a jump. SET_JUMP_END(context, statement); + if (context->call_env.is_naked_fn) + { + RETURN_SEMA_ERROR(statement, "'return' is not allowed in '@naked' functions."); + } Type *expected_rtype = context->rtype; ASSERT(expected_rtype && "We should always have known type from a function return."); @@ -3309,6 +3313,7 @@ bool sema_analyse_function_body(SemaContext *context, Decl *func) context->original_module = NULL; context->call_env = (CallEnv) { .current_function = func, + .is_naked_fn = func->func_decl.attr_naked, .kind = CALL_ENV_FUNCTION, .pure = func->func_decl.signature.attrs.is_pure, .ignore_deprecation = func->allow_deprecated || (func->resolved_attributes && func->attrs_resolved && func->attrs_resolved->deprecated && func->resolved_attributes && func->attrs_resolved->deprecated) @@ -3372,7 +3377,7 @@ NEXT: ASSERT_SPAN(func,context->active_scope.depth == 1); if (!context->active_scope.end_jump.active) { - if (canonical_rtype != type_void) + if (canonical_rtype != type_void && !is_naked) { RETURN_SEMA_ERROR(func, "Missing return statement at the end of the function."); } diff --git a/test/test_suite/asm/naked_call_with_instructions.c3t b/test/test_suite/asm/naked_call_with_instructions.c3t new file mode 100644 index 000000000..96835c3cb --- /dev/null +++ b/test/test_suite/asm/naked_call_with_instructions.c3t @@ -0,0 +1,28 @@ +// #target: macos-x64 +module test; +import std; +fn void main() +{ + char[8] foo = {255, 0, 0, 0, 0, 0, 0, 0}; + io::printn(to_ulong(&foo)); +} + +fn ulong to_ulong(char[8]* buf) @naked @noinline +{ + asm + { + movq $rax, [buf]; + ret; + } + unreachable(); +} + +/* #expect: test.ll + +define i64 @test.to_ulong(ptr %0) #1 { +entry: + call void asm sideeffect alignstack "movq ($0), %rax\0Aret \0A", "r,~{rax},~{flags},~{dirflag},~{fspr}"(ptr %0) + unreachable +} + +attributes #1 = { naked noinline nounwind uwtable "no-trapping-math"="true" "stack-protector-buffer-size"="8" } diff --git a/test/test_suite/lambda/lambda_attributes.c3t b/test/test_suite/lambda/lambda_attributes.c3t index 0427d4378..a7072bfd8 100644 --- a/test/test_suite/lambda/lambda_attributes.c3t +++ b/test/test_suite/lambda/lambda_attributes.c3t @@ -3,14 +3,15 @@ module test; alias IntFn = fn int(); IntFn lambda @export = fn () => 1; -IntFn lambda_naked @export = fn () @naked => 2; +IntFn lambda_naked @export = fn () @naked { asm("ret"); }; /* #expect: test.ll ; Function Attrs: naked nounwind uwtable define internal i32 @"test.$global$lambda2"() #0 { entry: - ret i32 2 + call void asm sideeffect alignstack "ret", "~{dirflag},~{fpsr},~{flags}"() + unreachable } ; Function Attrs: nounwind uwtable