| author | |
| committer | |
| log | 15c316b0d8152c0a1ad5b4b26efdf3fdc8cfb4c0 |
| tree | 0bd91c0ba2a63ed3495e94385fbedaa1e3a6fde0 |
| parent | 3c7555cb679492f3f1c0ce320cbdf4a3769e56db |
| signature |
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 #151510 files changed, 415 insertions(+), 165 deletions(-)
doc/docgen.zig+21-7| ... | @@ -274,7 +274,7 @@ const Code = struct { | ... | @@ -274,7 +274,7 @@ const Code = struct { |
| 274 | is_inline: bool, | 274 | is_inline: bool, |
| 275 | mode: builtin.Mode, | 275 | mode: builtin.Mode, |
| 276 | link_objects: []const []const u8, | 276 | link_objects: []const []const u8, |
| 277 | target_windows: bool, | 277 | target_str: ?[]const u8, |
| 278 | link_libc: bool, | 278 | link_libc: bool, |
| 279 | 279 | ||
| 280 | const Id = union(enum) { | 280 | const Id = union(enum) { |
| ... | @@ -491,7 +491,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { | ... | @@ -491,7 +491,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { |
| 491 | var mode = builtin.Mode.Debug; | 491 | var mode = builtin.Mode.Debug; |
| 492 | var link_objects = std.ArrayList([]const u8).init(allocator); | 492 | var link_objects = std.ArrayList([]const u8).init(allocator); |
| 493 | defer link_objects.deinit(); | 493 | defer link_objects.deinit(); |
| 494 | var target_windows = false; | 494 | var target_str: ?[]const u8 = null; |
| 495 | var link_libc = false; | 495 | var link_libc = false; |
| 496 | 496 | ||
| 497 | const source_token = while (true) { | 497 | const source_token = while (true) { |
| ... | @@ -506,7 +506,9 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { | ... | @@ -506,7 +506,9 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { |
| 506 | const obj_tok = try eatToken(tokenizer, Token.Id.TagContent); | 506 | const obj_tok = try eatToken(tokenizer, Token.Id.TagContent); |
| 507 | try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]); | 507 | try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]); |
| 508 | } else if (mem.eql(u8, end_tag_name, "target_windows")) { | 508 | } 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"; | ||
| 510 | } else if (mem.eql(u8, end_tag_name, "link_libc")) { | 512 | } else if (mem.eql(u8, end_tag_name, "link_libc")) { |
| 511 | link_libc = true; | 513 | link_libc = true; |
| 512 | } else if (mem.eql(u8, end_tag_name, "code_end")) { | 514 | } else if (mem.eql(u8, end_tag_name, "code_end")) { |
| ... | @@ -526,7 +528,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { | ... | @@ -526,7 +528,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { |
| 526 | .is_inline = is_inline, | 528 | .is_inline = is_inline, |
| 527 | .mode = mode, | 529 | .mode = mode, |
| 528 | .link_objects = link_objects.toOwnedSlice(), | 530 | .link_objects = link_objects.toOwnedSlice(), |
| 529 | .target_windows = target_windows, | 531 | .target_str = target_str, |
| 530 | .link_libc = link_libc, | 532 | .link_libc = link_libc, |
| 531 | }, | 533 | }, |
| 532 | }); | 534 | }); |
| ... | @@ -998,7 +1000,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var | ... | @@ -998,7 +1000,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var |
| 998 | try io.writeFile(tmp_source_file_name, trimmed_raw_source); | 1000 | try io.writeFile(tmp_source_file_name, trimmed_raw_source); |
| 999 | 1001 | ||
| 1000 | switch (code.id) { | 1002 | switch (code.id) { |
| 1001 | Code.Id.Exe => |expected_outcome| { | 1003 | Code.Id.Exe => |expected_outcome| code_block: { |
| 1002 | const name_plus_bin_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, exe_ext); | 1004 | const name_plus_bin_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, exe_ext); |
| 1003 | const tmp_bin_file_name = try os.path.join( | 1005 | const tmp_bin_file_name = try os.path.join( |
| 1004 | allocator, | 1006 | allocator, |
| ... | @@ -1046,8 +1048,20 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var | ... | @@ -1046,8 +1048,20 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var |
| 1046 | try build_args.append("c"); | 1048 | try build_args.append("c"); |
| 1047 | try out.print(" --library c"); | 1049 | try out.print(" --library c"); |
| 1048 | } | 1050 | } |
| 1051 | if (code.target_str) |triple| { | ||
| 1052 | try build_args.appendSlice([][]const u8{ "-target", triple }); | ||
| 1053 | } | ||
| 1049 | _ = exec(allocator, &env_map, build_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "example failed to compile"); | 1054 | _ = exec(allocator, &env_map, build_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "example failed to compile"); |
| 1050 | 1055 | ||
| 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 | |||
| 1051 | const run_args = [][]const u8{tmp_bin_file_name}; | 1065 | const run_args = [][]const u8{tmp_bin_file_name}; |
| 1052 | 1066 | ||
| 1053 | const result = if (expected_outcome == ExpectedOutcome.Fail) blk: { | 1067 | const result = if (expected_outcome == ExpectedOutcome.Fail) blk: { |
| ... | @@ -1105,8 +1119,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var | ... | @@ -1105,8 +1119,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var |
| 1105 | try out.print(" --release-small"); | 1119 | try out.print(" --release-small"); |
| 1106 | }, | 1120 | }, |
| 1107 | } | 1121 | } |
| 1108 | if (code.target_windows) { | 1122 | if (code.target_str) |triple| { |
| 1109 | try test_args.appendSlice([][]const u8{ "-target", "x86_64-windows" }); | 1123 | try test_args.appendSlice([][]const u8{ "-target", triple }); |
| 1110 | } | 1124 | } |
| 1111 | const result = exec(allocator, &env_map, test_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "test failed"); | 1125 | const result = exec(allocator, &env_map, test_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "test failed"); |
| 1112 | const escaped_stderr = try escapeHtml(allocator, result.stderr); | 1126 | const escaped_stderr = try escapeHtml(allocator, result.stderr); |
doc/langref.html.in+189-4| ... | @@ -5396,11 +5396,196 @@ pub fn main() void { | ... | @@ -5396,11 +5396,196 @@ pub fn main() void { |
| 5396 | {#see_also|inline while|inline for#} | 5396 | {#see_also|inline while|inline for#} |
| 5397 | {#header_close#} | 5397 | {#header_close#} |
| 5398 | {#header_open|Assembly#} | 5398 | {#header_open|Assembly#} |
| 5399 | <p>TODO: example of inline assembly</p> | 5399 | <p> |
| 5400 | <p>TODO: example of module level assembly</p> | 5400 | For some use cases, it may be necessary to directly control the machine code generated |
| 5401 | <p>TODO: example of using inline assembly return value</p> | 5401 | by Zig programs, rather than relying on Zig's code generation. For these cases, one |
| 5402 | <p>TODO: example of using inline assembly assigning values to variables</p> | 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#} | ||
| 5407 | pub 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 | |||
| 5414 | pub const SYS_write = 1; | ||
| 5415 | pub const SYS_exit = 60; | ||
| 5416 | |||
| 5417 | pub const STDOUT_FILENO = 1; | ||
| 5418 | |||
| 5419 | pub 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 | |||
| 5428 | pub 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. | ||
| 5449 | volatile ( | ||
| 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#} | ||
| 5567 | const std = @import("std"); | ||
| 5568 | const assert = std.debug.assert; | ||
| 5569 | |||
| 5570 | comptime { | ||
| 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 | |||
| 5580 | extern fn my_func(a: i32, b: i32) i32; | ||
| 5581 | |||
| 5582 | test "global assembly" { | ||
| 5583 | assert(my_func(12, 34) == 46); | ||
| 5584 | } | ||
| 5585 | {#code_end#} | ||
| 5586 | {#header_close#} | ||
| 5403 | {#header_close#} | 5587 | {#header_close#} |
| 5588 | |||
| 5404 | {#header_open|Atomics#} | 5589 | {#header_open|Atomics#} |
| 5405 | <p>TODO: @fence()</p> | 5590 | <p>TODO: @fence()</p> |
| 5406 | <p>TODO: @atomic rmw</p> | 5591 | <p>TODO: @atomic rmw</p> |
src/all_types.hpp+12-4| ... | @@ -800,9 +800,8 @@ struct AsmToken { | ... | @@ -800,9 +800,8 @@ struct AsmToken { |
| 800 | }; | 800 | }; |
| 801 | 801 | ||
| 802 | struct AstNodeAsmExpr { | 802 | struct AstNodeAsmExpr { |
| 803 | bool is_volatile; | 803 | Token *volatile_token; |
| 804 | Buf *asm_template; | 804 | Token *asm_template; |
| 805 | ZigList<AsmToken> token_list; | ||
| 806 | ZigList<AsmOutput*> output_list; | 805 | ZigList<AsmOutput*> output_list; |
| 807 | ZigList<AsmInput*> input_list; | 806 | ZigList<AsmInput*> input_list; |
| 808 | ZigList<Buf*> clobber_list; | 807 | ZigList<Buf*> clobber_list; |
| ... | @@ -2169,6 +2168,7 @@ enum IrInstructionId { | ... | @@ -2169,6 +2168,7 @@ enum IrInstructionId { |
| 2169 | IrInstructionIdArrayType, | 2168 | IrInstructionIdArrayType, |
| 2170 | IrInstructionIdPromiseType, | 2169 | IrInstructionIdPromiseType, |
| 2171 | IrInstructionIdSliceType, | 2170 | IrInstructionIdSliceType, |
| 2171 | IrInstructionIdGlobalAsm, | ||
| 2172 | IrInstructionIdAsm, | 2172 | IrInstructionIdAsm, |
| 2173 | IrInstructionIdSizeOf, | 2173 | IrInstructionIdSizeOf, |
| 2174 | IrInstructionIdTestNonNull, | 2174 | IrInstructionIdTestNonNull, |
| ... | @@ -2677,10 +2677,18 @@ struct IrInstructionSliceType { | ... | @@ -2677,10 +2677,18 @@ struct IrInstructionSliceType { |
| 2677 | bool allow_zero; | 2677 | bool allow_zero; |
| 2678 | }; | 2678 | }; |
| 2679 | 2679 | ||
| 2680 | struct IrInstructionGlobalAsm { | ||
| 2681 | IrInstruction base; | ||
| 2682 | |||
| 2683 | Buf *asm_code; | ||
| 2684 | }; | ||
| 2685 | |||
| 2680 | struct IrInstructionAsm { | 2686 | struct IrInstructionAsm { |
| 2681 | IrInstruction base; | 2687 | IrInstruction base; |
| 2682 | 2688 | ||
| 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; | ||
| 2684 | IrInstruction **input_list; | 2692 | IrInstruction **input_list; |
| 2685 | IrInstruction **output_types; | 2693 | IrInstruction **output_types; |
| 2686 | ZigVar **output_vars; | 2694 | ZigVar **output_vars; |
src/ast_render.cpp+2-2| ... | @@ -862,8 +862,8 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { | ... | @@ -862,8 +862,8 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { |
| 862 | case NodeTypeAsmExpr: | 862 | case NodeTypeAsmExpr: |
| 863 | { | 863 | { |
| 864 | AstNodeAsmExpr *asm_expr = &node->data.asm_expr; | 864 | AstNodeAsmExpr *asm_expr = &node->data.asm_expr; |
| 865 | const char *volatile_str = asm_expr->is_volatile ? " volatile" : ""; | 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)); | 866 | fprintf(ar->f, "asm%s (\"%s\"\n", volatile_str, buf_ptr(&asm_expr->asm_template->data.str_lit.str)); |
| 867 | print_indent(ar); | 867 | print_indent(ar); |
| 868 | fprintf(ar->f, ": "); | 868 | fprintf(ar->f, ": "); |
| 869 | for (size_t i = 0; i < asm_expr->output_list.length; i += 1) { | 869 | 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 | ... | @@ -3793,8 +3793,8 @@ static LLVMValueRef ir_render_union_field_ptr(CodeGen *g, IrExecutable *executab |
| 3793 | return bitcasted_union_field_ptr; | 3793 | return bitcasted_union_field_ptr; |
| 3794 | } | 3794 | } |
| 3795 | 3795 | ||
| 3796 | static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok) { | 3796 | static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok, Buf *src_template) { |
| 3797 | const char *ptr = buf_ptr(node->data.asm_expr.asm_template) + tok->start + 2; | 3797 | const char *ptr = buf_ptr(src_template) + tok->start + 2; |
| 3798 | size_t len = tok->end - tok->start - 2; | 3798 | size_t len = tok->end - tok->start - 2; |
| 3799 | size_t result = 0; | 3799 | size_t result = 0; |
| 3800 | for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1, result += 1) { | 3800 | 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 | ... | @@ -3817,13 +3817,13 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru |
| 3817 | assert(asm_node->type == NodeTypeAsmExpr); | 3817 | assert(asm_node->type == NodeTypeAsmExpr); |
| 3818 | AstNodeAsmExpr *asm_expr = &asm_node->data.asm_expr; | 3818 | AstNodeAsmExpr *asm_expr = &asm_node->data.asm_expr; |
| 3819 | 3819 | ||
| 3820 | Buf *src_template = asm_expr->asm_template; | 3820 | Buf *src_template = instruction->asm_template; |
| 3821 | 3821 | ||
| 3822 | Buf llvm_template = BUF_INIT; | 3822 | Buf llvm_template = BUF_INIT; |
| 3823 | buf_resize(&llvm_template, 0); | 3823 | buf_resize(&llvm_template, 0); |
| 3824 | 3824 | ||
| 3825 | for (size_t token_i = 0; token_i < asm_expr->token_list.length; token_i += 1) { | 3825 | for (size_t token_i = 0; token_i < instruction->token_list_len; token_i += 1) { |
| 3826 | AsmToken *asm_token = &asm_expr->token_list.at(token_i); | 3826 | AsmToken *asm_token = &instruction->token_list[token_i]; |
| 3827 | switch (asm_token->id) { | 3827 | switch (asm_token->id) { |
| 3828 | case AsmTokenIdTemplate: | 3828 | case AsmTokenIdTemplate: |
| 3829 | for (size_t offset = asm_token->start; offset < asm_token->end; offset += 1) { | 3829 | 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 | ... | @@ -3840,7 +3840,7 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru |
| 3840 | break; | 3840 | break; |
| 3841 | case AsmTokenIdVar: | 3841 | case AsmTokenIdVar: |
| 3842 | { | 3842 | { |
| 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); |
| 3844 | assert(index < SIZE_MAX); | 3844 | assert(index < SIZE_MAX); |
| 3845 | buf_appendf(&llvm_template, "$%" ZIG_PRI_usize "", index); | 3845 | buf_appendf(&llvm_template, "$%" ZIG_PRI_usize "", index); |
| 3846 | break; | 3846 | break; |
| ... | @@ -3937,7 +3937,7 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru | ... | @@ -3937,7 +3937,7 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru |
| 3937 | } | 3937 | } |
| 3938 | LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false); | 3938 | LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false); |
| 3939 | 3939 | ||
| 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); |
| 3941 | LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template), | 3941 | LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template), |
| 3942 | buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT); | 3942 | buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT); |
| 3943 | 3943 | ||
| ... | @@ -5480,6 +5480,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, | ... | @@ -5480,6 +5480,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, |
| 5480 | case IrInstructionIdCmpxchgSrc: | 5480 | case IrInstructionIdCmpxchgSrc: |
| 5481 | case IrInstructionIdLoadPtr: | 5481 | case IrInstructionIdLoadPtr: |
| 5482 | case IrInstructionIdBitCast: | 5482 | case IrInstructionIdBitCast: |
| 5483 | case IrInstructionIdGlobalAsm: | ||
| 5483 | zig_unreachable(); | 5484 | zig_unreachable(); |
| 5484 | 5485 | ||
| 5485 | case IrInstructionIdDeclVarGen: | 5486 | case IrInstructionIdDeclVarGen: |
src/ir.cpp+160-28| ... | @@ -513,6 +513,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSliceType *) { | ... | @@ -513,6 +513,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSliceType *) { |
| 513 | return IrInstructionIdSliceType; | 513 | return IrInstructionIdSliceType; |
| 514 | } | 514 | } |
| 515 | 515 | ||
| 516 | static constexpr IrInstructionId ir_instruction_id(IrInstructionGlobalAsm *) { | ||
| 517 | return IrInstructionIdGlobalAsm; | ||
| 518 | } | ||
| 519 | |||
| 516 | static constexpr IrInstructionId ir_instruction_id(IrInstructionAsm *) { | 520 | static constexpr IrInstructionId ir_instruction_id(IrInstructionAsm *) { |
| 517 | return IrInstructionIdAsm; | 521 | return IrInstructionIdAsm; |
| 518 | } | 522 | } |
| ... | @@ -1628,10 +1632,21 @@ static IrInstruction *ir_build_slice_type(IrBuilder *irb, Scope *scope, AstNode | ... | @@ -1628,10 +1632,21 @@ static IrInstruction *ir_build_slice_type(IrBuilder *irb, Scope *scope, AstNode |
| 1628 | return &instruction->base; | 1632 | return &instruction->base; |
| 1629 | } | 1633 | } |
| 1630 | 1634 | ||
| 1631 | static IrInstruction *ir_build_asm(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction **input_list, | 1635 | static IrInstruction *ir_build_global_asm(IrBuilder *irb, Scope *scope, AstNode *source_node, Buf *asm_code) { |
| 1632 | IrInstruction **output_types, ZigVar **output_vars, size_t return_count, bool has_side_effects) | 1636 | IrInstructionGlobalAsm *instruction = ir_build_instruction<IrInstructionGlobalAsm>(irb, scope, source_node); |
| 1637 | instruction->asm_code = asm_code; | ||
| 1638 | return &instruction->base; | ||
| 1639 | } | ||
| 1640 | |||
| 1641 | static 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) | ||
| 1633 | { | 1645 | { |
| 1634 | IrInstructionAsm *instruction = ir_build_instruction<IrInstructionAsm>(irb, scope, source_node); | 1646 | 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; | ||
| 1635 | instruction->input_list = input_list; | 1650 | instruction->input_list = input_list; |
| 1636 | instruction->output_types = output_types; | 1651 | instruction->output_types = output_types; |
| 1637 | instruction->output_vars = output_vars; | 1652 | instruction->output_vars = output_vars; |
| ... | @@ -5861,21 +5876,142 @@ static IrInstruction *ir_gen_undefined_literal(IrBuilder *irb, Scope *scope, Ast | ... | @@ -5861,21 +5876,142 @@ static IrInstruction *ir_gen_undefined_literal(IrBuilder *irb, Scope *scope, Ast |
| 5861 | return ir_build_const_undefined(irb, scope, node); | 5876 | return ir_build_const_undefined(irb, scope, node); |
| 5862 | } | 5877 | } |
| 5863 | 5878 | ||
| 5879 | static 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 | |||
| 5864 | static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) { | 5972 | static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) { |
| 5973 | Error err; | ||
| 5865 | assert(node->type == NodeTypeAsmExpr); | 5974 | 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; | ||
| 5866 | 5980 | ||
| 5867 | IrInstruction **input_list = allocate<IrInstruction *>(node->data.asm_expr.input_list.length); | 5981 | if (!in_fn_scope) { |
| 5868 | IrInstruction **output_types = allocate<IrInstruction *>(node->data.asm_expr.output_list.length); | 5982 | if (is_volatile) { |
| 5869 | ZigVar **output_vars = allocate<ZigVar *>(node->data.asm_expr.output_list.length); | 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); | ||
| 5870 | size_t return_count = 0; | 6007 | size_t return_count = 0; |
| 5871 | bool is_volatile = node->data.asm_expr.is_volatile; | 6008 | if (!is_volatile && asm_expr->output_list.length == 0) { |
| 5872 | if (!is_volatile && node->data.asm_expr.output_list.length == 0) { | ||
| 5873 | add_node_error(irb->codegen, node, | 6009 | add_node_error(irb->codegen, node, |
| 5874 | buf_sprintf("assembly expression with no output must be marked volatile")); | 6010 | buf_sprintf("assembly expression with no output must be marked volatile")); |
| 5875 | return irb->codegen->invalid_instruction; | 6011 | return irb->codegen->invalid_instruction; |
| 5876 | } | 6012 | } |
| 5877 | for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1) { | 6013 | for (size_t i = 0; i < asm_expr->output_list.length; i += 1) { |
| 5878 | AsmOutput *asm_output = node->data.asm_expr.output_list.at(i); | 6014 | AsmOutput *asm_output = asm_expr->output_list.at(i); |
| 5879 | if (asm_output->return_type) { | 6015 | if (asm_output->return_type) { |
| 5880 | return_count += 1; | 6016 | return_count += 1; |
| 5881 | 6017 | ||
| ... | @@ -5911,8 +6047,8 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod | ... | @@ -5911,8 +6047,8 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod |
| 5911 | return irb->codegen->invalid_instruction; | 6047 | return irb->codegen->invalid_instruction; |
| 5912 | } | 6048 | } |
| 5913 | } | 6049 | } |
| 5914 | for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) { | 6050 | for (size_t i = 0; i < asm_expr->input_list.length; i += 1) { |
| 5915 | AsmInput *asm_input = node->data.asm_expr.input_list.at(i); | 6051 | AsmInput *asm_input = asm_expr->input_list.at(i); |
| 5916 | IrInstruction *input_value = ir_gen_node(irb, asm_input->expr, scope); | 6052 | IrInstruction *input_value = ir_gen_node(irb, asm_input->expr, scope); |
| 5917 | if (input_value == irb->codegen->invalid_instruction) | 6053 | if (input_value == irb->codegen->invalid_instruction) |
| 5918 | return irb->codegen->invalid_instruction; | 6054 | return irb->codegen->invalid_instruction; |
| ... | @@ -5920,7 +6056,8 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod | ... | @@ -5920,7 +6056,8 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod |
| 5920 | input_list[i] = input_value; | 6056 | input_list[i] = input_value; |
| 5921 | } | 6057 | } |
| 5922 | 6058 | ||
| 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); | ||
| 5924 | } | 6061 | } |
| 5925 | 6062 | ||
| 5926 | static IrInstruction *ir_gen_if_optional_expr(IrBuilder *irb, Scope *scope, AstNode *node) { | 6063 | static 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, | ... | @@ -16309,27 +16446,18 @@ static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira, |
| 16309 | zig_unreachable(); | 16446 | zig_unreachable(); |
| 16310 | } | 16447 | } |
| 16311 | 16448 | ||
| 16449 | static 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 | |||
| 16312 | static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAsm *asm_instruction) { | 16456 | static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAsm *asm_instruction) { |
| 16313 | assert(asm_instruction->base.source_node->type == NodeTypeAsmExpr); | 16457 | assert(asm_instruction->base.source_node->type == NodeTypeAsmExpr); |
| 16314 | 16458 | ||
| 16315 | AstNodeAsmExpr *asm_expr = &asm_instruction->base.source_node->data.asm_expr; | 16459 | AstNodeAsmExpr *asm_expr = &asm_instruction->base.source_node->data.asm_expr; |
| 16316 | 16460 | ||
| 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 | |||
| 16333 | if (!ir_emit_global_runtime_side_effect(ira, &asm_instruction->base)) | 16461 | if (!ir_emit_global_runtime_side_effect(ira, &asm_instruction->base)) |
| 16334 | return ira->codegen->invalid_instruction; | 16462 | return ira->codegen->invalid_instruction; |
| 16335 | 16463 | ||
| ... | @@ -16367,6 +16495,7 @@ static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAs | ... | @@ -16367,6 +16495,7 @@ static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAs |
| 16367 | 16495 | ||
| 16368 | IrInstruction *result = ir_build_asm(&ira->new_irb, | 16496 | IrInstruction *result = ir_build_asm(&ira->new_irb, |
| 16369 | asm_instruction->base.scope, asm_instruction->base.source_node, | 16497 | asm_instruction->base.scope, asm_instruction->base.source_node, |
| 16498 | asm_instruction->asm_template, asm_instruction->token_list, asm_instruction->token_list_len, | ||
| 16370 | input_list, output_types, asm_instruction->output_vars, asm_instruction->return_count, | 16499 | input_list, output_types, asm_instruction->output_vars, asm_instruction->return_count, |
| 16371 | asm_instruction->has_side_effects); | 16500 | asm_instruction->has_side_effects); |
| 16372 | result->value.type = return_type; | 16501 | result->value.type = return_type; |
| ... | @@ -22584,6 +22713,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio | ... | @@ -22584,6 +22713,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio |
| 22584 | return ir_analyze_instruction_set_float_mode(ira, (IrInstructionSetFloatMode *)instruction); | 22713 | return ir_analyze_instruction_set_float_mode(ira, (IrInstructionSetFloatMode *)instruction); |
| 22585 | case IrInstructionIdSliceType: | 22714 | case IrInstructionIdSliceType: |
| 22586 | return ir_analyze_instruction_slice_type(ira, (IrInstructionSliceType *)instruction); | 22715 | return ir_analyze_instruction_slice_type(ira, (IrInstructionSliceType *)instruction); |
| 22716 | case IrInstructionIdGlobalAsm: | ||
| 22717 | return ir_analyze_instruction_global_asm(ira, (IrInstructionGlobalAsm *)instruction); | ||
| 22587 | case IrInstructionIdAsm: | 22718 | case IrInstructionIdAsm: |
| 22588 | return ir_analyze_instruction_asm(ira, (IrInstructionAsm *)instruction); | 22719 | return ir_analyze_instruction_asm(ira, (IrInstructionAsm *)instruction); |
| 22589 | case IrInstructionIdArrayType: | 22720 | case IrInstructionIdArrayType: |
| ... | @@ -22938,6 +23069,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { | ... | @@ -22938,6 +23069,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { |
| 22938 | case IrInstructionIdCmpxchgSrc: | 23069 | case IrInstructionIdCmpxchgSrc: |
| 22939 | case IrInstructionIdAssertZero: | 23070 | case IrInstructionIdAssertZero: |
| 22940 | case IrInstructionIdResizeSlice: | 23071 | case IrInstructionIdResizeSlice: |
| 23072 | case IrInstructionIdGlobalAsm: | ||
| 22941 | return true; | 23073 | return true; |
| 22942 | 23074 | ||
| 22943 | case IrInstructionIdPhi: | 23075 | case IrInstructionIdPhi: |
src/ir_print.cpp+8-1| ... | @@ -436,11 +436,15 @@ static void ir_print_slice_type(IrPrint *irp, IrInstructionSliceType *instructio | ... | @@ -436,11 +436,15 @@ static void ir_print_slice_type(IrPrint *irp, IrInstructionSliceType *instructio |
| 436 | ir_print_other_instruction(irp, instruction->child_type); | 436 | ir_print_other_instruction(irp, instruction->child_type); |
| 437 | } | 437 | } |
| 438 | 438 | ||
| 439 | static void ir_print_global_asm(IrPrint *irp, IrInstructionGlobalAsm *instruction) { | ||
| 440 | fprintf(irp->f, "asm(\"%s\")", buf_ptr(instruction->asm_code)); | ||
| 441 | } | ||
| 442 | |||
| 439 | static void ir_print_asm(IrPrint *irp, IrInstructionAsm *instruction) { | 443 | static void ir_print_asm(IrPrint *irp, IrInstructionAsm *instruction) { |
| 440 | assert(instruction->base.source_node->type == NodeTypeAsmExpr); | 444 | assert(instruction->base.source_node->type == NodeTypeAsmExpr); |
| 441 | AstNodeAsmExpr *asm_expr = &instruction->base.source_node->data.asm_expr; | 445 | AstNodeAsmExpr *asm_expr = &instruction->base.source_node->data.asm_expr; |
| 442 | const char *volatile_kw = instruction->has_side_effects ? " volatile" : ""; | 446 | 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)); |
| 444 | 448 | ||
| 445 | for (size_t i = 0; i < asm_expr->output_list.length; i += 1) { | 449 | for (size_t i = 0; i < asm_expr->output_list.length; i += 1) { |
| 446 | AsmOutput *asm_output = asm_expr->output_list.at(i); | 450 | AsmOutput *asm_output = asm_expr->output_list.at(i); |
| ... | @@ -1519,6 +1523,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { | ... | @@ -1519,6 +1523,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { |
| 1519 | case IrInstructionIdSliceType: | 1523 | case IrInstructionIdSliceType: |
| 1520 | ir_print_slice_type(irp, (IrInstructionSliceType *)instruction); | 1524 | ir_print_slice_type(irp, (IrInstructionSliceType *)instruction); |
| 1521 | break; | 1525 | break; |
| 1526 | case IrInstructionIdGlobalAsm: | ||
| 1527 | ir_print_global_asm(irp, (IrInstructionGlobalAsm *)instruction); | ||
| 1528 | break; | ||
| 1522 | case IrInstructionIdAsm: | 1529 | case IrInstructionIdAsm: |
| 1523 | ir_print_asm(irp, (IrInstructionAsm *)instruction); | 1530 | ir_print_asm(irp, (IrInstructionAsm *)instruction); |
| 1524 | break; | 1531 | break; |
src/parser.cpp+5-111| ... | @@ -85,7 +85,7 @@ static AstNode *ast_parse_asm_output(ParseContext *pc); | ... | @@ -85,7 +85,7 @@ static AstNode *ast_parse_asm_output(ParseContext *pc); |
| 85 | static AsmOutput *ast_parse_asm_output_item(ParseContext *pc); | 85 | static AsmOutput *ast_parse_asm_output_item(ParseContext *pc); |
| 86 | static AstNode *ast_parse_asm_input(ParseContext *pc); | 86 | static AstNode *ast_parse_asm_input(ParseContext *pc); |
| 87 | static AsmInput *ast_parse_asm_input_item(ParseContext *pc); | 87 | static AsmInput *ast_parse_asm_input_item(ParseContext *pc); |
| 88 | static AstNode *ast_parse_asm_cloppers(ParseContext *pc); | 88 | static AstNode *ast_parse_asm_clobbers(ParseContext *pc); |
| 89 | static Token *ast_parse_break_label(ParseContext *pc); | 89 | static Token *ast_parse_break_label(ParseContext *pc); |
| 90 | static Token *ast_parse_block_label(ParseContext *pc); | 90 | static Token *ast_parse_block_label(ParseContext *pc); |
| 91 | static AstNode *ast_parse_field_init(ParseContext *pc); | 91 | static AstNode *ast_parse_field_init(ParseContext *pc); |
| ... | @@ -140,24 +140,6 @@ static void ast_error(ParseContext *pc, Token *token, const char *format, ...) { | ... | @@ -140,24 +140,6 @@ static void ast_error(ParseContext *pc, Token *token, const char *format, ...) { |
| 140 | exit(EXIT_FAILURE); | 140 | exit(EXIT_FAILURE); |
| 141 | } | 141 | } |
| 142 | 142 | ||
| 143 | ATTRIBUTE_PRINTF(4, 5) | ||
| 144 | ATTRIBUTE_NORETURN | ||
| 145 | static 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 | |||
| 161 | static Buf ast_token_str(Buf *input, Token *token) { | 143 | static Buf ast_token_str(Buf *input, Token *token) { |
| 162 | Buf str = BUF_INIT; | 144 | Buf str = BUF_INIT; |
| 163 | buf_init_from_mem(&str, buf_ptr(input) + token->start_pos, token->end_pos - token->start_pos); | 145 | 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) { | ... | @@ -486,93 +468,6 @@ AstNode *ast_parse_bin_op_simple(ParseContext *pc) { |
| 486 | return res; | 468 | return res; |
| 487 | } | 469 | } |
| 488 | 470 | ||
| 489 | static 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 | |||
| 576 | AstNode *ast_parse(Buf *buf, ZigList<Token> *tokens, ZigType *owner, ErrColor err_color) { | 471 | AstNode *ast_parse(Buf *buf, ZigList<Token> *tokens, ZigType *owner, ErrColor err_color) { |
| 577 | ParseContext pc = {}; | 472 | ParseContext pc = {}; |
| 578 | pc.err_color = err_color; | 473 | pc.err_color = err_color; |
| ... | @@ -1931,9 +1826,8 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc) { | ... | @@ -1931,9 +1826,8 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc) { |
| 1931 | 1826 | ||
| 1932 | res->line = asm_token->start_line; | 1827 | res->line = asm_token->start_line; |
| 1933 | res->column = asm_token->start_column; | 1828 | res->column = asm_token->start_column; |
| 1934 | res->data.asm_expr.is_volatile = volatile_token != nullptr; | 1829 | res->data.asm_expr.volatile_token = volatile_token; |
| 1935 | res->data.asm_expr.asm_template = token_buf(asm_template); | 1830 | res->data.asm_expr.asm_template = asm_template; |
| 1936 | ast_parse_asm_template(pc, res); | ||
| 1937 | return res; | 1831 | return res; |
| 1938 | } | 1832 | } |
| 1939 | 1833 | ||
| ... | @@ -1985,7 +1879,7 @@ static AstNode *ast_parse_asm_input(ParseContext *pc) { | ... | @@ -1985,7 +1879,7 @@ static AstNode *ast_parse_asm_input(ParseContext *pc) { |
| 1985 | return nullptr; | 1879 | return nullptr; |
| 1986 | 1880 | ||
| 1987 | ZigList<AsmInput *> input_list = ast_parse_list(pc, TokenIdComma, ast_parse_asm_input_item); | 1881 | 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); |
| 1989 | if (res == nullptr) | 1883 | if (res == nullptr) |
| 1990 | res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr); | 1884 | res = ast_create_node_no_line_info(pc, NodeTypeAsmExpr); |
| 1991 | 1885 | ||
| ... | @@ -2013,7 +1907,7 @@ static AsmInput *ast_parse_asm_input_item(ParseContext *pc) { | ... | @@ -2013,7 +1907,7 @@ static AsmInput *ast_parse_asm_input_item(ParseContext *pc) { |
| 2013 | } | 1907 | } |
| 2014 | 1908 | ||
| 2015 | // AsmClobbers <- COLON StringList | 1909 | // AsmClobbers <- COLON StringList |
| 2016 | static AstNode *ast_parse_asm_cloppers(ParseContext *pc) { | 1910 | static AstNode *ast_parse_asm_clobbers(ParseContext *pc) { |
| 2017 | if (eat_token_if(pc, TokenIdColon) == nullptr) | 1911 | if (eat_token_if(pc, TokenIdColon) == nullptr) |
| 2018 | return nullptr; | 1912 | return nullptr; |
| 2019 | 1913 |
test/compile_errors.zig+9| ... | @@ -2,6 +2,15 @@ const tests = @import("tests.zig"); | ... | @@ -2,6 +2,15 @@ const tests = @import("tests.zig"); |
| 2 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | 3 | ||
| 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { | 4 | pub 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 | |||
| 5 | cases.add( | 14 | cases.add( |
| 6 | "invalid multiple dereferences", | 15 | "invalid multiple dereferences", |
| 7 | \\export fn a() void { | 16 | \\export fn a() void { |
test/stage1/behavior/asm.zig+1-1| ... | @@ -3,7 +3,7 @@ const expect = @import("std").testing.expect; | ... | @@ -3,7 +3,7 @@ const expect = @import("std").testing.expect; |
| 3 | 3 | ||
| 4 | comptime { | 4 | comptime { |
| 5 | if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { | 5 | if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { |
| 6 | asm volatile ( | 6 | asm ( |
| 7 | \\.globl this_is_my_alias; | 7 | \\.globl this_is_my_alias; |
| 8 | \\.type this_is_my_alias, @function; | 8 | \\.type this_is_my_alias, @function; |
| 9 | \\.set this_is_my_alias, derp; | 9 | \\.set this_is_my_alias, derp; |