mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
add pub and export visibility modifiers and optimization
This commit is contained in:
parent
9ca9a2c554
commit
024052b448
@ -27,12 +27,11 @@ readable, safe, optimal, and concise code to solve any computing problem.
|
|||||||
* Source code is UTF-8.
|
* Source code is UTF-8.
|
||||||
* Shebang line OK so language can be used for "scripting" as well.
|
* Shebang line OK so language can be used for "scripting" as well.
|
||||||
* Ability to mark functions as test and automatically run them in test mode.
|
* Ability to mark functions as test and automatically run them in test mode.
|
||||||
|
This mode should automatically provide test coverage.
|
||||||
* Memory zeroed by default, unless you initialize with "uninitialized".
|
* Memory zeroed by default, unless you initialize with "uninitialized".
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
* pub/private/export functions
|
|
||||||
* make sure that release mode optimizes out empty private functions
|
|
||||||
* test framework to test for compile errors
|
* test framework to test for compile errors
|
||||||
* Simple .so library
|
* Simple .so library
|
||||||
* Multiple files
|
* Multiple files
|
||||||
@ -69,11 +68,13 @@ TopLevelDecl : FnDef | ExternBlock
|
|||||||
|
|
||||||
ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnDecl) token(RBrace)
|
ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnDecl) token(RBrace)
|
||||||
|
|
||||||
FnProto : token(Fn) token(Symbol) ParamDeclList option(token(Arrow) Type)
|
FnProto : many(Directive) option(FnVisibleMod) token(Fn) token(Symbol) ParamDeclList option(token(Arrow) Type)
|
||||||
|
|
||||||
|
FnVisibleMod : token(Pub) | token(Export)
|
||||||
|
|
||||||
FnDecl : FnProto token(Semicolon)
|
FnDecl : FnProto token(Semicolon)
|
||||||
|
|
||||||
FnDef : many(Directive) FnProto Block
|
FnDef : FnProto Block
|
||||||
|
|
||||||
ParamDeclList : token(LParen) list(ParamDecl, token(Comma)) token(RParen)
|
ParamDeclList : token(LParen) list(ParamDecl, token(Comma)) token(RParen)
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ if exists("b:current_syntax")
|
|||||||
finish
|
finish
|
||||||
endif
|
endif
|
||||||
|
|
||||||
syn keyword zigKeyword fn return mut const extern unreachable
|
syn keyword zigKeyword fn return mut const extern unreachable export pub
|
||||||
|
|
||||||
let b:current_syntax = "zig"
|
let b:current_syntax = "zig"
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ extern {
|
|||||||
fn exit(code: i32) -> unreachable;
|
fn exit(code: i32) -> unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _start() -> unreachable {
|
export fn _start() -> unreachable {
|
||||||
puts("Hello, world!");
|
puts("Hello, world!");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@ -26,6 +26,7 @@ struct FnTableEntry {
|
|||||||
AstNode *fn_def_node;
|
AstNode *fn_def_node;
|
||||||
bool is_extern;
|
bool is_extern;
|
||||||
bool internal_linkage;
|
bool internal_linkage;
|
||||||
|
unsigned calling_convention;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TypeId {
|
enum TypeId {
|
||||||
@ -51,7 +52,7 @@ struct TypeTableEntry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct CodeGen {
|
struct CodeGen {
|
||||||
LLVMModuleRef mod;
|
LLVMModuleRef module;
|
||||||
AstNode *root;
|
AstNode *root;
|
||||||
ZigList<ErrorMsg> errors;
|
ZigList<ErrorMsg> errors;
|
||||||
LLVMBuilderRef builder;
|
LLVMBuilderRef builder;
|
||||||
@ -228,6 +229,7 @@ static void find_declarations(CodeGen *g, AstNode *node) {
|
|||||||
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
||||||
fn_table_entry->proto_node = fn_proto;
|
fn_table_entry->proto_node = fn_proto;
|
||||||
fn_table_entry->is_extern = true;
|
fn_table_entry->is_extern = true;
|
||||||
|
fn_table_entry->calling_convention = LLVMCCallConv;
|
||||||
g->fn_table.put(name, fn_table_entry);
|
g->fn_table.put(name, fn_table_entry);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -244,6 +246,12 @@ static void find_declarations(CodeGen *g, AstNode *node) {
|
|||||||
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
||||||
fn_table_entry->proto_node = proto_node;
|
fn_table_entry->proto_node = proto_node;
|
||||||
fn_table_entry->fn_def_node = node;
|
fn_table_entry->fn_def_node = node;
|
||||||
|
fn_table_entry->internal_linkage = proto_node->data.fn_proto.visib_mod != FnProtoVisibModExport;
|
||||||
|
if (fn_table_entry->internal_linkage) {
|
||||||
|
fn_table_entry->calling_convention = LLVMFastCallConv;
|
||||||
|
} else {
|
||||||
|
fn_table_entry->calling_convention = LLVMCCallConv;
|
||||||
|
}
|
||||||
g->fn_table.put(proto_name, fn_table_entry);
|
g->fn_table.put(proto_name, fn_table_entry);
|
||||||
g->fn_defs.append(fn_table_entry);
|
g->fn_defs.append(fn_table_entry);
|
||||||
|
|
||||||
@ -512,12 +520,12 @@ void semantic_analyze(CodeGen *g) {
|
|||||||
g->target_data_ref = LLVMGetTargetMachineData(g->target_machine);
|
g->target_data_ref = LLVMGetTargetMachineData(g->target_machine);
|
||||||
|
|
||||||
|
|
||||||
g->mod = LLVMModuleCreateWithName("ZigModule");
|
g->module = LLVMModuleCreateWithName("ZigModule");
|
||||||
|
|
||||||
g->pointer_size_bytes = LLVMPointerSize(g->target_data_ref);
|
g->pointer_size_bytes = LLVMPointerSize(g->target_data_ref);
|
||||||
|
|
||||||
g->builder = LLVMCreateBuilder();
|
g->builder = LLVMCreateBuilder();
|
||||||
g->dbuilder = new llvm::DIBuilder(*llvm::unwrap(g->mod), true);
|
g->dbuilder = new llvm::DIBuilder(*llvm::unwrap(g->module), true);
|
||||||
|
|
||||||
|
|
||||||
add_types(g);
|
add_types(g);
|
||||||
@ -550,8 +558,8 @@ static LLVMValueRef gen_fn_call(CodeGen *g, AstNode *fn_call_node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_debug_source_node(g, fn_call_node);
|
add_debug_source_node(g, fn_call_node);
|
||||||
LLVMValueRef result = LLVMBuildCall(g->builder, fn_table_entry->fn_value,
|
LLVMValueRef result = LLVMZigBuildCall(g->builder, fn_table_entry->fn_value,
|
||||||
param_values, actual_param_count, "");
|
param_values, actual_param_count, fn_table_entry->calling_convention, "");
|
||||||
|
|
||||||
if (type_is_unreachable(fn_table_entry->proto_node->data.fn_proto.return_type)) {
|
if (type_is_unreachable(fn_table_entry->proto_node->data.fn_proto.return_type)) {
|
||||||
return LLVMBuildUnreachable(g->builder);
|
return LLVMBuildUnreachable(g->builder);
|
||||||
@ -566,7 +574,7 @@ static LLVMValueRef find_or_create_string(CodeGen *g, Buf *str) {
|
|||||||
return entry->value;
|
return entry->value;
|
||||||
}
|
}
|
||||||
LLVMValueRef text = LLVMConstString(buf_ptr(str), buf_len(str), false);
|
LLVMValueRef text = LLVMConstString(buf_ptr(str), buf_len(str), false);
|
||||||
LLVMValueRef global_value = LLVMAddGlobal(g->mod, LLVMTypeOf(text), "");
|
LLVMValueRef global_value = LLVMAddGlobal(g->module, LLVMTypeOf(text), "");
|
||||||
LLVMSetLinkage(global_value, LLVMPrivateLinkage);
|
LLVMSetLinkage(global_value, LLVMPrivateLinkage);
|
||||||
LLVMSetInitializer(global_value, text);
|
LLVMSetInitializer(global_value, text);
|
||||||
LLVMSetGlobalConstant(global_value, true);
|
LLVMSetGlobalConstant(global_value, true);
|
||||||
@ -615,6 +623,8 @@ static void gen_block(CodeGen *g, AstNode *block_node, bool add_implicit_return)
|
|||||||
g->di_file, block_node->line + 1, block_node->column + 1);
|
g->di_file, block_node->line + 1, block_node->column + 1);
|
||||||
g->block_scopes.append(di_block);
|
g->block_scopes.append(di_block);
|
||||||
|
|
||||||
|
add_debug_source_node(g, block_node);
|
||||||
|
|
||||||
for (int i = 0; i < block_node->data.block.statements.length; i += 1) {
|
for (int i = 0; i < block_node->data.block.statements.length; i += 1) {
|
||||||
AstNode *statement_node = block_node->data.block.statements.at(i);
|
AstNode *statement_node = block_node->data.block.statements.at(i);
|
||||||
switch (statement_node->type) {
|
switch (statement_node->type) {
|
||||||
@ -714,16 +724,15 @@ void code_gen(CodeGen *g) {
|
|||||||
param_types[param_decl_i] = to_llvm_type(type_node);
|
param_types[param_decl_i] = to_llvm_type(type_node);
|
||||||
}
|
}
|
||||||
LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, fn_proto->params.length, 0);
|
LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, fn_proto->params.length, 0);
|
||||||
LLVMValueRef fn = LLVMAddFunction(g->mod, buf_ptr(&fn_proto->name), function_type);
|
LLVMValueRef fn = LLVMAddFunction(g->module, buf_ptr(&fn_proto->name), function_type);
|
||||||
|
|
||||||
LLVMSetLinkage(fn, fn_table_entry->internal_linkage ? LLVMPrivateLinkage : LLVMExternalLinkage);
|
LLVMSetLinkage(fn, fn_table_entry->internal_linkage ? LLVMInternalLinkage : LLVMExternalLinkage);
|
||||||
|
|
||||||
if (type_is_unreachable(fn_proto->return_type)) {
|
if (type_is_unreachable(fn_proto->return_type)) {
|
||||||
LLVMAddFunctionAttr(fn, LLVMNoReturnAttribute);
|
LLVMAddFunctionAttr(fn, LLVMNoReturnAttribute);
|
||||||
}
|
}
|
||||||
if (fn_table_entry->is_extern) {
|
LLVMSetFunctionCallConv(fn, fn_table_entry->calling_convention);
|
||||||
LLVMSetFunctionCallConv(fn, LLVMCCallConv);
|
if (!fn_table_entry->is_extern) {
|
||||||
} else {
|
|
||||||
LLVMAddFunctionAttr(fn, LLVMNoUnwindAttribute);
|
LLVMAddFunctionAttr(fn, LLVMNoUnwindAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,10 +777,19 @@ void code_gen(CodeGen *g) {
|
|||||||
|
|
||||||
g->dbuilder->finalize();
|
g->dbuilder->finalize();
|
||||||
|
|
||||||
LLVMDumpModule(g->mod);
|
LLVMDumpModule(g->module);
|
||||||
|
|
||||||
|
// in release mode, we're sooooo confident that we've generated correct ir,
|
||||||
|
// that we skip the verify module step in order to get better performance.
|
||||||
|
#ifndef NDEBUG
|
||||||
char *error = nullptr;
|
char *error = nullptr;
|
||||||
LLVMVerifyModule(g->mod, LLVMAbortProcessAction, &error);
|
LLVMVerifyModule(g->module, LLVMAbortProcessAction, &error);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void code_gen_optimize(CodeGen *g) {
|
||||||
|
LLVMZigOptimizeModule(g->target_machine, g->module);
|
||||||
|
LLVMDumpModule(g->module);
|
||||||
}
|
}
|
||||||
|
|
||||||
ZigList<ErrorMsg> *codegen_error_messages(CodeGen *g) {
|
ZigList<ErrorMsg> *codegen_error_messages(CodeGen *g) {
|
||||||
@ -907,7 +925,9 @@ void code_gen_link(CodeGen *g, const char *out_file) {
|
|||||||
buf_append_str(&out_file_o, ".o");
|
buf_append_str(&out_file_o, ".o");
|
||||||
|
|
||||||
char *err_msg = nullptr;
|
char *err_msg = nullptr;
|
||||||
if (LLVMTargetMachineEmitToFile(g->target_machine, g->mod, buf_ptr(&out_file_o), LLVMObjectFile, &err_msg)) {
|
if (LLVMZigTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(&out_file_o),
|
||||||
|
LLVMObjectFile, &err_msg))
|
||||||
|
{
|
||||||
zig_panic("unable to write object file: %s", err_msg);
|
zig_panic("unable to write object file: %s", err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,8 @@ void codegen_set_strip(CodeGen *codegen, bool strip);
|
|||||||
|
|
||||||
void semantic_analyze(CodeGen *g);
|
void semantic_analyze(CodeGen *g);
|
||||||
|
|
||||||
|
void code_gen_optimize(CodeGen *g);
|
||||||
|
|
||||||
void code_gen(CodeGen *g);
|
void code_gen(CodeGen *g);
|
||||||
|
|
||||||
void code_gen_link(CodeGen *g, const char *out_file);
|
void code_gen_link(CodeGen *g, const char *out_file);
|
||||||
|
|||||||
@ -118,6 +118,12 @@ static int build(const char *arg0, const char *in_file, const char *out_file,
|
|||||||
fprintf(stderr, "------------------\n");
|
fprintf(stderr, "------------------\n");
|
||||||
code_gen(codegen);
|
code_gen(codegen);
|
||||||
|
|
||||||
|
if (release) {
|
||||||
|
fprintf(stderr, "\nOptimization:\n");
|
||||||
|
fprintf(stderr, "---------------\n");
|
||||||
|
code_gen_optimize(codegen);
|
||||||
|
}
|
||||||
|
|
||||||
fprintf(stderr, "\nLink:\n");
|
fprintf(stderr, "\nLink:\n");
|
||||||
fprintf(stderr, "-------\n");
|
fprintf(stderr, "-------\n");
|
||||||
code_gen_link(codegen, out_file);
|
code_gen_link(codegen, out_file);
|
||||||
|
|||||||
236
src/parser.cpp
236
src/parser.cpp
@ -268,6 +268,53 @@ static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AstNode *ast_parse_directive(ParseContext *pc, int token_index, int *new_token_index) {
|
||||||
|
Token *number_sign = &pc->tokens->at(token_index);
|
||||||
|
token_index += 1;
|
||||||
|
ast_expect_token(pc, number_sign, TokenIdNumberSign);
|
||||||
|
|
||||||
|
AstNode *node = ast_create_node(NodeTypeDirective, number_sign);
|
||||||
|
|
||||||
|
Token *name_symbol = &pc->tokens->at(token_index);
|
||||||
|
token_index += 1;
|
||||||
|
ast_expect_token(pc, name_symbol, TokenIdSymbol);
|
||||||
|
|
||||||
|
ast_buf_from_token(pc, name_symbol, &node->data.directive.name);
|
||||||
|
|
||||||
|
Token *l_paren = &pc->tokens->at(token_index);
|
||||||
|
token_index += 1;
|
||||||
|
ast_expect_token(pc, l_paren, TokenIdLParen);
|
||||||
|
|
||||||
|
Token *param_str = &pc->tokens->at(token_index);
|
||||||
|
token_index += 1;
|
||||||
|
ast_expect_token(pc, param_str, TokenIdStringLiteral);
|
||||||
|
|
||||||
|
parse_string_literal(pc, param_str, &node->data.directive.param);
|
||||||
|
|
||||||
|
Token *r_paren = &pc->tokens->at(token_index);
|
||||||
|
token_index += 1;
|
||||||
|
ast_expect_token(pc, r_paren, TokenIdRParen);
|
||||||
|
|
||||||
|
*new_token_index = token_index;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ast_parse_directives(ParseContext *pc, int *token_index,
|
||||||
|
ZigList<AstNode *> *directives)
|
||||||
|
{
|
||||||
|
for (;;) {
|
||||||
|
Token *token = &pc->tokens->at(*token_index);
|
||||||
|
if (token->id == TokenIdNumberSign) {
|
||||||
|
AstNode *directive_node = ast_parse_directive(pc, *token_index, token_index);
|
||||||
|
directives->append(directive_node);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zig_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Type : token(Symbol) | PointerType | token(Unreachable)
|
Type : token(Symbol) | PointerType | token(Unreachable)
|
||||||
PointerType : token(Star) token(Const) Type | token(Star) token(Mut) Type;
|
PointerType : token(Star) token(Const) Type | token(Star) token(Mut) Type;
|
||||||
@ -500,48 +547,74 @@ static AstNode *ast_parse_block(ParseContext *pc, int token_index, int *new_toke
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FnProto : token(Fn) token(Symbol) ParamDeclList option(token(Arrow) Type)
|
FnProto : many(Directive) option(FnVisibleMod) token(Fn) token(Symbol) ParamDeclList option(token(Arrow) Type)
|
||||||
*/
|
*/
|
||||||
static AstNode *ast_parse_fn_proto(ParseContext *pc, int token_index, int *new_token_index) {
|
static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mandatory) {
|
||||||
Token *fn_token = &pc->tokens->at(token_index);
|
Token *token = &pc->tokens->at(*token_index);
|
||||||
token_index += 1;
|
|
||||||
ast_expect_token(pc, fn_token, TokenIdKeywordFn);
|
|
||||||
|
|
||||||
AstNode *node = ast_create_node(NodeTypeFnProto, fn_token);
|
FnProtoVisibMod visib_mod;
|
||||||
|
|
||||||
|
if (token->id == TokenIdKeywordPub) {
|
||||||
|
visib_mod = FnProtoVisibModPub;
|
||||||
|
*token_index += 1;
|
||||||
|
|
||||||
|
Token *fn_token = &pc->tokens->at(*token_index);
|
||||||
|
*token_index += 1;
|
||||||
|
ast_expect_token(pc, fn_token, TokenIdKeywordFn);
|
||||||
|
} else if (token->id == TokenIdKeywordExport) {
|
||||||
|
visib_mod = FnProtoVisibModExport;
|
||||||
|
*token_index += 1;
|
||||||
|
|
||||||
|
Token *fn_token = &pc->tokens->at(*token_index);
|
||||||
|
*token_index += 1;
|
||||||
|
ast_expect_token(pc, fn_token, TokenIdKeywordFn);
|
||||||
|
} else if (token->id == TokenIdKeywordFn) {
|
||||||
|
visib_mod = FnProtoVisibModPrivate;
|
||||||
|
*token_index += 1;
|
||||||
|
} else if (mandatory) {
|
||||||
|
ast_invalid_token_error(pc, token);
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNode *node = ast_create_node(NodeTypeFnProto, token);
|
||||||
|
node->data.fn_proto.visib_mod = visib_mod;
|
||||||
|
node->data.fn_proto.directives = pc->directive_list;
|
||||||
|
pc->directive_list = nullptr;
|
||||||
|
|
||||||
|
|
||||||
Token *fn_name = &pc->tokens->at(token_index);
|
Token *fn_name = &pc->tokens->at(*token_index);
|
||||||
token_index += 1;
|
*token_index += 1;
|
||||||
ast_expect_token(pc, fn_name, TokenIdSymbol);
|
ast_expect_token(pc, fn_name, TokenIdSymbol);
|
||||||
|
|
||||||
ast_buf_from_token(pc, fn_name, &node->data.fn_proto.name);
|
ast_buf_from_token(pc, fn_name, &node->data.fn_proto.name);
|
||||||
|
|
||||||
|
|
||||||
ast_parse_param_decl_list(pc, token_index, &token_index, &node->data.fn_proto.params);
|
ast_parse_param_decl_list(pc, *token_index, token_index, &node->data.fn_proto.params);
|
||||||
|
|
||||||
Token *arrow = &pc->tokens->at(token_index);
|
Token *arrow = &pc->tokens->at(*token_index);
|
||||||
if (arrow->id == TokenIdArrow) {
|
if (arrow->id == TokenIdArrow) {
|
||||||
token_index += 1;
|
*token_index += 1;
|
||||||
node->data.fn_proto.return_type = ast_parse_type(pc, token_index, &token_index);
|
node->data.fn_proto.return_type = ast_parse_type(pc, *token_index, token_index);
|
||||||
} else {
|
} else {
|
||||||
node->data.fn_proto.return_type = ast_create_void_type_node(pc, arrow);
|
node->data.fn_proto.return_type = ast_create_void_type_node(pc, arrow);
|
||||||
}
|
}
|
||||||
|
|
||||||
*new_token_index = token_index;
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FnDef : FnProto Block
|
FnDef : FnProto Block
|
||||||
*/
|
*/
|
||||||
static AstNode *ast_parse_fn_def(ParseContext *pc, int token_index, int *new_token_index) {
|
static AstNode *ast_parse_fn_def(ParseContext *pc, int *token_index, bool mandatory) {
|
||||||
AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, &token_index);
|
AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, mandatory);
|
||||||
|
if (!fn_proto)
|
||||||
|
return nullptr;
|
||||||
AstNode *node = ast_create_node_with_node(NodeTypeFnDef, fn_proto);
|
AstNode *node = ast_create_node_with_node(NodeTypeFnDef, fn_proto);
|
||||||
|
|
||||||
node->data.fn_def.fn_proto = fn_proto;
|
node->data.fn_def.fn_proto = fn_proto;
|
||||||
node->data.fn_def.body = ast_parse_block(pc, token_index, &token_index);
|
node->data.fn_def.body = ast_parse_block(pc, *token_index, token_index);
|
||||||
|
|
||||||
*new_token_index = token_index;
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,7 +622,7 @@ static AstNode *ast_parse_fn_def(ParseContext *pc, int token_index, int *new_tok
|
|||||||
FnDecl : FnProto token(Semicolon)
|
FnDecl : FnProto token(Semicolon)
|
||||||
*/
|
*/
|
||||||
static AstNode *ast_parse_fn_decl(ParseContext *pc, int token_index, int *new_token_index) {
|
static AstNode *ast_parse_fn_decl(ParseContext *pc, int token_index, int *new_token_index) {
|
||||||
AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, &token_index);
|
AstNode *fn_proto = ast_parse_fn_proto(pc, &token_index, true);
|
||||||
AstNode *node = ast_create_node_with_node(NodeTypeFnDecl, fn_proto);
|
AstNode *node = ast_create_node_with_node(NodeTypeFnDecl, fn_proto);
|
||||||
|
|
||||||
node->data.fn_decl.fn_proto = fn_proto;
|
node->data.fn_decl.fn_proto = fn_proto;
|
||||||
@ -565,78 +638,45 @@ static AstNode *ast_parse_fn_decl(ParseContext *pc, int token_index, int *new_to
|
|||||||
/*
|
/*
|
||||||
Directive : token(NumberSign) token(Symbol) token(LParen) token(String) token(RParen)
|
Directive : token(NumberSign) token(Symbol) token(LParen) token(String) token(RParen)
|
||||||
*/
|
*/
|
||||||
static AstNode *ast_parse_directive(ParseContext *pc, int token_index, int *new_token_index) {
|
|
||||||
Token *number_sign = &pc->tokens->at(token_index);
|
|
||||||
token_index += 1;
|
|
||||||
ast_expect_token(pc, number_sign, TokenIdNumberSign);
|
|
||||||
|
|
||||||
AstNode *node = ast_create_node(NodeTypeDirective, number_sign);
|
|
||||||
|
|
||||||
Token *name_symbol = &pc->tokens->at(token_index);
|
|
||||||
token_index += 1;
|
|
||||||
ast_expect_token(pc, name_symbol, TokenIdSymbol);
|
|
||||||
|
|
||||||
ast_buf_from_token(pc, name_symbol, &node->data.directive.name);
|
|
||||||
|
|
||||||
Token *l_paren = &pc->tokens->at(token_index);
|
|
||||||
token_index += 1;
|
|
||||||
ast_expect_token(pc, l_paren, TokenIdLParen);
|
|
||||||
|
|
||||||
Token *param_str = &pc->tokens->at(token_index);
|
|
||||||
token_index += 1;
|
|
||||||
ast_expect_token(pc, param_str, TokenIdStringLiteral);
|
|
||||||
|
|
||||||
parse_string_literal(pc, param_str, &node->data.directive.param);
|
|
||||||
|
|
||||||
Token *r_paren = &pc->tokens->at(token_index);
|
|
||||||
token_index += 1;
|
|
||||||
ast_expect_token(pc, r_paren, TokenIdRParen);
|
|
||||||
|
|
||||||
*new_token_index = token_index;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ast_parse_directives(ParseContext *pc, int token_index, int *new_token_index,
|
|
||||||
ZigList<AstNode *> *directives)
|
|
||||||
{
|
|
||||||
for (;;) {
|
|
||||||
Token *token = &pc->tokens->at(token_index);
|
|
||||||
if (token->id == TokenIdNumberSign) {
|
|
||||||
AstNode *directive_node = ast_parse_directive(pc, token_index, &token_index);
|
|
||||||
directives->append(directive_node);
|
|
||||||
} else {
|
|
||||||
*new_token_index = token_index;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zig_unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnProtoDecl) token(RBrace)
|
ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnProtoDecl) token(RBrace)
|
||||||
*/
|
*/
|
||||||
static AstNode *ast_parse_extern_block(ParseContext *pc, int token_index, int *new_token_index) {
|
static AstNode *ast_parse_extern_block(ParseContext *pc, int *token_index, bool mandatory) {
|
||||||
Token *extern_kw = &pc->tokens->at(token_index);
|
Token *extern_kw = &pc->tokens->at(*token_index);
|
||||||
token_index += 1;
|
if (extern_kw->id != TokenIdKeywordExtern) {
|
||||||
ast_expect_token(pc, extern_kw, TokenIdKeywordExtern);
|
if (mandatory)
|
||||||
|
ast_invalid_token_error(pc, extern_kw);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
*token_index += 1;
|
||||||
|
|
||||||
AstNode *node = ast_create_node(NodeTypeExternBlock, extern_kw);
|
AstNode *node = ast_create_node(NodeTypeExternBlock, extern_kw);
|
||||||
|
|
||||||
node->data.extern_block.directives = pc->directive_list;
|
node->data.extern_block.directives = pc->directive_list;
|
||||||
pc->directive_list = nullptr;
|
pc->directive_list = nullptr;
|
||||||
|
|
||||||
Token *l_brace = &pc->tokens->at(token_index);
|
Token *l_brace = &pc->tokens->at(*token_index);
|
||||||
token_index += 1;
|
*token_index += 1;
|
||||||
ast_expect_token(pc, l_brace, TokenIdLBrace);
|
ast_expect_token(pc, l_brace, TokenIdLBrace);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Token *token = &pc->tokens->at(token_index);
|
Token *directive_token = &pc->tokens->at(*token_index);
|
||||||
|
assert(!pc->directive_list);
|
||||||
|
pc->directive_list = allocate<ZigList<AstNode*>>(1);
|
||||||
|
ast_parse_directives(pc, token_index, pc->directive_list);
|
||||||
|
|
||||||
|
Token *token = &pc->tokens->at(*token_index);
|
||||||
if (token->id == TokenIdRBrace) {
|
if (token->id == TokenIdRBrace) {
|
||||||
token_index += 1;
|
if (pc->directive_list->length > 0) {
|
||||||
*new_token_index = token_index;
|
ast_error(directive_token, "invalid directive");
|
||||||
|
}
|
||||||
|
pc->directive_list = nullptr;
|
||||||
|
|
||||||
|
*token_index += 1;
|
||||||
return node;
|
return node;
|
||||||
} else {
|
} else {
|
||||||
AstNode *child = ast_parse_fn_decl(pc, token_index, &token_index);
|
AstNode *child = ast_parse_fn_decl(pc, *token_index, token_index);
|
||||||
node->data.extern_block.fn_decls.append(child);
|
node->data.extern_block.fn_decls.append(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -645,25 +685,31 @@ static AstNode *ast_parse_extern_block(ParseContext *pc, int token_index, int *n
|
|||||||
zig_unreachable();
|
zig_unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ast_parse_top_level_decls(ParseContext *pc, int token_index, int *new_token_index,
|
static void ast_parse_top_level_decls(ParseContext *pc, int *token_index, ZigList<AstNode *> *top_level_decls) {
|
||||||
ZigList<AstNode *> *top_level_decls)
|
|
||||||
{
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Token *token = &pc->tokens->at(token_index);
|
Token *directive_token = &pc->tokens->at(*token_index);
|
||||||
if (token->id == TokenIdNumberSign) {
|
assert(!pc->directive_list);
|
||||||
assert(!pc->directive_list);
|
pc->directive_list = allocate<ZigList<AstNode*>>(1);
|
||||||
pc->directive_list = allocate<ZigList<AstNode*>>(1);
|
ast_parse_directives(pc, token_index, pc->directive_list);
|
||||||
ast_parse_directives(pc, token_index, &token_index, pc->directive_list);
|
|
||||||
} else if (token->id == TokenIdKeywordFn) {
|
AstNode *fn_decl_node = ast_parse_fn_def(pc, token_index, false);
|
||||||
AstNode *fn_decl_node = ast_parse_fn_def(pc, token_index, &token_index);
|
if (fn_decl_node) {
|
||||||
top_level_decls->append(fn_decl_node);
|
top_level_decls->append(fn_decl_node);
|
||||||
} else if (token->id == TokenIdKeywordExtern) {
|
continue;
|
||||||
AstNode *extern_node = ast_parse_extern_block(pc, token_index, &token_index);
|
|
||||||
top_level_decls->append(extern_node);
|
|
||||||
} else {
|
|
||||||
*new_token_index = token_index;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstNode *extern_node = ast_parse_extern_block(pc, token_index, false);
|
||||||
|
if (extern_node) {
|
||||||
|
top_level_decls->append(extern_node);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc->directive_list->length > 0) {
|
||||||
|
ast_error(directive_token, "invalid directive");
|
||||||
|
}
|
||||||
|
pc->directive_list = nullptr;
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
zig_unreachable();
|
zig_unreachable();
|
||||||
}
|
}
|
||||||
@ -674,11 +720,11 @@ AstNode *ast_parse(Buf *buf, ZigList<Token> *tokens) {
|
|||||||
pc.root = ast_create_node(NodeTypeRoot, &tokens->at(0));
|
pc.root = ast_create_node(NodeTypeRoot, &tokens->at(0));
|
||||||
pc.tokens = tokens;
|
pc.tokens = tokens;
|
||||||
|
|
||||||
int new_token_index;
|
int token_index = 0;
|
||||||
ast_parse_top_level_decls(&pc, 0, &new_token_index, &pc.root->data.root.top_level_decls);
|
ast_parse_top_level_decls(&pc, &token_index, &pc.root->data.root.top_level_decls);
|
||||||
|
|
||||||
if (new_token_index != tokens->length - 1) {
|
if (token_index != tokens->length - 1) {
|
||||||
ast_invalid_token_error(&pc, &tokens->at(new_token_index));
|
ast_invalid_token_error(&pc, &tokens->at(token_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
return pc.root;
|
return pc.root;
|
||||||
|
|||||||
@ -34,7 +34,15 @@ struct AstNodeRoot {
|
|||||||
ZigList<AstNode *> top_level_decls;
|
ZigList<AstNode *> top_level_decls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum FnProtoVisibMod {
|
||||||
|
FnProtoVisibModPrivate,
|
||||||
|
FnProtoVisibModPub,
|
||||||
|
FnProtoVisibModExport,
|
||||||
|
};
|
||||||
|
|
||||||
struct AstNodeFnProto {
|
struct AstNodeFnProto {
|
||||||
|
ZigList<AstNode *> *directives;
|
||||||
|
FnProtoVisibMod visib_mod;
|
||||||
Buf name;
|
Buf name;
|
||||||
ZigList<AstNode *> params;
|
ZigList<AstNode *> params;
|
||||||
AstNode *return_type;
|
AstNode *return_type;
|
||||||
|
|||||||
@ -163,6 +163,10 @@ static void end_token(Tokenize *t) {
|
|||||||
t->cur_tok->id = TokenIdKeywordExtern;
|
t->cur_tok->id = TokenIdKeywordExtern;
|
||||||
} else if (mem_eql_str(token_mem, token_len, "unreachable")) {
|
} else if (mem_eql_str(token_mem, token_len, "unreachable")) {
|
||||||
t->cur_tok->id = TokenIdKeywordUnreachable;
|
t->cur_tok->id = TokenIdKeywordUnreachable;
|
||||||
|
} else if (mem_eql_str(token_mem, token_len, "pub")) {
|
||||||
|
t->cur_tok->id = TokenIdKeywordPub;
|
||||||
|
} else if (mem_eql_str(token_mem, token_len, "export")) {
|
||||||
|
t->cur_tok->id = TokenIdKeywordExport;
|
||||||
}
|
}
|
||||||
|
|
||||||
t->cur_tok = nullptr;
|
t->cur_tok = nullptr;
|
||||||
@ -407,6 +411,8 @@ static const char * token_name(Token *token) {
|
|||||||
case TokenIdKeywordReturn: return "Return";
|
case TokenIdKeywordReturn: return "Return";
|
||||||
case TokenIdKeywordExtern: return "Extern";
|
case TokenIdKeywordExtern: return "Extern";
|
||||||
case TokenIdKeywordUnreachable: return "Unreachable";
|
case TokenIdKeywordUnreachable: return "Unreachable";
|
||||||
|
case TokenIdKeywordPub: return "Pub";
|
||||||
|
case TokenIdKeywordExport: return "Export";
|
||||||
case TokenIdLParen: return "LParen";
|
case TokenIdLParen: return "LParen";
|
||||||
case TokenIdRParen: return "RParen";
|
case TokenIdRParen: return "RParen";
|
||||||
case TokenIdComma: return "Comma";
|
case TokenIdComma: return "Comma";
|
||||||
|
|||||||
@ -19,6 +19,8 @@ enum TokenId {
|
|||||||
TokenIdKeywordConst,
|
TokenIdKeywordConst,
|
||||||
TokenIdKeywordExtern,
|
TokenIdKeywordExtern,
|
||||||
TokenIdKeywordUnreachable,
|
TokenIdKeywordUnreachable,
|
||||||
|
TokenIdKeywordPub,
|
||||||
|
TokenIdKeywordExport,
|
||||||
TokenIdLParen,
|
TokenIdLParen,
|
||||||
TokenIdRParen,
|
TokenIdRParen,
|
||||||
TokenIdComma,
|
TokenIdComma,
|
||||||
|
|||||||
134
src/zig_llvm.cpp
134
src/zig_llvm.cpp
@ -10,7 +10,19 @@
|
|||||||
#include <llvm/InitializePasses.h>
|
#include <llvm/InitializePasses.h>
|
||||||
#include <llvm/PassRegistry.h>
|
#include <llvm/PassRegistry.h>
|
||||||
#include <llvm/MC/SubtargetFeature.h>
|
#include <llvm/MC/SubtargetFeature.h>
|
||||||
|
#include <llvm/Support/raw_ostream.h>
|
||||||
|
#include <llvm/Support/FileSystem.h>
|
||||||
|
#include <llvm/Target/TargetMachine.h>
|
||||||
|
#include <llvm/IR/LegacyPassManager.h>
|
||||||
|
#include <llvm/IR/Module.h>
|
||||||
|
#include <llvm/IR/Verifier.h>
|
||||||
|
#include <llvm/IR/Instructions.h>
|
||||||
|
#include <llvm/IR/IRBuilder.h>
|
||||||
|
#include <llvm/Analysis/TargetLibraryInfo.h>
|
||||||
|
#include <llvm/Analysis/TargetTransformInfo.h>
|
||||||
|
#include <llvm/Transforms/IPO.h>
|
||||||
|
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
|
||||||
|
#include <llvm/Transforms/Scalar.h>
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
@ -42,3 +54,123 @@ char *LLVMZigGetNativeFeatures(void) {
|
|||||||
|
|
||||||
return strdup(features.getString().c_str());
|
return strdup(features.getString().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void addAddDiscriminatorsPass(const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) {
|
||||||
|
PM.add(createAddDiscriminatorsPass());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LLVMZigOptimizeModule(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref) {
|
||||||
|
TargetMachine* target_machine = reinterpret_cast<TargetMachine*>(targ_machine_ref);
|
||||||
|
Module* module = unwrap(module_ref);
|
||||||
|
TargetLibraryInfoImpl tlii(Triple(module->getTargetTriple()));
|
||||||
|
|
||||||
|
PassManagerBuilder *PMBuilder = new PassManagerBuilder();
|
||||||
|
PMBuilder->OptLevel = target_machine->getOptLevel();
|
||||||
|
PMBuilder->SizeLevel = 0;
|
||||||
|
PMBuilder->BBVectorize = true;
|
||||||
|
PMBuilder->SLPVectorize = true;
|
||||||
|
PMBuilder->LoopVectorize = true;
|
||||||
|
|
||||||
|
PMBuilder->DisableUnitAtATime = false;
|
||||||
|
PMBuilder->DisableUnrollLoops = false;
|
||||||
|
PMBuilder->MergeFunctions = true;
|
||||||
|
PMBuilder->PrepareForLTO = true;
|
||||||
|
PMBuilder->RerollLoops = true;
|
||||||
|
|
||||||
|
PMBuilder->addExtension(PassManagerBuilder::EP_EarlyAsPossible, addAddDiscriminatorsPass);
|
||||||
|
|
||||||
|
PMBuilder->LibraryInfo = &tlii;
|
||||||
|
|
||||||
|
PMBuilder->Inliner = createFunctionInliningPass(PMBuilder->OptLevel, PMBuilder->SizeLevel);
|
||||||
|
|
||||||
|
// Set up the per-function pass manager.
|
||||||
|
legacy::FunctionPassManager *FPM = new legacy::FunctionPassManager(module);
|
||||||
|
FPM->add(createTargetTransformInfoWrapperPass(target_machine->getTargetIRAnalysis()));
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool verify_module = true;
|
||||||
|
#else
|
||||||
|
bool verify_module = false;
|
||||||
|
#endif
|
||||||
|
if (verify_module) {
|
||||||
|
FPM->add(createVerifierPass());
|
||||||
|
}
|
||||||
|
PMBuilder->populateFunctionPassManager(*FPM);
|
||||||
|
|
||||||
|
// Set up the per-module pass manager.
|
||||||
|
legacy::PassManager *MPM = new legacy::PassManager();
|
||||||
|
MPM->add(createTargetTransformInfoWrapperPass(target_machine->getTargetIRAnalysis()));
|
||||||
|
|
||||||
|
PMBuilder->populateModulePassManager(*MPM);
|
||||||
|
|
||||||
|
|
||||||
|
// run per function optimization passes
|
||||||
|
FPM->doInitialization();
|
||||||
|
for (Function &F : *module)
|
||||||
|
if (!F.isDeclaration())
|
||||||
|
FPM->run(F);
|
||||||
|
FPM->doFinalization();
|
||||||
|
|
||||||
|
// run per module optimization passes
|
||||||
|
MPM->run(*module);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LLVMBool LLVMZigTargetMachineEmit(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref,
|
||||||
|
raw_pwrite_stream &out_stream, LLVMCodeGenFileType codegen, char **err_msg)
|
||||||
|
{
|
||||||
|
TargetMachine* target_machine = reinterpret_cast<TargetMachine*>(targ_machine_ref);
|
||||||
|
Module* module = unwrap(module_ref);
|
||||||
|
TargetLibraryInfoImpl tlii(Triple(module->getTargetTriple()));
|
||||||
|
|
||||||
|
legacy::PassManager pass;
|
||||||
|
|
||||||
|
pass.add(new TargetLibraryInfoWrapperPass(tlii));
|
||||||
|
|
||||||
|
const DataLayout *td = target_machine->getDataLayout();
|
||||||
|
|
||||||
|
if (!td) {
|
||||||
|
*err_msg = strdup("No DataLayout in TargetMachine");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
module->setDataLayout(*td);
|
||||||
|
|
||||||
|
|
||||||
|
TargetMachine::CodeGenFileType ft;
|
||||||
|
switch (codegen) {
|
||||||
|
case LLVMAssemblyFile:
|
||||||
|
ft = TargetMachine::CGFT_AssemblyFile;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ft = TargetMachine::CGFT_ObjectFile;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (target_machine->addPassesToEmitFile(pass, out_stream, ft)) {
|
||||||
|
*err_msg = strdup("TargetMachine can't emit a file of this type");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pass.run(*module);
|
||||||
|
|
||||||
|
out_stream.flush();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVMBool LLVMZigTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref,
|
||||||
|
char* filename, LLVMCodeGenFileType codegen, char** err_msg)
|
||||||
|
{
|
||||||
|
std::error_code error_code;
|
||||||
|
raw_fd_ostream dest(filename, error_code, sys::fs::F_None);
|
||||||
|
if (error_code) {
|
||||||
|
*err_msg = strdup(error_code.message().c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return LLVMZigTargetMachineEmit(targ_machine_ref, module_ref, dest, codegen, err_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVMValueRef LLVMZigBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args,
|
||||||
|
unsigned NumArgs, unsigned CC, const char *Name)
|
||||||
|
{
|
||||||
|
CallInst *call_inst = CallInst::Create(unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Name);
|
||||||
|
call_inst->setCallingConv(CC);
|
||||||
|
return wrap(unwrap(B)->Insert(call_inst));
|
||||||
|
}
|
||||||
|
|||||||
@ -21,4 +21,12 @@ void LLVMZigInitializeUnreachableBlockElimPass(LLVMPassRegistryRef R);
|
|||||||
char *LLVMZigGetHostCPUName(void);
|
char *LLVMZigGetHostCPUName(void);
|
||||||
char *LLVMZigGetNativeFeatures(void);
|
char *LLVMZigGetNativeFeatures(void);
|
||||||
|
|
||||||
|
LLVMBool LLVMZigTargetMachineEmitToFile(LLVMTargetMachineRef target_machine, LLVMModuleRef module,
|
||||||
|
char* filename, LLVMCodeGenFileType codegen, char** error_msg);
|
||||||
|
|
||||||
|
void LLVMZigOptimizeModule(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref);
|
||||||
|
|
||||||
|
LLVMValueRef LLVMZigBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args,
|
||||||
|
unsigned NumArgs, unsigned CC, const char *Name);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -53,7 +53,7 @@ static void add_all_test_cases(void) {
|
|||||||
fn exit(code: i32) -> unreachable;
|
fn exit(code: i32) -> unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _start() -> unreachable {
|
export fn _start() -> unreachable {
|
||||||
puts("Hello, world!");
|
puts("Hello, world!");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ static void add_all_test_cases(void) {
|
|||||||
fn empty_function_1() {}
|
fn empty_function_1() {}
|
||||||
fn empty_function_2() { return; }
|
fn empty_function_2() { return; }
|
||||||
|
|
||||||
fn _start() -> unreachable {
|
export fn _start() -> unreachable {
|
||||||
empty_function_1();
|
empty_function_1();
|
||||||
empty_function_2();
|
empty_function_2();
|
||||||
this_is_a_function();
|
this_is_a_function();
|
||||||
@ -95,7 +95,7 @@ static void add_all_test_cases(void) {
|
|||||||
|
|
||||||
/// this is a documentation comment
|
/// this is a documentation comment
|
||||||
/// doc comment line 2
|
/// doc comment line 2
|
||||||
fn _start() -> unreachable {
|
export fn _start() -> unreachable {
|
||||||
puts(/* mid-line comment /* nested */ */ "OK");
|
puts(/* mid-line comment /* nested */ */ "OK");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user