From 1b432b557608bd047afaad71ffb7bd2ddf23c444 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 May 2022 20:38:53 -0700 Subject: [PATCH] stage2: implement global assembly 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. --- src/Module.zig | 15 +++++++++++++++ src/Sema.zig | 28 +++++++++++++++++++++++----- src/codegen/llvm.zig | 14 ++++++++++++++ src/codegen/llvm/bindings.zig | 3 +++ test/behavior/asm.zig | 1 - 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 55ec1fdd2c..864663ada2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -151,6 +151,8 @@ allocated_decls: std.SegmentedList(Decl, 0) = .{}, /// When a Decl object is freed from `allocated_decls`, it is pushed into this stack. decls_free_list: std.ArrayListUnmanaged(Decl.Index) = .{}, +global_assembly: std.AutoHashMapUnmanaged(Decl.Index, []u8) = .{}, + const MonomorphedFuncsSet = std.HashMapUnmanaged( *Fn, void, @@ -2831,6 +2833,7 @@ pub fn deinit(mod: *Module) void { mod.decls_free_list.deinit(gpa); mod.allocated_decls.deinit(gpa); + mod.global_assembly.deinit(gpa); } pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void { @@ -2842,6 +2845,9 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void { if (decl.deletion_flag) { assert(mod.deletion_set.swapRemove(decl_index)); } + if (mod.global_assembly.fetchRemove(decl_index)) |kv| { + gpa.free(kv.value); + } if (decl.has_tv) { if (decl.getInnerNamespace()) |namespace| { namespace.destroyDecls(mod); @@ -5714,3 +5720,12 @@ pub fn markDeclAlive(mod: *Module, decl: *Decl) void { fn markDeclIndexAlive(mod: *Module, decl_index: Decl.Index) void { return mod.markDeclAlive(mod.declPtr(decl_index)); } + +pub fn addGlobalAssembly(mod: *Module, decl_index: Decl.Index, source: []const u8) !void { + try mod.global_assembly.ensureUnusedCapacity(mod.gpa, 1); + + const duped_source = try mod.gpa.dupe(u8, source); + errdefer mod.gpa.free(duped_source); + + mod.global_assembly.putAssumeCapacityNoClobber(decl_index, duped_source); +} diff --git a/src/Sema.zig b/src/Sema.zig index 8125fc0cc1..9b4977c616 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10517,16 +10517,35 @@ fn zirAsm( const is_volatile = @truncate(u1, extended.small >> 15) != 0; const is_global_assembly = sema.func == null; - if (block.is_comptime and !is_global_assembly) { - try sema.requireRuntimeBlock(block, src); - } - if (extra.data.asm_source == 0) { // This can move to become an AstGen error after inline assembly improvements land // and stage1 code matches stage2 code. return sema.fail(block, src, "assembly code must use string literal syntax", .{}); } + const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); + + if (is_global_assembly) { + if (outputs_len != 0) { + return sema.fail(block, src, "module-level assembly does not support outputs", .{}); + } + if (inputs_len != 0) { + return sema.fail(block, src, "module-level assembly does not support inputs", .{}); + } + if (clobbers_len != 0) { + return sema.fail(block, src, "module-level assembly does not support clobbers", .{}); + } + if (is_volatile) { + return sema.fail(block, src, "volatile keyword is redundant on module-level assembly", .{}); + } + try sema.mod.addGlobalAssembly(sema.owner_decl_index, asm_source); + return Air.Inst.Ref.void_value; + } + + if (block.is_comptime) { + try sema.requireRuntimeBlock(block, src); + } + if (outputs_len > 1) { return sema.fail(block, src, "TODO implement Sema for asm with more than 1 output", .{}); } @@ -10591,7 +10610,6 @@ fn zirAsm( needed_capacity += name.*.len / 4 + 1; } - const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); needed_capacity += (asm_source.len + 3) / 4; const gpa = sema.gpa; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 068067d765..65289b1a8c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -476,6 +476,19 @@ pub const Object = struct { _ = builder.buildRet(is_lt); } + fn genModuleLevelAssembly(object: *Object, comp: *Compilation) !void { + const mod = comp.bin_file.options.module.?; + if (mod.global_assembly.count() == 0) return; + var buffer = std.ArrayList(u8).init(comp.gpa); + defer buffer.deinit(); + var it = mod.global_assembly.iterator(); + while (it.next()) |kv| { + try buffer.appendSlice(kv.value_ptr.*); + try buffer.append('\n'); + } + object.llvm_module.setModuleInlineAsm2(buffer.items.ptr, buffer.items.len - 1); + } + pub fn flushModule(self: *Object, comp: *Compilation, prog_node: *std.Progress.Node) !void { var sub_prog_node = prog_node.start("LLVM Emit Object", 0); sub_prog_node.activate(); @@ -484,6 +497,7 @@ pub const Object = struct { try self.genErrorNameTable(comp); try self.genCmpLtErrorsLenFunction(comp); + try self.genModuleLevelAssembly(comp); if (self.di_builder) |dib| { // When lowering debug info for pointers, we emitted the element types as diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 560b9544dd..4732e0dd49 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -381,6 +381,9 @@ pub const Module = opaque { pub const createDIBuilder = ZigLLVMCreateDIBuilder; extern fn ZigLLVMCreateDIBuilder(module: *const Module, allow_unresolved: bool) *DIBuilder; + + pub const setModuleInlineAsm2 = LLVMSetModuleInlineAsm2; + extern fn LLVMSetModuleInlineAsm2(M: *const Module, Asm: [*]const u8, Len: usize) void; }; pub const lookupIntrinsicID = LLVMLookupIntrinsicID; diff --git a/test/behavior/asm.zig b/test/behavior/asm.zig index dab6f12127..9aa95a3a0b 100644 --- a/test/behavior/asm.zig +++ b/test/behavior/asm.zig @@ -23,7 +23,6 @@ test "module level assembly" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO if (is_x86_64_linux) { try expect(this_is_my_alias() == 1234);