authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-03-20 19:00:23-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-03-20 19:00:23-04:00
log15c316b0d8152c0a1ad5b4b26efdf3fdc8cfb4c0
tree0bd91c0ba2a63ed3495e94385fbedaa1e3a6fde0
parent3c7555cb679492f3f1c0ce320cbdf4a3769e56db
signaturelock-open Commit is signed but in an unrecognized format.

add docs for assembly and fix global assembly parsing

Previously, global assembly was parsed expecting it to have the template syntax. However global assembly has no inputs, outputs, or clobbers, and thus does not have template syntax. This is now fixed. This commit also adds a compile error for using volatile on global assembly, since it is meaningless. closes #1515

10 files changed, 415 insertions(+), 165 deletions(-)

doc/docgen.zig+21-7
......@@ -274,7 +274,7 @@ const Code = struct {
274274 is_inline: bool,
275275 mode: builtin.Mode,
276276 link_objects: []const []const u8,
277 target_windows: bool,
277 target_str: ?[]const u8,
278278 link_libc: bool,
279279
280280 const Id = union(enum) {
......@@ -491,7 +491,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc {
491491 var mode = builtin.Mode.Debug;
492492 var link_objects = std.ArrayList([]const u8).init(allocator);
493493 defer link_objects.deinit();
494 var target_windows = false;
494 var target_str: ?[]const u8 = null;
495495 var link_libc = false;
496496
497497 const source_token = while (true) {
......@@ -506,7 +506,9 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc {
506506 const obj_tok = try eatToken(tokenizer, Token.Id.TagContent);
507507 try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]);
508508 } else if (mem.eql(u8, end_tag_name, "target_windows")) {
509 target_windows = true;
509 target_str = "x86_64-windows";
510 } else if (mem.eql(u8, end_tag_name, "target_linux_x86_64")) {
511 target_str = "x86_64-linux";
510512 } else if (mem.eql(u8, end_tag_name, "link_libc")) {
511513 link_libc = true;
512514 } else if (mem.eql(u8, end_tag_name, "code_end")) {
......@@ -526,7 +528,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc {
526528 .is_inline = is_inline,
527529 .mode = mode,
528530 .link_objects = link_objects.toOwnedSlice(),
529 .target_windows = target_windows,
531 .target_str = target_str,
530532 .link_libc = link_libc,
531533 },
532534 });
......@@ -998,7 +1000,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
9981000 try io.writeFile(tmp_source_file_name, trimmed_raw_source);
9991001
10001002 switch (code.id) {
1001 Code.Id.Exe => |expected_outcome| {
1003 Code.Id.Exe => |expected_outcome| code_block: {
10021004 const name_plus_bin_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, exe_ext);
10031005 const tmp_bin_file_name = try os.path.join(
10041006 allocator,
......@@ -1046,8 +1048,20 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
10461048 try build_args.append("c");
10471049 try out.print(" --library c");
10481050 }
1051 if (code.target_str) |triple| {
1052 try build_args.appendSlice([][]const u8{ "-target", triple });
1053 }
10491054 _ = exec(allocator, &env_map, build_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "example failed to compile");
10501055
1056 if (code.target_str) |triple| {
1057 if (mem.startsWith(u8, triple, "x86_64-linux") and
1058 (builtin.os != builtin.Os.linux or builtin.arch != builtin.Arch.x86_64))
1059 {
1060 // skip execution
1061 break :code_block;
1062 }
1063 }
1064
10511065 const run_args = [][]const u8{tmp_bin_file_name};
10521066
10531067 const result = if (expected_outcome == ExpectedOutcome.Fail) blk: {
......@@ -1105,8 +1119,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
11051119 try out.print(" --release-small");
11061120 },
11071121 }
1108 if (code.target_windows) {
1109 try test_args.appendSlice([][]const u8{ "-target", "x86_64-windows" });
1122 if (code.target_str) |triple| {
1123 try test_args.appendSlice([][]const u8{ "-target", triple });
11101124 }
11111125 const result = exec(allocator, &env_map, test_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "test failed");
11121126 const escaped_stderr = try escapeHtml(allocator, result.stderr);
doc/langref.html.in+189-4
......@@ -5396,11 +5396,196 @@ pub fn main() void {
53965396 {#see_also|inline while|inline for#}
53975397 {#header_close#}
53985398 {#header_open|Assembly#}
5399 <p>TODO: example of inline assembly</p>
5400 <p>TODO: example of module level assembly</p>
5401 <p>TODO: example of using inline assembly return value</p>
5402 <p>TODO: example of using inline assembly assigning values to variables</p>
5399 <p>
5400 For some use cases, it may be necessary to directly control the machine code generated
5401 by Zig programs, rather than relying on Zig's code generation. For these cases, one
5402 can use inline assembly. Here is an example of implementing Hello, World on x86_64 Linux
5403 using inline assembly:
5404 </p>
5405 {#code_begin|exe#}
5406 {#target_linux_x86_64#}
5407pub fn main() noreturn {
5408 const msg = "hello world\n";
5409 _ = syscall3(SYS_write, STDOUT_FILENO, @ptrToInt(&msg), msg.len);
5410 _ = syscall1(SYS_exit, 0);
5411 unreachable;
5412}
5413
5414pub const SYS_write = 1;
5415pub const SYS_exit = 60;
5416
5417pub const STDOUT_FILENO = 1;
5418
5419pub fn syscall1(number: usize, arg1: usize) usize {
5420 return asm volatile ("syscall"
5421 : [ret] "={rax}" (-> usize)
5422 : [number] "{rax}" (number),
5423 [arg1] "{rdi}" (arg1)
5424 : "rcx", "r11"
5425 );
5426}
5427
5428pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize {
5429 return asm volatile ("syscall"
5430 : [ret] "={rax}" (-> usize)
5431 : [number] "{rax}" (number),
5432 [arg1] "{rdi}" (arg1),
5433 [arg2] "{rsi}" (arg2),
5434 [arg3] "{rdx}" (arg3)
5435 : "rcx", "r11"
5436 );
5437}
5438 {#code_end#}
5439 <p>
5440 Dissecting the syntax:
5441 </p>
5442 <pre>{#syntax#}// Inline assembly is an expression which returns a value.
5443// the `asm` keyword begins the expression.
5444_ = asm
5445// `volatile` is an optional modifier that tells Zig this
5446// inline assembly expression has side-effects. Without
5447// `volatile`, Zig is allowed to delete the inline assembly
5448// code if the result is unused.
5449volatile (
5450// Next is a comptime string which is the assembly code.
5451// Inside this string one may use `%[ret]`, `%[number]`,
5452// or `%[arg1]` where a register is expected, to specify
5453// the register that Zig uses for the argument or return value,
5454// if the register constraint strings are used. However in
5455// the below code, this is not used. A literal `%` can be
5456// obtained by escaping it with a double percent: `%%`.
5457// Often multiline string syntax comes in handy here.
5458 \\syscall
5459// Next is the output. It is possible in the future Zig will
5460// support multiple outputs, depending on how
5461// https://github.com/ziglang/zig/issues/215 is resolved.
5462// It is allowed for there to be no outputs, in which case
5463// this colon would be directly followed by the colon for the inputs.
5464 :
5465// This specifies the name to be used in `%[ret]` syntax in
5466// the above assembly string. This example does not use it,
5467// but the syntax is mandatory.
5468 [ret]
5469// Next is the output constraint string. This feature is still
5470// considered unstable in Zig, and so LLVM/GCC documentation
5471// must be used to understand the semantics.
5472// http://releases.llvm.org/8.0.0/docs/LangRef.html#inline-asm-constraint-string
5473// https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
5474// In this example, the constraint string means "the result value of
5475// this inline assembly instruction is whatever is in $rax".
5476 "={rax}"
5477// Next is either a value binding, or `->` and then a type. The
5478// type is the result type of the inline assembly expression.
5479// If it is a value binding, then `%[ret]` syntax would be used
5480// to refer to the register bound to the value.
5481 (-> usize)
5482// Next is the list of inputs.
5483// The constraint for these inputs means, "when the assembly code is
5484// executed, $rax shall have the value of `number` and $rdi shall have
5485// the value of `arg1`". Any number of input parameters is allowed,
5486// including none.
5487 : [number] "{rax}" (number),
5488 [arg1] "{rdi}" (arg1)
5489// Next is the list of clobbers. These declare a set of registers whose
5490// values will not be preserved by the execution of this assembly code.
5491// These do not include output or input registers. The special clobber
5492// value of "memory" means that the assembly writes to arbitrary undeclared
5493// memory locations - not only the memory pointed to by a declared indirect
5494// output. In this example we list $rcx and $r11 because it is known the
5495// kernel syscall does not preserve these registers.
5496 : "rcx", "r11"
5497);{#endsyntax#}</pre>
5498 <p>
5499 For i386 and x86_64 targets, the syntax is AT&amp;T syntax, rather than the more
5500 popular Intel syntax. This is due to technical constraints; assembly parsing is
5501 provided by LLVM and its support for Intel syntax is buggy and not well tested.
5502 </p>
5503 <p>
5504 Some day Zig may have its own assembler. This would allow it to integrate more seamlessly
5505 into the language, as well as be compatible with the popular NASM syntax. This documentation
5506 section will be updated before 1.0.0 is released, with a conclusive statement about the status
5507 of AT&amp;T vs Intel/NASM syntax.
5508 </p>
5509 {#header_open|Output Constraints#}
5510 <p>
5511 Output constraints are still considered to be unstable in Zig, and
5512 so
5513 <a href="http://releases.llvm.org/8.0.0/docs/LangRef.html#inline-asm-constraint-string">LLVM documentation</a>
5514 and
5515 <a href="https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html">GCC documentation</a>
5516 must be used to understand the semantics.
5517 </p>
5518 <p>
5519 Note that some breaking changes to output constraints are planned with
5520 <a href="https://github.com/ziglang/zig/issues/215">issue #215</a>.
5521 </p>
5522 {#header_close#}
5523
5524 {#header_open|Input Constraints#}
5525 <p>
5526 Input constraints are still considered to be unstable in Zig, and
5527 so
5528 <a href="http://releases.llvm.org/8.0.0/docs/LangRef.html#inline-asm-constraint-string">LLVM documentation</a>
5529 and
5530 <a href="https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html">GCC documentation</a>
5531 must be used to understand the semantics.
5532 </p>
5533 <p>
5534 Note that some breaking changes to input constraints are planned with
5535 <a href="https://github.com/ziglang/zig/issues/215">issue #215</a>.
5536 </p>
5537 {#header_close#}
5538
5539 {#header_open|Clobbers#}
5540 <p>
5541 Clobbers are the set of registers whose values will not be preserved by the execution of
5542 the assembly code. These do not include output or input registers. The special clobber
5543 value of {#syntax#}"memory"{#endsyntax#} means that the assembly causes writes to
5544 arbitrary undeclared memory locations - not only the memory pointed to by a declared
5545 indirect output.
5546 </p>
5547 <p>
5548 Failure to declare the full set of clobbers for a given inline assembly
5549 expression is unchecked {#link|Undefined Behavior#}.
5550 </p>
5551 {#header_close#}
5552
5553 {#header_open|Global Assembly#}
5554 <p>
5555 When an assembly expression occurs in a top level {#link|comptime#} block, this is
5556 <strong>global assembly</strong>.
5557 </p>
5558 <p>
5559 This kind of assembly has different rules than inline assembly. First, {#syntax#}volatile{#endsyntax#}
5560 is not valid because all global assembly is unconditionally included.
5561 Second, there are no inputs, outputs, or clobbers. All global assembly is concatenated
5562 verbatim into one long string and assembled together. There are no template substitution rules regarding
5563 <code>%</code> as there are in inline assembly expressions.
5564 </p>
5565 {#code_begin|test|global-asm#}
5566 {#target_linux_x86_64#}
5567const std = @import("std");
5568const assert = std.debug.assert;
5569
5570comptime {
5571 asm (
5572 \\.global my_func;
5573 \\.type my_func, @function;
5574 \\my_func:
5575 \\ lea (%rdi,%rsi,1),%eax
5576 \\ retq
5577 );
5578}
5579
5580extern fn my_func(a: i32, b: i32) i32;
5581
5582test "global assembly" {
5583 assert(my_func(12, 34) == 46);
5584}
5585 {#code_end#}
5586 {#header_close#}
54035587 {#header_close#}
5588
54045589 {#header_open|Atomics#}
54055590 <p>TODO: @fence()</p>
54065591 <p>TODO: @atomic rmw</p>
src/all_types.hpp+12-4
......@@ -800,9 +800,8 @@ struct AsmToken {
800800};
801801
802802struct AstNodeAsmExpr {
803 bool is_volatile;
804 Buf *asm_template;
805 ZigList<AsmToken> token_list;
803 Token *volatile_token;
804 Token *asm_template;
806805 ZigList<AsmOutput*> output_list;
807806 ZigList<AsmInput*> input_list;
808807 ZigList<Buf*> clobber_list;
......@@ -2169,6 +2168,7 @@ enum IrInstructionId {
21692168 IrInstructionIdArrayType,
21702169 IrInstructionIdPromiseType,
21712170 IrInstructionIdSliceType,
2171 IrInstructionIdGlobalAsm,
21722172 IrInstructionIdAsm,
21732173 IrInstructionIdSizeOf,
21742174 IrInstructionIdTestNonNull,
......@@ -2677,10 +2677,18 @@ struct IrInstructionSliceType {
26772677 bool allow_zero;
26782678};
26792679
2680struct IrInstructionGlobalAsm {
2681 IrInstruction base;
2682
2683 Buf *asm_code;
2684};
2685
26802686struct IrInstructionAsm {
26812687 IrInstruction base;
26822688
2683 // Most information on inline assembly comes from the source node.
2689 Buf *asm_template;
2690 AsmToken *token_list;
2691 size_t token_list_len;
26842692 IrInstruction **input_list;
26852693 IrInstruction **output_types;
26862694 ZigVar **output_vars;
src/ast_render.cpp+2-2
......@@ -862,8 +862,8 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
862862 case NodeTypeAsmExpr:
863863 {
864864 AstNodeAsmExpr *asm_expr = &node->data.asm_expr;
865 const char *volatile_str = asm_expr->is_volatile ? " volatile" : "";
866 fprintf(ar->f, "asm%s (\"%s\"\n", volatile_str, buf_ptr(asm_expr->asm_template));
865 const char *volatile_str = (asm_expr->volatile_token != nullptr) ? " volatile" : "";
866 fprintf(ar->f, "asm%s (\"%s\"\n", volatile_str, buf_ptr(&asm_expr->asm_template->data.str_lit.str));
867867 print_indent(ar);
868868 fprintf(ar->f, ": ");
869869 for (size_t i = 0; i < asm_expr->output_list.length; i += 1) {
src/codegen.cpp+8-7
......@@ -3793,8 +3793,8 @@ static LLVMValueRef ir_render_union_field_ptr(CodeGen *g, IrExecutable *executab
37933793 return bitcasted_union_field_ptr;
37943794}
37953795
3796static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok) {
3797 const char *ptr = buf_ptr(node->data.asm_expr.asm_template) + tok->start + 2;
3796static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok, Buf *src_template) {
3797 const char *ptr = buf_ptr(src_template) + tok->start + 2;
37983798 size_t len = tok->end - tok->start - 2;
37993799 size_t result = 0;
38003800 for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1, result += 1) {
......@@ -3817,13 +3817,13 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
38173817 assert(asm_node->type == NodeTypeAsmExpr);
38183818 AstNodeAsmExpr *asm_expr = &asm_node->data.asm_expr;
38193819
3820 Buf *src_template = asm_expr->asm_template;
3820 Buf *src_template = instruction->asm_template;
38213821
38223822 Buf llvm_template = BUF_INIT;
38233823 buf_resize(&llvm_template, 0);
38243824
3825 for (size_t token_i = 0; token_i < asm_expr->token_list.length; token_i += 1) {
3826 AsmToken *asm_token = &asm_expr->token_list.at(token_i);
3825 for (size_t token_i = 0; token_i < instruction->token_list_len; token_i += 1) {
3826 AsmToken *asm_token = &instruction->token_list[token_i];
38273827 switch (asm_token->id) {
38283828 case AsmTokenIdTemplate:
38293829 for (size_t offset = asm_token->start; offset < asm_token->end; offset += 1) {
......@@ -3840,7 +3840,7 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
38403840 break;
38413841 case AsmTokenIdVar:
38423842 {
3843 size_t index = find_asm_index(g, asm_node, asm_token);
3843 size_t index = find_asm_index(g, asm_node, asm_token, src_template);
38443844 assert(index < SIZE_MAX);
38453845 buf_appendf(&llvm_template, "$%" ZIG_PRI_usize "", index);
38463846 break;
......@@ -3937,7 +3937,7 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
39373937 }
39383938 LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false);
39393939
3940 bool is_volatile = asm_expr->is_volatile || (asm_expr->output_list.length == 0);
3940 bool is_volatile = instruction->has_side_effects || (asm_expr->output_list.length == 0);
39413941 LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template),
39423942 buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT);
39433943
......@@ -5480,6 +5480,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
54805480 case IrInstructionIdCmpxchgSrc:
54815481 case IrInstructionIdLoadPtr:
54825482 case IrInstructionIdBitCast:
5483 case IrInstructionIdGlobalAsm:
54835484 zig_unreachable();
54845485
54855486 case IrInstructionIdDeclVarGen:
src/ir.cpp+160-28
......@@ -513,6 +513,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSliceType *) {
513513 return IrInstructionIdSliceType;
514514}
515515
516static constexpr IrInstructionId ir_instruction_id(IrInstructionGlobalAsm *) {
517 return IrInstructionIdGlobalAsm;
518}
519
516520static constexpr IrInstructionId ir_instruction_id(IrInstructionAsm *) {
517521 return IrInstructionIdAsm;
518522}
......@@ -1628,10 +1632,21 @@ static IrInstruction *ir_build_slice_type(IrBuilder *irb, Scope *scope, AstNode
16281632 return &instruction->base;
16291633}
16301634
1631static IrInstruction *ir_build_asm(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction **input_list,
1632 IrInstruction **output_types, ZigVar **output_vars, size_t return_count, bool has_side_effects)
1635static IrInstruction *ir_build_global_asm(IrBuilder *irb, Scope *scope, AstNode *source_node, Buf *asm_code) {
1636 IrInstructionGlobalAsm *instruction = ir_build_instruction<IrInstructionGlobalAsm>(irb, scope, source_node);
1637 instruction->asm_code = asm_code;
1638 return &instruction->base;
1639}
1640
1641static IrInstruction *ir_build_asm(IrBuilder *irb, Scope *scope, AstNode *source_node,
1642 Buf *asm_template, AsmToken *token_list, size_t token_list_len,
1643 IrInstruction **input_list, IrInstruction **output_types, ZigVar **output_vars, size_t return_count,
1644 bool has_side_effects)
16331645{
16341646 IrInstructionAsm *instruction = ir_build_instruction<IrInstructionAsm>(irb, scope, source_node);
1647 instruction->asm_template = asm_template;
1648 instruction->token_list = token_list;
1649 instruction->token_list_len = token_list_len;
16351650 instruction->input_list = input_list;
16361651 instruction->output_types = output_types;
16371652 instruction->output_vars = output_vars;
......@@ -5861,21 +5876,142 @@ static IrInstruction *ir_gen_undefined_literal(IrBuilder *irb, Scope *scope, Ast
58615876 return ir_build_const_undefined(irb, scope, node);
58625877}
58635878
5879static Error parse_asm_template(IrBuilder *irb, AstNode *source_node, Buf *asm_template,
5880 ZigList<AsmToken> *tok_list)
5881{
5882 // TODO Connect the errors in this function back up to the actual source location
5883 // rather than just the token. https://github.com/ziglang/zig/issues/2080
5884 enum State {
5885 StateStart,
5886 StatePercent,
5887 StateTemplate,
5888 StateVar,
5889 };
5890
5891 assert(tok_list->length == 0);
5892
5893 AsmToken *cur_tok = nullptr;
5894
5895 enum State state = StateStart;
5896
5897 for (size_t i = 0; i < buf_len(asm_template); i += 1) {
5898 uint8_t c = *((uint8_t*)buf_ptr(asm_template) + i);
5899 switch (state) {
5900 case StateStart:
5901 if (c == '%') {
5902 tok_list->add_one();
5903 cur_tok = &tok_list->last();
5904 cur_tok->id = AsmTokenIdPercent;
5905 cur_tok->start = i;
5906 state = StatePercent;
5907 } else {
5908 tok_list->add_one();
5909 cur_tok = &tok_list->last();
5910 cur_tok->id = AsmTokenIdTemplate;
5911 cur_tok->start = i;
5912 state = StateTemplate;
5913 }
5914 break;
5915 case StatePercent:
5916 if (c == '%') {
5917 cur_tok->end = i;
5918 state = StateStart;
5919 } else if (c == '[') {
5920 cur_tok->id = AsmTokenIdVar;
5921 state = StateVar;
5922 } else if (c == '=') {
5923 cur_tok->id = AsmTokenIdUniqueId;
5924 cur_tok->end = i;
5925 state = StateStart;
5926 } else {
5927 add_node_error(irb->codegen, source_node,
5928 buf_create_from_str("expected a '%' or '['"));
5929 return ErrorSemanticAnalyzeFail;
5930 }
5931 break;
5932 case StateTemplate:
5933 if (c == '%') {
5934 cur_tok->end = i;
5935 i -= 1;
5936 cur_tok = nullptr;
5937 state = StateStart;
5938 }
5939 break;
5940 case StateVar:
5941 if (c == ']') {
5942 cur_tok->end = i;
5943 state = StateStart;
5944 } else if ((c >= 'a' && c <= 'z') ||
5945 (c >= '0' && c <= '9') ||
5946 (c == '_'))
5947 {
5948 // do nothing
5949 } else {
5950 add_node_error(irb->codegen, source_node,
5951 buf_sprintf("invalid substitution character: '%c'", c));
5952 return ErrorSemanticAnalyzeFail;
5953 }
5954 break;
5955 }
5956 }
5957
5958 switch (state) {
5959 case StateStart:
5960 break;
5961 case StatePercent:
5962 case StateVar:
5963 add_node_error(irb->codegen, source_node, buf_sprintf("unexpected end of assembly template"));
5964 return ErrorSemanticAnalyzeFail;
5965 case StateTemplate:
5966 cur_tok->end = buf_len(asm_template);
5967 break;
5968 }
5969 return ErrorNone;
5970}
5971
58645972static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
5973 Error err;
58655974 assert(node->type == NodeTypeAsmExpr);
5975 AstNodeAsmExpr *asm_expr = &node->data.asm_expr;
5976 bool is_volatile = asm_expr->volatile_token != nullptr;
5977 bool in_fn_scope = (scope_fn_entry(scope) != nullptr);
5978
5979 Buf *template_buf = &asm_expr->asm_template->data.str_lit.str;
58665980
5867 IrInstruction **input_list = allocate<IrInstruction *>(node->data.asm_expr.input_list.length);
5868 IrInstruction **output_types = allocate<IrInstruction *>(node->data.asm_expr.output_list.length);
5869 ZigVar **output_vars = allocate<ZigVar *>(node->data.asm_expr.output_list.length);
5981 if (!in_fn_scope) {
5982 if (is_volatile) {
5983 add_token_error(irb->codegen, node->owner, asm_expr->volatile_token,
5984 buf_sprintf("volatile is meaningless on global assembly"));
5985 return irb->codegen->invalid_instruction;
5986 }
5987
5988 if (asm_expr->output_list.length != 0 || asm_expr->input_list.length != 0 ||
5989 asm_expr->clobber_list.length != 0)
5990 {
5991 add_node_error(irb->codegen, node,
5992 buf_sprintf("global assembly cannot have inputs, outputs, or clobbers"));
5993 return irb->codegen->invalid_instruction;
5994 }
5995
5996 return ir_build_global_asm(irb, scope, node, template_buf);
5997 }
5998
5999 ZigList<AsmToken> tok_list = {};
6000 if ((err = parse_asm_template(irb, node, template_buf, &tok_list))) {
6001 return irb->codegen->invalid_instruction;
6002 }
6003
6004 IrInstruction **input_list = allocate<IrInstruction *>(asm_expr->input_list.length);
6005 IrInstruction **output_types = allocate<IrInstruction *>(asm_expr->output_list.length);
6006 ZigVar **output_vars = allocate<ZigVar *>(asm_expr->output_list.length);
58706007 size_t return_count = 0;
5871 bool is_volatile = node->data.asm_expr.is_volatile;
5872 if (!is_volatile && node->data.asm_expr.output_list.length == 0) {
6008 if (!is_volatile && asm_expr->output_list.length == 0) {
58736009 add_node_error(irb->codegen, node,
58746010 buf_sprintf("assembly expression with no output must be marked volatile"));
58756011 return irb->codegen->invalid_instruction;
58766012 }
5877 for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
5878 AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
6013 for (size_t i = 0; i < asm_expr->output_list.length; i += 1) {
6014 AsmOutput *asm_output = asm_expr->output_list.at(i);
58796015 if (asm_output->return_type) {
58806016 return_count += 1;
58816017
......@@ -5911,8 +6047,8 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod
59116047 return irb->codegen->invalid_instruction;
59126048 }
59136049 }
5914 for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
5915 AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
6050 for (size_t i = 0; i < asm_expr->input_list.length; i += 1) {
6051 AsmInput *asm_input = asm_expr->input_list.at(i);
59166052 IrInstruction *input_value = ir_gen_node(irb, asm_input->expr, scope);
59176053 if (input_value == irb->codegen->invalid_instruction)
59186054 return irb->codegen->invalid_instruction;
......@@ -5920,7 +6056,8 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod
59206056 input_list[i] = input_value;
59216057 }
59226058
5923 return ir_build_asm(irb, scope, node, input_list, output_types, output_vars, return_count, is_volatile);
6059 return ir_build_asm(irb, scope, node, template_buf, tok_list.items, tok_list.length,
6060 input_list, output_types, output_vars, return_count, is_volatile);
59246061}
59256062
59266063static IrInstruction *ir_gen_if_optional_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
......@@ -16309,27 +16446,18 @@ static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira,
1630916446 zig_unreachable();
1631016447}
1631116448
16449static IrInstruction *ir_analyze_instruction_global_asm(IrAnalyze *ira, IrInstructionGlobalAsm *instruction) {
16450 buf_append_char(&ira->codegen->global_asm, '\n');
16451 buf_append_buf(&ira->codegen->global_asm, instruction->asm_code);
16452
16453 return ir_const_void(ira, &instruction->base);
16454}
16455
1631216456static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAsm *asm_instruction) {
1631316457 assert(asm_instruction->base.source_node->type == NodeTypeAsmExpr);
1631416458
1631516459 AstNodeAsmExpr *asm_expr = &asm_instruction->base.source_node->data.asm_expr;
1631616460
16317 bool global_scope = (scope_fn_entry(asm_instruction->base.scope) == nullptr);
16318 if (global_scope) {
16319 if (asm_expr->output_list.length != 0 || asm_expr->input_list.length != 0 ||
16320 asm_expr->clobber_list.length != 0)
16321 {
16322 ir_add_error(ira, &asm_instruction->base,
16323 buf_sprintf("global assembly cannot have inputs, outputs, or clobbers"));
16324 return ira->codegen->invalid_instruction;
16325 }
16326
16327 buf_append_char(&ira->codegen->global_asm, '\n');
16328 buf_append_buf(&ira->codegen->global_asm, asm_expr->asm_template);
16329
16330 return ir_const_void(ira, &asm_instruction->base);
16331 }
16332
1633316461 if (!ir_emit_global_runtime_side_effect(ira, &asm_instruction->base))
1633416462 return ira->codegen->invalid_instruction;
1633516463
......@@ -16367,6 +16495,7 @@ static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAs
1636716495
1636816496 IrInstruction *result = ir_build_asm(&ira->new_irb,
1636916497 asm_instruction->base.scope, asm_instruction->base.source_node,
16498 asm_instruction->asm_template, asm_instruction->token_list, asm_instruction->token_list_len,
1637016499 input_list, output_types, asm_instruction->output_vars, asm_instruction->return_count,
1637116500 asm_instruction->has_side_effects);
1637216501 result->value.type = return_type;
......@@ -22584,6 +22713,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio
2258422713 return ir_analyze_instruction_set_float_mode(ira, (IrInstructionSetFloatMode *)instruction);
2258522714 case IrInstructionIdSliceType:
2258622715 return ir_analyze_instruction_slice_type(ira, (IrInstructionSliceType *)instruction);
22716 case IrInstructionIdGlobalAsm:
22717 return ir_analyze_instruction_global_asm(ira, (IrInstructionGlobalAsm *)instruction);
2258722718 case IrInstructionIdAsm:
2258822719 return ir_analyze_instruction_asm(ira, (IrInstructionAsm *)instruction);
2258922720 case IrInstructionIdArrayType:
......@@ -22938,6 +23069,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
2293823069 case IrInstructionIdCmpxchgSrc:
2293923070 case IrInstructionIdAssertZero:
2294023071 case IrInstructionIdResizeSlice:
23072 case IrInstructionIdGlobalAsm:
2294123073 return true;
2294223074
2294323075 case IrInstructionIdPhi:
src/ir_print.cpp+8-1
......@@ -436,11 +436,15 @@ static void ir_print_slice_type(IrPrint *irp, IrInstructionSliceType *instructio
436436 ir_print_other_instruction(irp, instruction->child_type);
437437}
438438
439static void ir_print_global_asm(IrPrint *irp, IrInstructionGlobalAsm *instruction) {
440 fprintf(irp->f, "asm(\"%s\")", buf_ptr(instruction->asm_code));
441}
442
439443static void ir_print_asm(IrPrint *irp, IrInstructionAsm *instruction) {
440444 assert(instruction->base.source_node->type == NodeTypeAsmExpr);
441445 AstNodeAsmExpr *asm_expr = &instruction->base.source_node->data.asm_expr;
442446 const char *volatile_kw = instruction->has_side_effects ? " volatile" : "";
443 fprintf(irp->f, "asm%s (\"%s\") : ", volatile_kw, buf_ptr(asm_expr->asm_template));
447 fprintf(irp->f, "asm%s (\"%s\") : ", volatile_kw, buf_ptr(instruction->asm_template));
444448
445449 for (size_t i = 0; i < asm_expr->output_list.length; i += 1) {
446450 AsmOutput *asm_output = asm_expr->output_list.at(i);
......@@ -1519,6 +1523,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
15191523 case IrInstructionIdSliceType:
15201524 ir_print_slice_type(irp, (IrInstructionSliceType *)instruction);
15211525 break;
1526 case IrInstructionIdGlobalAsm:
1527 ir_print_global_asm(irp, (IrInstructionGlobalAsm *)instruction);
1528 break;
15221529 case IrInstructionIdAsm:
15231530 ir_print_asm(irp, (IrInstructionAsm *)instruction);
15241531 break;
src/parser.cpp+5-111
......@@ -85,7 +85,7 @@ static AstNode *ast_parse_asm_output(ParseContext *pc);
8585static AsmOutput *ast_parse_asm_output_item(ParseContext *pc);
8686static AstNode *ast_parse_asm_input(ParseContext *pc);
8787static AsmInput *ast_parse_asm_input_item(ParseContext *pc);
88static AstNode *ast_parse_asm_cloppers(ParseContext *pc);
88static AstNode *ast_parse_asm_clobbers(ParseContext *pc);
8989static Token *ast_parse_break_label(ParseContext *pc);
9090static Token *ast_parse_block_label(ParseContext *pc);
9191static AstNode *ast_parse_field_init(ParseContext *pc);
......@@ -140,24 +140,6 @@ static void ast_error(ParseContext *pc, Token *token, const char *format, ...) {
140140 exit(EXIT_FAILURE);
141141}
142142
143ATTRIBUTE_PRINTF(4, 5)
144ATTRIBUTE_NORETURN
145static void ast_asm_error(ParseContext *pc, AstNode *node, size_t offset, const char *format, ...) {
146 assert(node->type == NodeTypeAsmExpr);
147 va_list ap;
148 va_start(ap, format);
149 Buf *msg = buf_vprintf(format, ap);
150 va_end(ap);
151
152 ErrorMsg *err = err_msg_create_with_line(pc->owner->data.structure.root_struct->path,
153 node->line, node->column,
154 pc->owner->data.structure.root_struct->source_code,
155 pc->owner->data.structure.root_struct->line_offsets, msg);
156
157 print_err_msg(err, pc->err_color);
158 exit(EXIT_FAILURE);
159}
160
161143static Buf ast_token_str(Buf *input, Token *token) {
162144 Buf str = BUF_INIT;
163145 buf_init_from_mem(&str, buf_ptr(input) + token->start_pos, token->end_pos - token->start_pos);
......@@ -486,93 +468,6 @@ AstNode *ast_parse_bin_op_simple(ParseContext *pc) {
486468 return res;
487469}
488470
489static void ast_parse_asm_template(ParseContext *pc, AstNode *node) {
490 Buf *asm_template = node->data.asm_expr.asm_template;
491
492 enum State {
493 StateStart,
494 StatePercent,
495 StateTemplate,
496 StateVar,
497 };
498
499 ZigList<AsmToken> *tok_list = &node->data.asm_expr.token_list;
500 assert(tok_list->length == 0);
501
502 AsmToken *cur_tok = nullptr;
503
504 enum State state = StateStart;
505
506 for (size_t i = 0; i < buf_len(asm_template); i += 1) {
507 uint8_t c = *((uint8_t*)buf_ptr(asm_template) + i);
508 switch (state) {
509 case StateStart:
510 if (c == '%') {
511 tok_list->add_one();
512 cur_tok = &tok_list->last();
513 cur_tok->id = AsmTokenIdPercent;
514 cur_tok->start = i;
515 state = StatePercent;
516 } else {
517 tok_list->add_one();
518 cur_tok = &tok_list->last();
519 cur_tok->id = AsmTokenIdTemplate;
520 cur_tok->start = i;
521 state = StateTemplate;
522 }
523 break;
524 case StatePercent:
525 if (c == '%') {
526 cur_tok->end = i;
527 state = StateStart;
528 } else if (c == '[') {
529 cur_tok->id = AsmTokenIdVar;
530 state = StateVar;
531 } else if (c == '=') {
532 cur_tok->id = AsmTokenIdUniqueId;
533 cur_tok->end = i;
534 state = StateStart;
535 } else {
536 ast_asm_error(pc, node, i, "expected a '%%' or '['");
537 }
538 break;
539 case StateTemplate:
540 if (c == '%') {
541 cur_tok->end = i;
542 i -= 1;
543 cur_tok = nullptr;
544 state = StateStart;
545 }
546 break;
547 case StateVar:
548 if (c == ']') {
549 cur_tok->end = i;
550 state = StateStart;
551 } else if ((c >= 'a' && c <= 'z') ||
552 (c >= '0' && c <= '9') ||
553 (c == '_'))
554 {
555 // do nothing
556 } else {
557 ast_asm_error(pc, node, i, "invalid substitution character: '%c'", c);
558 }
559 break;
560 }
561 }
562
563 switch (state) {
564 case StateStart:
565 break;
566 case StatePercent:
567 case StateVar:
568 ast_asm_error(pc, node, buf_len(asm_template), "unexpected end of assembly template");
569 break;
570 case StateTemplate:
571 cur_tok->end = buf_len(asm_template);
572 break;
573 }
574}
575
576471AstNode *ast_parse(Buf *buf, ZigList<Token> *tokens, ZigType *owner, ErrColor err_color) {
577472 ParseContext pc = {};
578473 pc.err_color = err_color;
......@@ -1931,9 +1826,8 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc) {
19311826
19321827 res->line = asm_token->start_line;
19331828 res->column = asm_token->start_column;
1934 res->data.asm_expr.is_volatile = volatile_token != nullptr;
1935 res->data.asm_expr.asm_template = token_buf(asm_template);
1936 ast_parse_asm_template(pc, res);
1829 res->data.asm_expr.volatile_token = volatile_token;
1830 res->data.asm_expr.asm_template = asm_template;
19371831 return res;
19381832}
19391833
......@@ -1985,7 +1879,7 @@ static AstNode *ast_parse_asm_input(ParseContext *pc) {
19851879 return nullptr;
19861880
19871881 ZigList<AsmInput *> input_list = ast_parse_list(pc, TokenIdComma, ast_parse_asm_input_item);
1988 AstNode *res = ast_parse_asm_cloppers(pc);
1882 AstNode *res = ast_parse_asm_clobbers(pc);
19891883 if (res == nullptr)
19901884 res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr);
19911885
......@@ -2013,7 +1907,7 @@ static AsmInput *ast_parse_asm_input_item(ParseContext *pc) {
20131907}
20141908
20151909// AsmClobbers <- COLON StringList
2016static AstNode *ast_parse_asm_cloppers(ParseContext *pc) {
1910static AstNode *ast_parse_asm_clobbers(ParseContext *pc) {
20171911 if (eat_token_if(pc, TokenIdColon) == nullptr)
20181912 return nullptr;
20191913
test/compile_errors.zig+9
......@@ -2,6 +2,15 @@ const tests = @import("tests.zig");
22const builtin = @import("builtin");
33
44pub fn addCases(cases: *tests.CompileErrorContext) void {
5 cases.add(
6 "volatile on global assembly",
7 \\comptime {
8 \\ asm volatile ("");
9 \\}
10 ,
11 "tmp.zig:2:9: error: volatile is meaningless on global assembly",
12 );
13
514 cases.add(
615 "invalid multiple dereferences",
716 \\export fn a() void {
test/stage1/behavior/asm.zig+1-1
......@@ -3,7 +3,7 @@ const expect = @import("std").testing.expect;
33
44comptime {
55 if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) {
6 asm volatile (
6 asm (
77 \\.globl this_is_my_alias;
88 \\.type this_is_my_alias, @function;
99 \\.set this_is_my_alias, derp;