add inline assembly support

master
Andrew Kelley 2015-12-10 15:34:38 -07:00
parent 3e8a98fa61
commit 0dbee2300e
15 changed files with 383 additions and 20 deletions

View File

@ -115,7 +115,12 @@ set(C_HEADERS
"${CMAKE_SOURCE_DIR}/c_headers/xtestintrin.h"
)
set(ZIG_STD_SRC
"${CMAKE_SOURCE_DIR}/std/bootstrap.zig"
)
set(C_HEADERS_DEST "lib/zig/include")
set(ZIG_STD_DEST "lib/zig/std")
set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h")
configure_file (
"${CMAKE_SOURCE_DIR}/src/config.h.in"
@ -142,6 +147,7 @@ target_link_libraries(zig LINK_PUBLIC
install(TARGETS zig DESTINATION bin)
install(FILES ${C_HEADERS} DESTINATION ${C_HEADERS_DEST})
install(FILES ${ZIG_STD_SRC} DESTINATION ${ZIG_STD_DEST})
add_executable(run_tests ${TEST_SOURCES})
target_link_libraries(run_tests)

View File

@ -58,7 +58,6 @@ compromises backward compatibility.
* structs
* loops
* enums
* inline assembly and syscalls
* conditional compilation and ability to check target platform and architecture
* main function with command line arguments
* void pointer constant
@ -83,10 +82,23 @@ compromises backward compatibility.
## Building
### Debug / Development Build
```
mkdir build
cd build
cmake ..
cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd)
make
make install
./run_tests
```
### Release / Install Build
```
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
sudo make install
```

View File

@ -70,7 +70,15 @@ VariableDeclaration : token(Let) option(token(Mut)) token(Symbol) (token(Eq) Exp
Expression : BlockExpression | NonBlockExpression
NonBlockExpression : ReturnExpression | AssignmentExpression
NonBlockExpression : ReturnExpression | AssignmentExpression | AsmExpression
AsmExpression : token(Asm) option(token(Volatile)) token(LParen) token(String) option(AsmOutput) token(RParen)
AsmOutput : token(Colon) list(AsmOutputItem, token(Comma)) option(AsmInput)
AsmInput : token(Colon) list(AsmInputItem, token(Comma)) option(AsmClobbers)
AsmClobbers: token(Colon) list(token(String), token(Comma))
AssignmentExpression : BoolOrExpression token(Equal) BoolOrExpression | BoolOrExpression

View File

@ -7,8 +7,8 @@ if exists("b:current_syntax")
finish
endif
syn keyword zigKeyword fn return mut const extern unreachable export pub as use while
syn keyword zigKeyword if else let void goto type enum struct continue break match
syn keyword zigKeyword fn return mut const extern unreachable export pub as use while asm
syn keyword zigKeyword if else let void goto type enum struct continue break match volatile
syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128
syn keyword zigConstant null

View File

@ -0,0 +1,11 @@
export executable "hello";
#link("c")
extern {
fn printf(__format: *const u8, ...) -> i32;
}
export fn main(argc : isize, argv : *mut *mut u8, env : *mut *mut u8) -> i32 {
printf("argc = %zu\n", argc);
return 0;
}

View File

@ -43,6 +43,7 @@ static AstNode *first_executing_node(AstNode *node) {
case NodeTypeIfExpr:
case NodeTypeLabel:
case NodeTypeGoto:
case NodeTypeAsmExpr:
return node;
}
zig_panic("unreachable");
@ -205,8 +206,26 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
for (int i = 0; i < node->data.fn_proto.directives->length; i += 1) {
AstNode *directive_node = node->data.fn_proto.directives->at(i);
Buf *name = &directive_node->data.directive.name;
add_node_error(g, directive_node,
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
if (buf_eql_str(name, "attribute")) {
Buf *attr_name = &directive_node->data.directive.param;
if (fn_table_entry->fn_def_node) {
if (buf_eql_str(attr_name, "naked")) {
fn_table_entry->fn_attr_list.append(FnAttrIdNaked);
} else if (buf_eql_str(attr_name, "alwaysinline")) {
fn_table_entry->fn_attr_list.append(FnAttrIdAlwaysInline);
} else {
add_node_error(g, directive_node,
buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
}
} else {
add_node_error(g, directive_node,
buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
}
} else {
add_node_error(g, directive_node,
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
}
}
for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
@ -338,6 +357,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
resolve_function_proto(g, proto_node, fn_table_entry);
assert(!proto_node->codegen_node);
proto_node->codegen_node = allocate<CodeGenNode>(1);
proto_node->codegen_node->data.fn_proto_node.fn_table_entry = fn_table_entry;
@ -415,6 +435,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
case NodeTypeIfExpr:
case NodeTypeLabel:
case NodeTypeGoto:
case NodeTypeAsmExpr:
zig_unreachable();
}
}
@ -626,6 +647,11 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
return_type = g->builtin_types.entry_unreachable;
break;
}
case NodeTypeAsmExpr:
{
return_type = g->builtin_types.entry_void;
break;
}
case NodeTypeBinOpExpr:
{
switch (node->data.bin_op_expr.bin_op) {
@ -1004,6 +1030,7 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
case NodeTypeIfExpr:
case NodeTypeLabel:
case NodeTypeGoto:
case NodeTypeAsmExpr:
zig_unreachable();
}
}

View File

@ -82,6 +82,11 @@ struct LabelTableEntry {
bool entered_from_fallthrough;
};
enum FnAttrId {
FnAttrIdNaked,
FnAttrIdAlwaysInline,
};
struct FnTableEntry {
LLVMValueRef fn_value;
AstNode *proto_node;
@ -90,6 +95,7 @@ struct FnTableEntry {
bool internal_linkage;
unsigned calling_convention;
ImportTableEntry *import_entry;
ZigList<FnAttrId> fn_attr_list;
// reminder: hash tables must be initialized before use
HashMap<Buf *, LabelTableEntry *, buf_hash, buf_eql_buf> label_table;
@ -113,6 +119,7 @@ struct CodeGen {
TypeTableEntry *entry_bool;
TypeTableEntry *entry_u8;
TypeTableEntry *entry_i32;
TypeTableEntry *entry_isize;
TypeTableEntry *entry_f32;
TypeTableEntry *entry_string_literal;
TypeTableEntry *entry_void;
@ -124,6 +131,7 @@ struct CodeGen {
unsigned pointer_size_bytes;
bool is_static;
bool strip_debug_symbols;
bool insert_bootstrap_code;
CodeGenBuildType build_type;
LLVMTargetMachineRef target_machine;
bool is_native_target;

View File

@ -609,6 +609,41 @@ static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *i
return return_value;
}
static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) {
assert(node->type == NodeTypeAsmExpr);
Buf *src_template = &node->data.asm_expr.asm_template;
Buf llvm_template = BUF_INIT;
buf_resize(&llvm_template, 0);
for (int token_i = 0; token_i < node->data.asm_expr.token_list.length; token_i += 1) {
AsmToken *asm_token = &node->data.asm_expr.token_list.at(token_i);
switch (asm_token->id) {
case AsmTokenIdTemplate:
for (int offset = asm_token->start; offset < asm_token->end; offset += 1) {
uint8_t c = *((uint8_t*)(buf_ptr(src_template) + offset));
if (c == '$') {
buf_append_str(&llvm_template, "$$");
} else {
buf_append_char(&llvm_template, c);
}
}
break;
case AsmTokenIdPercent:
buf_append_char(&llvm_template, '%');
break;
}
}
LLVMTypeRef function_type = LLVMFunctionType(LLVMVoidType(), nullptr, 0, false);
LLVMValueRef asm_fn = LLVMConstInlineAsm(function_type, buf_ptr(&llvm_template), "", true, false);
add_debug_source_node(g, node);
return LLVMBuildCall(g->builder, asm_fn, nullptr, 0, "");
}
static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
switch (node->type) {
case NodeTypeBinOpExpr:
@ -663,6 +698,8 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
return LLVMConstNull(LLVMInt1Type());
case NodeTypeIfExpr:
return gen_if_expr(g, node);
case NodeTypeAsmExpr:
return gen_asm_expr(g, node);
case NodeTypeNumberLiteral:
{
Buf *number_str = &node->data.number;
@ -762,6 +799,16 @@ static LLVMZigDISubroutineType *create_di_function_type(CodeGen *g, AstNodeFnPro
return LLVMZigCreateSubroutineType(g->dbuilder, di_file, types, types_len, 0);
}
static LLVMAttribute to_llvm_fn_attr(FnAttrId attr_id) {
switch (attr_id) {
case FnAttrIdNaked:
return LLVMNakedAttribute;
case FnAttrIdAlwaysInline:
return LLVMAlwaysInlineAttribute;
}
zig_unreachable();
}
static void do_code_gen(CodeGen *g) {
assert(!g->errors.length);
@ -789,6 +836,11 @@ static void do_code_gen(CodeGen *g) {
LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, param_count, fn_proto->is_var_args);
LLVMValueRef fn = LLVMAddFunction(g->module, buf_ptr(&fn_proto->name), function_type);
for (int attr_i = 0; attr_i < fn_table_entry->fn_attr_list.length; attr_i += 1) {
FnAttrId attr_id = fn_table_entry->fn_attr_list.at(attr_i);
LLVMAddFunctionAttr(fn, to_llvm_fn_attr(attr_id));
}
LLVMSetLinkage(fn, fn_table_entry->internal_linkage ? LLVMInternalLinkage : LLVMExternalLinkage);
if (type_is_unreachable(g, fn_proto->return_type)) {
@ -966,6 +1018,19 @@ static void define_primitive_types(CodeGen *g) {
g->type_table.put(&entry->name, entry);
g->builtin_types.entry_i32 = entry;
}
{
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
entry->type_ref = LLVMIntType(g->pointer_size_bytes * 8);
buf_init_from_str(&entry->name, "isize");
entry->size_in_bits = g->pointer_size_bytes * 8;
entry->align_in_bits = g->pointer_size_bytes * 8;
entry->data.integral.is_signed = true;
entry->di_type = LLVMZigCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
entry->size_in_bits, entry->align_in_bits,
LLVMZigEncoding_DW_ATE_signed());
g->type_table.put(&entry->name, entry);
g->builtin_types.entry_isize = entry;
}
{
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat);
entry->type_ref = LLVMFloatType();
@ -1121,20 +1186,30 @@ static ImportTableEntry *codegen_add_code(CodeGen *g, Buf *source_path, Buf *sou
assert(import_entry->root->type == NodeTypeRoot);
for (int decl_i = 0; decl_i < import_entry->root->data.root.top_level_decls.length; decl_i += 1) {
AstNode *top_level_decl = import_entry->root->data.root.top_level_decls.at(decl_i);
if (top_level_decl->type != NodeTypeUse)
continue;
auto entry = g->import_table.maybe_get(&top_level_decl->data.use.path);
if (!entry) {
Buf full_path = BUF_INIT;
os_path_join(g->root_source_dir, &top_level_decl->data.use.path, &full_path);
Buf *import_code = buf_alloc();
if ((err = os_fetch_file_path(&full_path, import_code))) {
add_node_error(g, top_level_decl,
buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
break;
if (top_level_decl->type == NodeTypeUse) {
auto entry = g->import_table.maybe_get(&top_level_decl->data.use.path);
if (!entry) {
Buf full_path = BUF_INIT;
os_path_join(g->root_source_dir, &top_level_decl->data.use.path, &full_path);
Buf *import_code = buf_alloc();
if ((err = os_fetch_file_path(&full_path, import_code))) {
add_node_error(g, top_level_decl,
buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
break;
}
codegen_add_code(g, &top_level_decl->data.use.path, import_code);
}
} else if (top_level_decl->type == NodeTypeFnDef) {
AstNode *proto_node = top_level_decl->data.fn_def.fn_proto;
assert(proto_node->type == NodeTypeFnProto);
Buf *proto_name = &proto_node->data.fn_proto.name;
bool is_exported = (proto_node->data.fn_proto.visib_mod == FnProtoVisibModExport);
if (buf_eql_str(proto_name, "main") && is_exported) {
g->insert_bootstrap_code = true;
}
codegen_add_code(g, &top_level_decl->data.use.path, import_code);
}
}
@ -1146,6 +1221,17 @@ void codegen_add_root_code(CodeGen *g, Buf *source_path, Buf *source_code) {
g->root_import = codegen_add_code(g, source_path, source_code);
if (g->insert_bootstrap_code) {
Buf *path_to_bootstrap_src = buf_sprintf("%s/bootstrap.zig", ZIG_STD_DIR);
Buf *import_code = buf_alloc();
int err;
if ((err = os_fetch_file_path(path_to_bootstrap_src, import_code))) {
zig_panic("unable to open '%s': %s", buf_ptr(path_to_bootstrap_src), err_str(err));
}
codegen_add_code(g, path_to_bootstrap_src, import_code);
}
if (g->verbose) {
fprintf(stderr, "\nSemantic Analysis:\n");
fprintf(stderr, "--------------------\n");
@ -1185,6 +1271,9 @@ static void to_c_type(CodeGen *g, AstNode *type_node, Buf *out_buf) {
} else if (type_entry == g->builtin_types.entry_i32) {
g->c_stdint_used = true;
buf_init_from_str(out_buf, "int32_t");
} else if (type_entry == g->builtin_types.entry_isize) {
g->c_stdint_used = true;
buf_init_from_str(out_buf, "intptr_t");
} else if (type_entry == g->builtin_types.entry_f32) {
buf_init_from_str(out_buf, "float");
} else if (type_entry == g->builtin_types.entry_unreachable) {

View File

@ -7,5 +7,6 @@
#define ZIG_VERSION_STRING "@ZIG_VERSION@"
#define ZIG_HEADERS_DIR "@CMAKE_INSTALL_PREFIX@/@C_HEADERS_DEST@"
#define ZIG_STD_DIR "@CMAKE_INSTALL_PREFIX@/@ZIG_STD_DEST@"
#endif

View File

@ -104,6 +104,8 @@ const char *node_type_str(NodeType node_type) {
return "Label";
case NodeTypeGoto:
return "Label";
case NodeTypeAsmExpr:
return "AsmExpr";
}
zig_unreachable();
}
@ -290,6 +292,9 @@ void ast_print(AstNode *node, int indent) {
case NodeTypeGoto:
fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.go_to.name));
break;
case NodeTypeAsmExpr:
fprintf(stderr, "%s\n", node_type_str(node->type));
break;
}
}
@ -360,6 +365,71 @@ static void ast_buf_from_token(ParseContext *pc, Token *token, Buf *buf) {
buf_init_from_mem(buf, buf_ptr(pc->buf) + token->start_pos, token->end_pos - token->start_pos);
}
static void parse_asm_template(ParseContext *pc, AstNode *node) {
Buf *asm_template = &node->data.asm_expr.asm_template;
enum State {
StateStart,
StatePercent,
StateTemplate,
};
ZigList<AsmToken> *tok_list = &node->data.asm_expr.token_list;
assert(tok_list->length == 0);
AsmToken *cur_tok = nullptr;
enum State state = StateStart;
for (int i = 0; i < buf_len(asm_template); i += 1) {
uint8_t c = *((uint8_t*)buf_ptr(asm_template) + i);
switch (state) {
case StateStart:
if (c == '%') {
tok_list->add_one();
cur_tok = &tok_list->last();
cur_tok->id = AsmTokenIdPercent;
cur_tok->start = i;
state = StatePercent;
} else {
tok_list->add_one();
cur_tok = &tok_list->last();
cur_tok->id = AsmTokenIdTemplate;
cur_tok->start = i;
state = StateTemplate;
}
break;
case StatePercent:
if (c == '%') {
cur_tok->end = i;
state = StateStart;
} else {
zig_panic("TODO handle assembly tokenize error");
}
break;
case StateTemplate:
if (c == '%') {
cur_tok->end = i;
i -= 1;
cur_tok = nullptr;
state = StateStart;
}
break;
}
}
switch (state) {
case StateStart:
break;
case StatePercent:
zig_panic("TODO handle assembly tokenize error eof");
break;
case StateTemplate:
cur_tok->end = buf_len(asm_template);
break;
}
}
static void parse_string_literal(ParseContext *pc, Token *token, Buf *buf) {
// skip the double quotes at beginning and end
// convert escape sequences
@ -1264,7 +1334,50 @@ static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mand
}
/*
NonBlockExpression : ReturnExpression | AssignmentExpression
AsmExpression : token(Asm) option(token(Volatile)) token(LParen) token(String) option(AsmOutput) token(RParen)
*/
static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mandatory) {
Token *asm_token = &pc->tokens->at(*token_index);
if (asm_token->id != TokenIdKeywordAsm) {
if (mandatory) {
ast_invalid_token_error(pc, asm_token);
} else {
return nullptr;
}
}
AstNode *node = ast_create_node(pc, NodeTypeAsmExpr, asm_token);
*token_index += 1;
Token *lparen_tok = &pc->tokens->at(*token_index);
if (lparen_tok->id == TokenIdKeywordVolatile) {
node->data.asm_expr.is_volatile = true;
*token_index += 1;
lparen_tok = &pc->tokens->at(*token_index);
}
ast_expect_token(pc, lparen_tok, TokenIdLParen);
*token_index += 1;
Token *template_tok = &pc->tokens->at(*token_index);
ast_expect_token(pc, template_tok, TokenIdStringLiteral);
*token_index += 1;
parse_string_literal(pc, template_tok, &node->data.asm_expr.asm_template);
parse_asm_template(pc, node);
Token *rparen_tok = &pc->tokens->at(*token_index);
ast_expect_token(pc, rparen_tok, TokenIdRParen);
*token_index += 1;
return node;
}
/*
NonBlockExpression : ReturnExpression | AssignmentExpression | AsmExpression
*/
static AstNode *ast_parse_non_block_expr(ParseContext *pc, int *token_index, bool mandatory) {
Token *token = &pc->tokens->at(*token_index);
@ -1277,6 +1390,10 @@ static AstNode *ast_parse_non_block_expr(ParseContext *pc, int *token_index, boo
if (ass_expr)
return ass_expr;
AstNode *asm_expr = ast_parse_asm_expr(pc, token_index, false);
if (asm_expr)
return asm_expr;
if (mandatory)
ast_invalid_token_error(pc, token);

View File

@ -16,6 +16,7 @@
struct AstNode;
struct CodeGenNode;
struct ImportTableEntry;
struct AsmToken;
enum NodeType {
NodeTypeRoot,
@ -45,6 +46,7 @@ enum NodeType {
NodeTypeIfExpr,
NodeTypeLabel,
NodeTypeGoto,
NodeTypeAsmExpr,
};
struct AstNodeRoot {
@ -203,6 +205,12 @@ struct AstNodeGoto {
Buf name;
};
struct AstNodeAsmExpr {
bool is_volatile;
Buf asm_template;
ZigList<AsmToken> token_list;
};
struct AstNode {
enum NodeType type;
int line;
@ -231,6 +239,7 @@ struct AstNode {
AstNodeIfExpr if_expr;
AstNodeLabel label;
AstNodeGoto go_to;
AstNodeAsmExpr asm_expr;
Buf number;
Buf string;
Buf symbol;
@ -238,6 +247,17 @@ struct AstNode {
} data;
};
enum AsmTokenId {
AsmTokenIdTemplate,
AsmTokenIdPercent,
};
struct AsmToken {
enum AsmTokenId id;
int start;
int end;
};
__attribute__ ((format (printf, 2, 3)))
void ast_token_error(Token *token, const char *format, ...);

View File

@ -197,6 +197,10 @@ static void end_token(Tokenize *t) {
t->cur_tok->id = TokenIdKeywordElse;
} else if (mem_eql_str(token_mem, token_len, "goto")) {
t->cur_tok->id = TokenIdKeywordGoto;
} else if (mem_eql_str(token_mem, token_len, "volatile")) {
t->cur_tok->id = TokenIdKeywordVolatile;
} else if (mem_eql_str(token_mem, token_len, "asm")) {
t->cur_tok->id = TokenIdKeywordAsm;
}
t->cur_tok = nullptr;
@ -637,6 +641,8 @@ static const char * token_name(Token *token) {
case TokenIdKeywordIf: return "If";
case TokenIdKeywordElse: return "Else";
case TokenIdKeywordGoto: return "Goto";
case TokenIdKeywordVolatile: return "Volatile";
case TokenIdKeywordAsm: return "Asm";
case TokenIdLParen: return "LParen";
case TokenIdRParen: return "RParen";
case TokenIdComma: return "Comma";
@ -687,3 +693,40 @@ void print_tokens(Buf *buf, ZigList<Token> *tokens) {
fprintf(stderr, "\n");
}
}
bool is_printable(uint8_t c) {
switch (c) {
default:
return false;
case DIGIT:
case ALPHA:
case '!':
case '#':
case '$':
case '%':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case '-':
case '.':
case '/':
case ':':
case ';':
case '<':
case '=':
case '>':
case '?':
case '@':
case '^':
case '_':
case '`':
case '~':
case ' ':
return true;
}
}

View File

@ -30,6 +30,8 @@ enum TokenId {
TokenIdKeywordIf,
TokenIdKeywordElse,
TokenIdKeywordGoto,
TokenIdKeywordAsm,
TokenIdKeywordVolatile,
TokenIdLParen,
TokenIdRParen,
TokenIdComma,
@ -90,4 +92,6 @@ void tokenize(Buf *buf, Tokenization *out_tokenization);
void print_tokens(Buf *buf, ZigList<Token> *tokens);
bool is_printable(uint8_t c);
#endif

16
std/bootstrap.zig Normal file
View File

@ -0,0 +1,16 @@
// TODO conditionally compile this differently for non-ELF
#attribute("naked")
export fn _start() -> unreachable {
// TODO conditionally compile this differently for other architectures and other OSes
asm volatile ("
mov (%%rsp), %%rdi // first parameter is argc
lea 0x8(%%rsp), %%rsi // second parameter is argv
lea 0x10(%%rsp,%%rdi,8), %%rdx // third paremeter is env
callq main
mov %%rax, %%rdi // return value is the parameter to exit syscall
mov $60, %%rax // 60 is exit syscall number
syscall
");
unreachable
}

View File

@ -397,6 +397,7 @@ loop_2_end:
exit(0);
}
)SOURCE", "OK\n");
}
static void add_compile_failure_test_cases(void) {