| author | |
| committer | |
| log | 1b432b557608bd047afaad71ffb7bd2ddf23c444 |
| tree | 0930ca5f5f1e78460a75dc5d86e243fccb5027ba |
| parent | 0bebb688fbc4c6ffb88a2b0aefcf536143bb5f2e |
So far it's supported by the LLVM backend only. I recommend for the
other backends to wait for the resolution of #10761 before adding
support for this feature.5 files changed, 55 insertions(+), 6 deletions(-)
src/Module.zig+15| ... | ... | @@ -151,6 +151,8 @@ allocated_decls: std.SegmentedList(Decl, 0) = .{}, |
| 151 | 151 | /// When a Decl object is freed from `allocated_decls`, it is pushed into this stack. |
| 152 | 152 | decls_free_list: std.ArrayListUnmanaged(Decl.Index) = .{}, |
| 153 | 153 | |
| 154 | global_assembly: std.AutoHashMapUnmanaged(Decl.Index, []u8) = .{}, | |
| 155 | ||
| 154 | 156 | const MonomorphedFuncsSet = std.HashMapUnmanaged( |
| 155 | 157 | *Fn, |
| 156 | 158 | void, |
| ... | ... | @@ -2831,6 +2833,7 @@ pub fn deinit(mod: *Module) void { |
| 2831 | 2833 | |
| 2832 | 2834 | mod.decls_free_list.deinit(gpa); |
| 2833 | 2835 | mod.allocated_decls.deinit(gpa); |
| 2836 | mod.global_assembly.deinit(gpa); | |
| 2834 | 2837 | } |
| 2835 | 2838 | |
| 2836 | 2839 | pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void { |
| ... | ... | @@ -2842,6 +2845,9 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void { |
| 2842 | 2845 | if (decl.deletion_flag) { |
| 2843 | 2846 | assert(mod.deletion_set.swapRemove(decl_index)); |
| 2844 | 2847 | } |
| 2848 | if (mod.global_assembly.fetchRemove(decl_index)) |kv| { | |
| 2849 | gpa.free(kv.value); | |
| 2850 | } | |
| 2845 | 2851 | if (decl.has_tv) { |
| 2846 | 2852 | if (decl.getInnerNamespace()) |namespace| { |
| 2847 | 2853 | namespace.destroyDecls(mod); |
| ... | ... | @@ -5714,3 +5720,12 @@ pub fn markDeclAlive(mod: *Module, decl: *Decl) void { |
| 5714 | 5720 | fn markDeclIndexAlive(mod: *Module, decl_index: Decl.Index) void { |
| 5715 | 5721 | return mod.markDeclAlive(mod.declPtr(decl_index)); |
| 5716 | 5722 | } |
| 5723 | ||
| 5724 | pub fn addGlobalAssembly(mod: *Module, decl_index: Decl.Index, source: []const u8) !void { | |
| 5725 | try mod.global_assembly.ensureUnusedCapacity(mod.gpa, 1); | |
| 5726 | ||
| 5727 | const duped_source = try mod.gpa.dupe(u8, source); | |
| 5728 | errdefer mod.gpa.free(duped_source); | |
| 5729 | ||
| 5730 | mod.global_assembly.putAssumeCapacityNoClobber(decl_index, duped_source); | |
| 5731 | } |
src/Sema.zig+23-5| ... | ... | @@ -10517,16 +10517,35 @@ fn zirAsm( |
| 10517 | 10517 | const is_volatile = @truncate(u1, extended.small >> 15) != 0; |
| 10518 | 10518 | const is_global_assembly = sema.func == null; |
| 10519 | 10519 | |
| 10520 | if (block.is_comptime and !is_global_assembly) { | |
| 10521 | try sema.requireRuntimeBlock(block, src); | |
| 10522 | } | |
| 10523 | ||
| 10524 | 10520 | if (extra.data.asm_source == 0) { |
| 10525 | 10521 | // This can move to become an AstGen error after inline assembly improvements land |
| 10526 | 10522 | // and stage1 code matches stage2 code. |
| 10527 | 10523 | return sema.fail(block, src, "assembly code must use string literal syntax", .{}); |
| 10528 | 10524 | } |
| 10529 | 10525 | |
| 10526 | const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); | |
| 10527 | ||
| 10528 | if (is_global_assembly) { | |
| 10529 | if (outputs_len != 0) { | |
| 10530 | return sema.fail(block, src, "module-level assembly does not support outputs", .{}); | |
| 10531 | } | |
| 10532 | if (inputs_len != 0) { | |
| 10533 | return sema.fail(block, src, "module-level assembly does not support inputs", .{}); | |
| 10534 | } | |
| 10535 | if (clobbers_len != 0) { | |
| 10536 | return sema.fail(block, src, "module-level assembly does not support clobbers", .{}); | |
| 10537 | } | |
| 10538 | if (is_volatile) { | |
| 10539 | return sema.fail(block, src, "volatile keyword is redundant on module-level assembly", .{}); | |
| 10540 | } | |
| 10541 | try sema.mod.addGlobalAssembly(sema.owner_decl_index, asm_source); | |
| 10542 | return Air.Inst.Ref.void_value; | |
| 10543 | } | |
| 10544 | ||
| 10545 | if (block.is_comptime) { | |
| 10546 | try sema.requireRuntimeBlock(block, src); | |
| 10547 | } | |
| 10548 | ||
| 10530 | 10549 | if (outputs_len > 1) { |
| 10531 | 10550 | return sema.fail(block, src, "TODO implement Sema for asm with more than 1 output", .{}); |
| 10532 | 10551 | } |
| ... | ... | @@ -10591,7 +10610,6 @@ fn zirAsm( |
| 10591 | 10610 | needed_capacity += name.*.len / 4 + 1; |
| 10592 | 10611 | } |
| 10593 | 10612 | |
| 10594 | const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); | |
| 10595 | 10613 | needed_capacity += (asm_source.len + 3) / 4; |
| 10596 | 10614 | |
| 10597 | 10615 | const gpa = sema.gpa; |
src/codegen/llvm.zig+14| ... | ... | @@ -476,6 +476,19 @@ pub const Object = struct { |
| 476 | 476 | _ = builder.buildRet(is_lt); |
| 477 | 477 | } |
| 478 | 478 | |
| 479 | fn genModuleLevelAssembly(object: *Object, comp: *Compilation) !void { | |
| 480 | const mod = comp.bin_file.options.module.?; | |
| 481 | if (mod.global_assembly.count() == 0) return; | |
| 482 | var buffer = std.ArrayList(u8).init(comp.gpa); | |
| 483 | defer buffer.deinit(); | |
| 484 | var it = mod.global_assembly.iterator(); | |
| 485 | while (it.next()) |kv| { | |
| 486 | try buffer.appendSlice(kv.value_ptr.*); | |
| 487 | try buffer.append('\n'); | |
| 488 | } | |
| 489 | object.llvm_module.setModuleInlineAsm2(buffer.items.ptr, buffer.items.len - 1); | |
| 490 | } | |
| 491 | ||
| 479 | 492 | pub fn flushModule(self: *Object, comp: *Compilation, prog_node: *std.Progress.Node) !void { |
| 480 | 493 | var sub_prog_node = prog_node.start("LLVM Emit Object", 0); |
| 481 | 494 | sub_prog_node.activate(); |
| ... | ... | @@ -484,6 +497,7 @@ pub const Object = struct { |
| 484 | 497 | |
| 485 | 498 | try self.genErrorNameTable(comp); |
| 486 | 499 | try self.genCmpLtErrorsLenFunction(comp); |
| 500 | try self.genModuleLevelAssembly(comp); | |
| 487 | 501 | |
| 488 | 502 | if (self.di_builder) |dib| { |
| 489 | 503 | // When lowering debug info for pointers, we emitted the element types as |
src/codegen/llvm/bindings.zig+3| ... | ... | @@ -381,6 +381,9 @@ pub const Module = opaque { |
| 381 | 381 | |
| 382 | 382 | pub const createDIBuilder = ZigLLVMCreateDIBuilder; |
| 383 | 383 | extern fn ZigLLVMCreateDIBuilder(module: *const Module, allow_unresolved: bool) *DIBuilder; |
| 384 | ||
| 385 | pub const setModuleInlineAsm2 = LLVMSetModuleInlineAsm2; | |
| 386 | extern fn LLVMSetModuleInlineAsm2(M: *const Module, Asm: [*]const u8, Len: usize) void; | |
| 384 | 387 | }; |
| 385 | 388 | |
| 386 | 389 | pub const lookupIntrinsicID = LLVMLookupIntrinsicID; |
test/behavior/asm.zig-1| ... | ... | @@ -23,7 +23,6 @@ test "module level assembly" { |
| 23 | 23 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO |
| 24 | 24 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 25 | 25 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO |
| 26 | if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO | |
| 27 | 26 | |
| 28 | 27 | if (is_x86_64_linux) { |
| 29 | 28 | try expect(this_is_my_alias() == 1234); |