From 9ca9a2c5540683a54bae597c59152d06d095beef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 27 Nov 2015 10:52:31 -0700 Subject: [PATCH] allow empty function and return with no expression --- README.md | 6 ++-- src/codegen.cpp | 87 +++++++++++++++++++++++++++++++++++++++++---- src/parser.cpp | 30 ++++++++-------- test/standalone.cpp | 7 +++- 4 files changed, 107 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f735a21402..ee230e1b14 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ readable, safe, optimal, and concise code to solve any computing problem. ## Roadmap - * empty function and return with no expression + * pub/private/export functions + * make sure that release mode optimizes out empty private functions + * test framework to test for compile errors * Simple .so library * Multiple files * figure out integers @@ -87,7 +89,7 @@ Statement : ExpressionStatement | ReturnStatement ExpressionStatement : Expression token(Semicolon) -ReturnStatement : token(Return) Expression token(Semicolon) +ReturnStatement : token(Return) option(Expression) token(Semicolon) Expression : token(Number) | token(String) | token(Unreachable) | FnCall diff --git a/src/codegen.cpp b/src/codegen.cpp index 0cf60fe66b..6e9c590a2e 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -80,9 +80,14 @@ struct TypeNode { TypeTableEntry *entry; }; +struct FnDefNode { + bool add_implicit_return; +}; + struct CodeGenNode { union { TypeNode type_node; // for NodeTypeType + FnDefNode fn_def_node; // for NodeTypeFnDef } data; }; @@ -275,6 +280,60 @@ static void find_declarations(CodeGen *g, AstNode *node) { } } +static void check_fn_def_control_flow(CodeGen *g, AstNode *node) { + // Follow the execution flow and make sure the code returns appropriately. + // * A `return` statement in an unreachable type function should be an error. + // * Control flow should not be able to reach the end of an unreachable type function. + // * Functions that have a type other than void should not return without a value. + // * void functions without explicit return statements at the end need the + // add_implicit_return flag set on the codegen node. + assert(node->type == NodeTypeFnDef); + AstNode *proto_node = node->data.fn_def.fn_proto; + assert(proto_node->type == NodeTypeFnProto); + AstNode *return_type_node = proto_node->data.fn_proto.return_type; + assert(return_type_node->type == NodeTypeType); + + node->codegen_node = allocate(1); + FnDefNode *codegen_fn_def = &node->codegen_node->data.fn_def_node; + + assert(return_type_node->codegen_node); + TypeTableEntry *type_entry = return_type_node->codegen_node->data.type_node.entry; + assert(type_entry); + TypeId type_id = type_entry->id; + + AstNode *body_node = node->data.fn_def.body; + assert(body_node->type == NodeTypeBlock); + + // TODO once we understand types, do this pass after type checking, and + // if an expression has an unreachable value then stop looking at statements after + // it. then we can remove the check to `unreachable` in the end of this function. + bool prev_statement_return = false; + for (int i = 0; i < body_node->data.block.statements.length; i += 1) { + AstNode *statement_node = body_node->data.block.statements.at(i); + if (statement_node->type == NodeTypeStatementReturn) { + if (type_id == TypeIdUnreachable) { + add_node_error(g, statement_node, + buf_sprintf("return statement in function with unreachable return type")); + return; + } else { + prev_statement_return = true; + } + } else if (prev_statement_return) { + add_node_error(g, statement_node, + buf_sprintf("unreachable code")); + } + } + + if (!prev_statement_return) { + if (type_id == TypeIdVoid) { + codegen_fn_def->add_implicit_return = true; + } else if (type_id != TypeIdUnreachable) { + add_node_error(g, node, + buf_sprintf("control reaches end of non-void function")); + } + } +} + static void analyze_node(CodeGen *g, AstNode *node) { switch (node->type) { case NodeTypeRoot: @@ -299,6 +358,8 @@ static void analyze_node(CodeGen *g, AstNode *node) { AstNode *proto_node = node->data.fn_def.fn_proto; assert(proto_node->type == NodeTypeFnProto); analyze_node(g, proto_node); + + check_fn_def_control_flow(g, node); break; } case NodeTypeFnDecl: @@ -331,7 +392,9 @@ static void analyze_node(CodeGen *g, AstNode *node) { } break; case NodeTypeStatementReturn: - analyze_node(g, node->data.statement_return.expression); + if (node->data.statement_return.expression) { + analyze_node(g, node->data.statement_return.expression); + } break; case NodeTypeExpression: switch (node->data.expression.type) { @@ -545,7 +608,7 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *expr_node) { zig_unreachable(); } -static void gen_block(CodeGen *g, AstNode *block_node) { +static void gen_block(CodeGen *g, AstNode *block_node, bool add_implicit_return) { assert(block_node->type == NodeTypeBlock); llvm::DILexicalBlock *di_block = g->dbuilder->createLexicalBlock(g->block_scopes.last(), @@ -558,10 +621,15 @@ static void gen_block(CodeGen *g, AstNode *block_node) { case NodeTypeStatementReturn: { AstNode *expr_node = statement_node->data.statement_return.expression; - LLVMValueRef value = gen_expr(g, expr_node); + if (expr_node) { + LLVMValueRef value = gen_expr(g, expr_node); - add_debug_source_node(g, statement_node); - LLVMBuildRet(g->builder, value); + add_debug_source_node(g, statement_node); + LLVMBuildRet(g->builder, value); + } else { + add_debug_source_node(g, statement_node); + LLVMBuildRetVoid(g->builder); + } break; } case NodeTypeExpression: @@ -583,6 +651,10 @@ static void gen_block(CodeGen *g, AstNode *block_node) { } } + if (add_implicit_return) { + LLVMBuildRetVoid(g->builder); + } + g->block_scopes.pop(); } @@ -685,7 +757,10 @@ void code_gen(CodeGen *g) { LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn, "entry"); LLVMPositionBuilderAtEnd(g->builder, entry_block); - gen_block(g, fn_def_node->data.fn_def.body); + CodeGenNode *codegen_node = fn_def_node->codegen_node; + assert(codegen_node); + bool add_implicit_return = codegen_node->data.fn_def_node.add_implicit_return; + gen_block(g, fn_def_node->data.fn_def.body, add_implicit_return); g->block_scopes.pop(); } diff --git a/src/parser.cpp b/src/parser.cpp index 5a640690de..4b965e1fbf 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -128,7 +128,8 @@ void ast_print(AstNode *node, int indent) { break; case NodeTypeStatementReturn: fprintf(stderr, "ReturnStatement\n"); - ast_print(node->data.statement_return.expression, indent + 2); + if (node->data.statement_return.expression) + ast_print(node->data.statement_return.expression, indent + 2); break; case NodeTypeExternBlock: { @@ -258,7 +259,7 @@ void ast_invalid_token_error(ParseContext *pc, Token *token) { ast_error(token, "invalid token: '%s'", buf_ptr(&token_value)); } -static AstNode *ast_parse_expression(ParseContext *pc, int token_index, int *new_token_index); +static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool mandatory); static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) { @@ -374,7 +375,7 @@ static void ast_parse_fn_call_param_list(ParseContext *pc, int token_index, int } for (;;) { - AstNode *expr = ast_parse_expression(pc, token_index, &token_index); + AstNode *expr = ast_parse_expression(pc, &token_index, true); params->append(expr); Token *token = &pc->tokens->at(token_index); @@ -411,28 +412,29 @@ static AstNode *ast_parse_fn_call(ParseContext *pc, int token_index, int *new_to /* Expression : token(Number) | token(String) | token(Unreachable) | FnCall */ -static AstNode *ast_parse_expression(ParseContext *pc, int token_index, int *new_token_index) { - Token *token = &pc->tokens->at(token_index); +static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool mandatory) { + Token *token = &pc->tokens->at(*token_index); AstNode *node = ast_create_node(NodeTypeExpression, token); if (token->id == TokenIdKeywordUnreachable) { node->data.expression.type = AstNodeExpressionTypeUnreachable; - token_index += 1; + *token_index += 1; } else if (token->id == TokenIdSymbol) { node->data.expression.type = AstNodeExpressionTypeFnCall; - node->data.expression.data.fn_call = ast_parse_fn_call(pc, token_index, &token_index); + node->data.expression.data.fn_call = ast_parse_fn_call(pc, *token_index, token_index); } else if (token->id == TokenIdNumberLiteral) { node->data.expression.type = AstNodeExpressionTypeNumber; ast_buf_from_token(pc, token, &node->data.expression.data.number); - token_index += 1; + *token_index += 1; } else if (token->id == TokenIdStringLiteral) { node->data.expression.type = AstNodeExpressionTypeString; parse_string_literal(pc, token, &node->data.expression.data.string); - token_index += 1; - } else { + *token_index += 1; + } else if (mandatory) { ast_invalid_token_error(pc, token); + } else { + return nullptr; } - *new_token_index = token_index; return node; } @@ -441,14 +443,14 @@ Statement : ExpressionStatement | ReturnStatement ; ExpressionStatement : Expression token(Semicolon) ; -ReturnStatement : token(Return) Expression token(Semicolon) ; +ReturnStatement : token(Return) option(Expression) token(Semicolon) ; */ static AstNode *ast_parse_statement(ParseContext *pc, int token_index, int *new_token_index) { Token *token = &pc->tokens->at(token_index); if (token->id == TokenIdKeywordReturn) { AstNode *node = ast_create_node(NodeTypeStatementReturn, token); token_index += 1; - node->data.statement_return.expression = ast_parse_expression(pc, token_index, &token_index); + node->data.statement_return.expression = ast_parse_expression(pc, &token_index, false); Token *semicolon = &pc->tokens->at(token_index); token_index += 1; @@ -460,7 +462,7 @@ static AstNode *ast_parse_statement(ParseContext *pc, int token_index, int *new_ token->id == TokenIdKeywordUnreachable || token->id == TokenIdNumberLiteral) { - AstNode *node = ast_parse_expression(pc, token_index, &token_index); + AstNode *node = ast_parse_expression(pc, &token_index, true); Token *semicolon = &pc->tokens->at(token_index); token_index += 1; diff --git a/test/standalone.cpp b/test/standalone.cpp index caeb926a06..a4848db127 100644 --- a/test/standalone.cpp +++ b/test/standalone.cpp @@ -66,7 +66,12 @@ static void add_all_test_cases(void) { fn exit(code: i32) -> unreachable; } + fn empty_function_1() {} + fn empty_function_2() { return; } + fn _start() -> unreachable { + empty_function_1(); + empty_function_2(); this_is_a_function(); } @@ -86,7 +91,7 @@ static void add_all_test_cases(void) { /** * multi line doc comment */ - fn another_function() -> i32 { return 0; } + fn another_function() {} /// this is a documentation comment /// doc comment line 2