diff --git a/Sources/LLVM/DIBuilder.swift b/Sources/LLVM/DIBuilder.swift new file mode 100644 index 00000000..7ac1dcb7 --- /dev/null +++ b/Sources/LLVM/DIBuilder.swift @@ -0,0 +1,259 @@ +#if SWIFT_PACKAGE +import cllvm +#endif + +/// Source languages known by DWARF. +public enum DWARFSourceLanguage { + case ada83 + + case ada95 + + case c + + case c89 + + case c99 + + case c11 + + case cPlusPlus + + case cPlusPlus03 + + case cPlusPlus11 + + case cPlusPlus14 + + case cobol74 + + case cobol85 + + case fortran77 + + case fortran90 + + case fortran03 + + case fortran08 + + case pascal83 + + case modula2 + + case java + + case fortran95 + + case PLI + + case objC + + case objCPlusPlus + + case UPC + + case D + + case python + + case openCL + + case go + + case modula3 + + case haskell + + case ocaml + + case rust + + case swift + + case julia + + case dylan + + case renderScript + + case BLISS + + // MARK: Vendor Extensions + + case mipsAssembler + + case googleRenderScript + + case borlandDelphi + + + private static let languageMapping: [DWARFSourceLanguage: LLVMDWARFSourceLanguage] = [ + .c: LLVMDWARFSourceLanguageC, .c89: LLVMDWARFSourceLanguageC89, + .c99: LLVMDWARFSourceLanguageC99, .c11: LLVMDWARFSourceLanguageC11, + .ada83: LLVMDWARFSourceLanguageAda83, + .cPlusPlus: LLVMDWARFSourceLanguageC_plus_plus, + .cPlusPlus03: LLVMDWARFSourceLanguageC_plus_plus_03, + .cPlusPlus11: LLVMDWARFSourceLanguageC_plus_plus_11, + .cPlusPlus14: LLVMDWARFSourceLanguageC_plus_plus_14, + .cobol74: LLVMDWARFSourceLanguageCobol74, + .cobol85: LLVMDWARFSourceLanguageCobol85, + .fortran77: LLVMDWARFSourceLanguageFortran77, + .fortran90: LLVMDWARFSourceLanguageFortran90, + .pascal83: LLVMDWARFSourceLanguagePascal83, + .modula2: LLVMDWARFSourceLanguageModula2, + .java: LLVMDWARFSourceLanguageJava, + .ada95: LLVMDWARFSourceLanguageAda95, + .fortran95: LLVMDWARFSourceLanguageFortran95, + .PLI: LLVMDWARFSourceLanguagePLI, + .objC: LLVMDWARFSourceLanguageObjC, + .objCPlusPlus: LLVMDWARFSourceLanguageObjC_plus_plus, + .UPC: LLVMDWARFSourceLanguageUPC, + .D: LLVMDWARFSourceLanguageD, + .python: LLVMDWARFSourceLanguagePython, + .openCL: LLVMDWARFSourceLanguageOpenCL, + .go: LLVMDWARFSourceLanguageGo, + .modula3: LLVMDWARFSourceLanguageModula3, + .haskell: LLVMDWARFSourceLanguageHaskell, + .ocaml: LLVMDWARFSourceLanguageOCaml, + .rust: LLVMDWARFSourceLanguageRust, + .swift: LLVMDWARFSourceLanguageSwift, + .julia: LLVMDWARFSourceLanguageJulia, + .dylan: LLVMDWARFSourceLanguageDylan, + .fortran03: LLVMDWARFSourceLanguageFortran03, + .fortran08: LLVMDWARFSourceLanguageFortran08, + .renderScript: LLVMDWARFSourceLanguageRenderScript, + .BLISS: LLVMDWARFSourceLanguageBLISS, + .mipsAssembler: LLVMDWARFSourceLanguageMips_Assembler, + .googleRenderScript: LLVMDWARFSourceLanguageGOOGLE_RenderScript, + .borlandDelphi: LLVMDWARFSourceLanguageBORLAND_Delphi, + ] + + /// Retrieves the corresponding `LLVMDWARFSourceLanguage`. + public var llvm: LLVMDWARFSourceLanguage { + return DWARFSourceLanguage.languageMapping[self]! + } +} + +/// The amount of debug information to emit. +public enum DWARFEmissionKind { + case none + case full + case lineTablesOnly + + private static let emissionMapping: [DWARFEmissionKind: LLVMDWARFEmissionKind] = [ + .none: LLVMDWARFEmissionNone, .full: LLVMDWARFEmissionFull, + .lineTablesOnly: LLVMDWARFEmissionLineTablesOnly, + ] + + /// Retrieves the corresponding `LLVMDWARFEmissionKind`. + public var llvm: LLVMDWARFEmissionKind { + return DWARFEmissionKind.emissionMapping[self]! + } +} + +public final class DIBuilder { + internal let llvm: LLVMDIBuilderRef + + /// The module this `IRBuilder` is associated with. + public let module: Module + + public init(module: Module, allowUnresolved: Bool = false) { + self.module = module + if allowUnresolved { + self.llvm = LLVMCreateDIBuilder(module.llvm) + } else { + self.llvm = LLVMCreateDIBuilderDisallowUnresolved(module.llvm) + } + } + + /// A CompileUnit provides an anchor for all debugging information generated + /// during this instance of compilation. + /// + /// - Parameters: + /// - language: The source programming language. + /// - file: The file descriptor for the source file. + /// - kind: The kind of debug info to generate. + /// - optimized: A flag that indicates whether optimization is enabled or + /// not when compiling the source file. Defaults to `false`. + /// - splitDebugInlining: A flag that indicates whether to emit inline debug + /// information. Defaults to `false`. + /// - debugInfoForProfiling: A flag that indicates whether to emit extra + /// debug information for profile collection. + /// - flags: Command line options that are embedded in debug info for use + /// by third-party tools. + /// - identity: The identity of the tool that is compiling this source file. + /// - Returns: A value representing a compilation-unit level scope. + public func createCompileUnit( + for language: DWARFSourceLanguage, + in file: FileMetadata, + kind: DWARFEmissionKind, + optimized: Bool = false, + splitDebugInlining: Bool = false, + debugInfoForProfiling: Bool = false, + flags: [String] = [], + identity: String = "", + splitName: String = "" + ) -> Scope { + let allFlags = flags.joined(separator: " ") + guard let cu = LLVMDIBuilderCreateCompileUnit( + self.llvm, language.llvm, file.llvm, identity, identity.count, + optimized.llvm, + allFlags, allFlags.count, + /*Runtime Version*/0, + splitName, splitName.count, + kind.llvm, + /*DWOId*/0, + splitDebugInlining.llvm, + debugInfoForProfiling.llvm + ) else { + fatalError() + } + return Scope(llvm: cu) + } + + /// Create a file descriptor to hold debugging information for a file. + /// + /// - Parameters: + /// - name: The name of the file. + /// - directory: The directory the file resides in. + /// - Returns: A value represending metadata about a given file. + public func createFile(named name: String, in directory: String) -> FileMetadata { + guard let file = LLVMDIBuilderCreateFile(self.llvm, name, name.count, directory, directory.count) else { + fatalError("Failed to allocate metadata for a file") + } + return FileMetadata(llvm: file) + } + + /// Creates a new debug location that describes a source location. + /// + /// - Parameters: + /// - location: The location of the line and column for this information. + /// If the location of the value is unknown, pass + /// `(line: 0, column: 0)`. + /// - scope: The scope this debug location resides in. + /// - inlinedAt: If this location has been inlined somewhere, the scope in + /// which it was inlined. Defaults to `nil`. + /// - Returns: A value representing a debug location. + public func createDebugLocation( + at location : (line: Int, column: Int), + in scope: Scope, + inlinedAt: Scope? = nil + ) -> DebugLocation { + guard let loc = LLVMDIBuilderCreateDebugLocation( + self.module.context.llvm, UInt32(location.line), UInt32(location.column), + scope.llvm, inlinedAt?.llvm + ) else { + fatalError("Failed to allocate metadata for a debug location") + } + return DebugLocation(llvm: loc) + } + + /// Construct any deferred debug info descriptors. + public func finalize() { + LLVMDIBuilderFinalize(self.llvm) + } + + deinit { + LLVMDisposeDIBuilder(self.llvm) + } +} diff --git a/Sources/LLVM/Metadata.swift b/Sources/LLVM/Metadata.swift new file mode 100644 index 00000000..ef302948 --- /dev/null +++ b/Sources/LLVM/Metadata.swift @@ -0,0 +1,72 @@ +#if SWIFT_PACKAGE +import cllvm +#endif + +public protocol Metadata { + func asMetadata() -> LLVMMetadataRef +} + +struct AnyMetadata: Metadata { + let llvm: LLVMMetadataRef + + func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + +public struct VariableMetadata: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + +public struct FileMetadata: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + +public struct Scope: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + +public struct Macro: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + +public struct DIModule: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + +public struct DIExpression: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} + + +public struct DebugLocation: Metadata { + internal let llvm: LLVMMetadataRef + + public func asMetadata() -> LLVMMetadataRef { + return llvm + } +} diff --git a/Sources/LLVM/Module.swift b/Sources/LLVM/Module.swift index c3bed364..4ccdd72f 100644 --- a/Sources/LLVM/Module.swift +++ b/Sources/LLVM/Module.swift @@ -209,6 +209,25 @@ public final class Module: CustomStringConvertible { } } + /// The current debug metadata version number. + public static var debugMetadataVersion: UInt32 { + return LLVMDebugMetadataVersion(); + } + + /// The version of debug metadata that's present in this module. + public var debugMetadataVersion: UInt32 { + return LLVMGetModuleDebugMetadataVersion(self.llvm) + } + + /// Strip debug info in the module if it exists. + /// + /// To do this, we remove all calls to the debugger intrinsics and any named + /// metadata for debugging. We also remove debug locations for instructions. + /// Return true if module is modified. + public func stripDebugInfo() -> Bool { + return LLVMStripModuleDebugInfo(self.llvm) != 0 + } + /// Dump a representation of this module to stderr. public func dump() { LLVMDumpModule(llvm) diff --git a/Tests/LLVMTests/DIBuilderSpec.swift b/Tests/LLVMTests/DIBuilderSpec.swift new file mode 100644 index 00000000..683fc3b4 --- /dev/null +++ b/Tests/LLVMTests/DIBuilderSpec.swift @@ -0,0 +1,35 @@ +import LLVM +import XCTest +import FileCheck +import Foundation + +class DIBuilderSpec : XCTestCase { + func testDIBuilder() { + XCTAssertTrue(fileCheckOutput(of: .stderr, withPrefixes: ["DIBUILDER"]) { + // DIBUILDER: ; ModuleID = 'DIBuilderTest' + let module = Module(name: "DIBuilderTest") + // DIBUILDER: source_filename = "DIBuilderTest" + let builder = IRBuilder(module: module) + let debugBuilder = DIBuilder(module: module) + + let f = builder.addFunction("foo", type: FunctionType(argTypes: [], returnType: VoidType())) + let bb = f.appendBasicBlock(named: "entry") + builder.positionAtEnd(of: bb) + _ = builder.buildAlloca(type: IntType.int8) + + // DIBUILDER-DAG: !{{[0-9]+}} = !DIFile(filename: "test.trill", directory: "/") + let file = debugBuilder.createFile(named: "test.trill", in: "/") + // DIBUILDER-DAG: !{{[0-9]+}} = distinct !DICompileUnit(language: DW_LANG_Swift, file: !{{[0-9]+}}, isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !{{[0-9]+}}, splitDebugInlining: false) + _ = debugBuilder.createCompileUnit(for: .swift, in: file, kind: .full) + + debugBuilder.finalize() + module.dump() + }) + } + + #if !os(macOS) + static var allTests = testCase([ + ("testDIBuilder", testDIBuilder), + ]) + #endif +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 070020f9..777946cb 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -5,6 +5,7 @@ import XCTest #if !os(macOS) XCTMain([ ConstantSpec.allTests, + DIBuilderSpec.allTests, FileCheckSpec.allTests, IRBuilderSpec.allTests, IRExceptionSpec.allTests,