diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0bf95fe2..1fc9b2f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -331,11 +331,98 @@ jobs: - name: Install CMake run: | apt-get install -y curl - curl -L https://github.com/Kitware/CMake/releases/download/v3.29.2/cmake-3.29.2-linux-x86_64.tar.gz | tar xz --strip-component 1 -C /usr/local/ + curl -L https://github.com/Kitware/CMake/releases/download/v3.30.2/cmake-3.30.2-linux-x86_64.tar.gz | tar xz --strip-component 1 -C /usr/local/ - run: cmake -G Ninja -B ./build - run: cmake --build ./build - run: ./build/bin/wasmkit --version + build-llvm-ubuntu: + strategy: + matrix: + include: + - os: ubuntu-24.04 + llvm-target: X86 + - os: ubuntu-24.04-arm + llvm-target: AArch64 + runs-on: ${{ matrix.os }} + container: + image: swift:6.2-noble + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: apt-get update && apt-get install -y ninja-build python3 + - name: Install CMake + run: | + apt-get install -y curl + if [[ "${{ matrix.llvm-target }}" == "AArch64" ]]; then + export CMAKE_ARCH=aarch64 + elif [[ "${{ matrix.llvm-target }}" == "X86" ]]; then + export CMAKE_ARCH=x86_64 + else + exit 1 + fi + + curl -L "https://github.com/Kitware/CMake/releases/download/v3.30.2/cmake-3.30.2-linux-${CMAKE_ARCH}.tar.gz" | tar xz --strip-component 1 -C /usr/local/ + - name: Cache LLVM + id: cache-llvm + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/Vendor/llvm-project + # IMPORTANT: Update the key when updating LLVM tag below in the build step. + key: llvm-${{ matrix.os }}-${{ matrix.llvm-target }}-swift-main-2026-01-04 + - name: Build LLVM + if: steps.cache-llvm.outputs.cache-hit != 'true' + run: | + mkdir -p $GITHUB_WORKSPACE/Vendor/llvm-project + curl -L https://github.com/swiftlang/llvm-project/archive/refs/tags/swift-DEVELOPMENT-SNAPSHOT-2026-01-04-a.tar.gz | tar xz --strip-component 1 -C $GITHUB_WORKSPACE/Vendor/llvm-project + cmake -DLLVM_TARGETS_TO_BUILD=${{ matrix.llvm-target }} -DLLVM_INCLUDE_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -G Ninja -S $GITHUB_WORKSPACE/Vendor/llvm-project/llvm -B $GITHUB_WORKSPACE/Vendor/llvm-project/llvm/build + cmake --build $GITHUB_WORKSPACE/Vendor/llvm-project/llvm/build + - name: Build WasmKit with LLVM + run: | + LLVM_DIR="$GITHUB_WORKSPACE/Vendor/llvm-project/llvm/build/lib/cmake/llvm/" cmake -DWASMKIT_BUILD_LLVM_BACKEND=ON -DWASMKIT_LLVM_BACKEND_TARGET=${{ matrix.llvm-target }} -DWASMKIT_BUILD_CLI=ON -G Ninja -B ./build + cmake --build ./build + - name: Run a smoke test + run: | + ./build/bin/wasmkit-llvm wat2wasm Examples/wasm/factorial.wat + ./build/bin/wasmkit-llvm run Examples/wasm/factorial.wasm fac i64:5 + + build-llvm-macos: + strategy: + matrix: + include: + - os: macos-26 + llvm-target: AArch64 + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Cache LLVM + id: cache-llvm + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/Vendor/llvm-project + # IMPORTANT: Update the key when updating LLVM tag below in the build step. + key: llvm-${{ matrix.os }}-${{ matrix.llvm-target }}-swift-main-2026-01-04 + - name: Build LLVM + if: steps.cache-llvm.outputs.cache-hit != 'true' + run: | + sudo xcode-select -s /Applications/Xcode_26.2.app + mkdir -p $GITHUB_WORKSPACE/Vendor/llvm-project + curl -L https://github.com/swiftlang/llvm-project/archive/refs/tags/swift-DEVELOPMENT-SNAPSHOT-2026-01-04-a.tar.gz | tar xz --strip-component 1 -C $GITHUB_WORKSPACE/Vendor/llvm-project + cmake -DLLVM_TARGETS_TO_BUILD=${{ matrix.llvm-target }} -DLLVM_INCLUDE_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -G Ninja -S $GITHUB_WORKSPACE/Vendor/llvm-project/llvm -B $GITHUB_WORKSPACE/Vendor/llvm-project/llvm/build + cmake --build $GITHUB_WORKSPACE/Vendor/llvm-project/llvm/build + - name: Build WasmKit with LLVM + run: | + LLVM_DIR="$GITHUB_WORKSPACE/Vendor/llvm-project/llvm/build/lib/cmake/llvm/" cmake -DWASMKIT_BUILD_LLVM_BACKEND=ON -DWASMKIT_LLVM_BACKEND_TARGET=${{ matrix.llvm-target }} -DWASMKIT_BUILD_CLI=ON -G Ninja -B ./build + cmake --build ./build + - name: Run a smoke test + run: | + ./build/bin/wasmkit-llvm wat2wasm Examples/wasm/factorial.wat + ./build/bin/wasmkit-llvm run Examples/wasm/factorial.wasm fac i64:5 + build-wasi: runs-on: ubuntu-24.04 container: diff --git a/CMakeLists.txt b/CMakeLists.txt index 048419d2..9b0193b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,21 @@ -cmake_minimum_required(VERSION 3.19.6) +cmake_minimum_required(VERSION 3.30) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) -project(WasmKit LANGUAGES C Swift) +project(WasmKit LANGUAGES C CXX Swift) -set(SWIFT_VERSION 5) +set(SWIFT_VERSION 6) set(CMAKE_Swift_LANGUAGE_VERSION ${SWIFT_VERSION}) +set(min_supported_swift_version 6.2) +if(CMAKE_Swift_COMPILER_VERSION VERSION_LESS "${min_supported_swift_version}") + message( + FATAL_ERROR + "Outdated Swift compiler: " + "Swift ${min_supported_swift_version} or newer is required." + ) +endif() + # Enable whole module optimization for Release or RelWithDebInfo builds. if(POLICY CMP0157) set(CMAKE_Swift_COMPILATION_MODE $,wholemodule,incremental>) @@ -14,13 +23,6 @@ else() add_compile_options($<$,$>:-wmo>) endif() -if(CMAKE_VERSION VERSION_LESS 3.21) - get_property(parent_dir DIRECTORY PROPERTY PARENT_DIRECTORY) - if(NOT parent_dir) - set(PROJECT_IS_TOP_LEVEL TRUE) - endif() -endif() - # The subdirectory into which host libraries will be installed. set(SWIFT_HOST_LIBRARIES_SUBDIRECTORY "swift/host") @@ -52,7 +54,9 @@ add_compile_definitions( include(FetchContent) -option(WASMKIT_BUILD_CLI "Build wasmkit-cli" ON) +option(WASMKIT_BUILD_CLI "Build WasmKit CLI" ON) +option(WASMKIT_BUILD_LLVM_BACKEND "Build WasmKit LLVM backend (experimental)" OFF) +option(WASMKIT_LLVM_BACKEND_TARGET "WasmKit LLVM backend target architecture" "aarch64") if(WASMKIT_BUILD_CLI) set(BUILD_TESTING OFF) # disable ArgumentParser tests @@ -67,6 +71,43 @@ if(WASMKIT_BUILD_CLI) endif() endif() +if(WASMKIT_BUILD_LLVM_BACKEND) + FetchContent_Declare( + SwiftLLVMBindings + GIT_REPOSITORY "https://github.com/swiftlang/swift-llvm-bindings.git" + GIT_TAG c1fffb92d4f9d7cb98800762631cfa354869fb46 + GIT_SHALLOW TRUE + GIT_PROGRESS ON + ) + + FetchContent_MakeAvailable(SwiftLLVMBindings) + + find_package(Subprocess CONFIG) + + if(NOT Subprocess_FOUND) + message("-- Vending Subprocess") + FetchContent_Declare( + Subprocess + GIT_REPOSITORY https://github.com/swiftlang/swift-subprocess + GIT_TAG 0.2.1 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(Subprocess) + endif() + + find_package(SwiftCollections CONFIG) + if(NOT SwiftCollections_FOUND) + message("-- Vending SwiftCollections") + FetchContent_Declare( + SwiftCollections + GIT_REPOSITORY https://github.com/apple/swift-collections + GIT_TAG 1.3.0 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(SwiftCollections) + endif() +endif() + find_package(SwiftSystem CONFIG) if(NOT SwiftSystem_FOUND) message("-- Vending SwiftSystem") diff --git a/Sources/CLICommands/CMakeLists.txt b/Sources/CLICommands/CMakeLists.txt index 910c4120..ada5b5b7 100644 --- a/Sources/CLICommands/CMakeLists.txt +++ b/Sources/CLICommands/CMakeLists.txt @@ -5,16 +5,4 @@ add_wasmkit_library(CLICommands ) target_link_wasmkit_libraries(CLICommands PUBLIC - WAT WasmKitWASI) - -add_dependencies( - CLICommands - - ArgumentParser -) - -target_link_libraries(CLICommands - - PUBLIC - ArgumentParser -) + ArgumentParser WAT WasmKitWASI) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 5e28ac12..9c09e136 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -11,3 +11,12 @@ if(WASMKIT_BUILD_CLI) add_subdirectory(CLI) add_subdirectory(CLICommands) endif() + +if(WASMKIT_BUILD_LLVM_BACKEND) + add_subdirectory(LLVMInterop) + add_subdirectory(LLVMBackend) + + if(WASMKIT_BUILD_CLI) + add_subdirectory(LLVMBackendCLI) + endif() +endif() diff --git a/Sources/LLVMBackend/CMakeLists.txt b/Sources/LLVMBackend/CMakeLists.txt new file mode 100644 index 00000000..0c1b51a6 --- /dev/null +++ b/Sources/LLVMBackend/CMakeLists.txt @@ -0,0 +1,42 @@ +add_library( + LLVMBackend + + CodegenContext.swift + IRContext+codegen.swift + IRFunctionVisitor.swift + Linker.swift + Loader.swift + Wasm32Memory.swift +) +target_compile_options( + LLVMBackend + PUBLIC + "SHELL:-Xcc -std=c++17" + "SHELL:-Xcc -fapinotes" + "SHELL:-Xcc -fapinotes-modules" + "-cxx-interoperability-mode=default" + -package-name WasmKitPackage +) + +target_include_directories( + LLVMBackend + PUBLIC + "${LLVMINTEROP_INCLUDE_DIR}" + "${LLVM_MAIN_INCLUDE_DIR}" + "${LLVM_INCLUDE_DIR}" +) + +set(WASMKIT_DEPENDENCIES WAT WasmKit WasmTypes WasmParser) +set(BUILD_TESTING OFF) + +add_dependencies(LLVMInterop BasicContainers LLVM_Utils Subprocess ${WASMKIT_DEPENDENCIES}) + +target_link_libraries( + LLVMBackend + + BasicContainers + ${WASMKIT_DEPENDENCIES} + LLVMInterop + LLVM_Utils + Subprocess +) diff --git a/Sources/LLVMBackend/CodegenContext.swift b/Sources/LLVMBackend/CodegenContext.swift new file mode 100644 index 00000000..33ba8a2b --- /dev/null +++ b/Sources/LLVMBackend/CodegenContext.swift @@ -0,0 +1,147 @@ +import BasicContainers +import LLVMInterop +import LLVM_Analysis +import LLVM_Utils +import SystemPackage +import WAT +import WasmParser +import WasmTypes + +package struct CodegenContext: ~Copyable { + enum Error: Swift.Error { + case objectFileEmissionFailed + case multiValueResultsNotSupportedYet(functionName: String) + case unsupportedImport(Import) + case unsupportedExport(Export) + } + + private(set) var ir: IRContext + + private let isVerbose: Bool + + package init(isVerbose: Bool) { + self.ir = IRContext() + self.isVerbose = isVerbose + } + + mutating func codegen(wasmStream: some ByteStream) throws { + var parser = Parser(stream: wasmStream) + + var types = [FunctionType]() + var functionTypes = [TypeIndex]() + var functionVisitors = RigidArray() + var importedFunctions = [IRValue]() + var functionNames = [Int: String]() + var memories = [Memory]() + + while let payload = try parser.parseNext() { + switch payload { + case .typeSection(let t): types = t + case .functionSection(let f): functionTypes.append(contentsOf: f) + case .memorySection(let m): + memories = m + + case .importSection(let imports): + var currentFunctionIndex = 0 + for i in imports { + switch i.descriptor { + case .function(let typeIndex): + let type = types[Int(typeIndex)] + + guard type.results.count <= 1 else { + throw Error.multiValueResultsNotSupportedYet(functionName: "\(i.module).\(i.name)") + } + + let irType = self.ir.__pointerTypeUnsafe() + + // Mangle the name with character counts to avoid naming collisions. + // Without this mangling we can't distinguish between a function + // ".print" from module "foo" and function "print" from module "foo.", + // as without character counts they both would be mangled as `foo.print`. + // Another alternative could be to introduce a separator that's not allowed + // in Wasm function and module names (which one?), but character counts + // seem more reliable and predictable at the moment of writing. + let name = "\(i.module.count)_\(i.name.count)_\(i.module).\(i.name)" + name.withStringRef { + importedFunctions.append(self.ir.__createImportedFunctionUnsafe($0, irType)) + } + functionNames[currentFunctionIndex] = name + currentFunctionIndex += 1 + functionTypes.append(typeIndex) + + case .global, .table, .memory: + throw Error.unsupportedImport(i) + } + } + + case .exportSection(let exports): + for e in exports { + switch e.descriptor { + case .function(let functionIndex): + functionNames[Int(functionIndex)] = e.name + + default: + throw Error.unsupportedExport(e) + } + } + + case .codeSection(let functions): + functionVisitors = RigidArray(capacity: functions.count) + for (i, f) in functions.enumerated() { + let functionIndex = importedFunctions.count + i + let type = types[Int(functionTypes[functionIndex])] + // Create visitors first before actually visiting instructions. + // This will forward-declare all `llvm::Function` instances so that `call` + // LLVM IR instructions have these instances to refer to and are valid. + let name = functionNames[functionIndex] ?? "\(functionIndex)" + try functionVisitors.append( + .init( + name: name, + type: type, + locals: type.parameters + f.locals, + code: f, + ir: self.ir + )) + + if functionNames[i] == nil { + functionNames[i] = name + } + } + default: continue + } + } + + for i in 0.. FilePath { + let fd = try FileDescriptor.open(wasmPath, .readOnly) + try self.codegen(wasmStream: FileHandleStream(fileHandle: fd)) + var objectFilePath = wasmPath + objectFilePath.extension = "o" + guard objectFilePath.string.withStringRef({ self.ir.emitObjectFile($0) }) else { + throw Error.objectFileEmissionFailed + } + + return objectFilePath + } +} + +extension IRContext { + func function(type: IRFunctionType, name: String) -> IRFunction? { + let f = name.withStringRef { + self.__functionUnsafe(type, $0) + } + + return if f._isValid { f } else { nil } + } +} diff --git a/Sources/LLVMBackend/IRContext+codegen.swift b/Sources/LLVMBackend/IRContext+codegen.swift new file mode 100644 index 00000000..b8299478 --- /dev/null +++ b/Sources/LLVMBackend/IRContext+codegen.swift @@ -0,0 +1,43 @@ +import LLVMInterop +import WasmTypes + +extension IRContext { + func codegen(resultType: [ValueType]) -> IRType { + switch resultType.count { + case 0: + return self.__voidTypeUnsafe() + case 1: + return codegen(type: resultType[0]) + default: + var types = IRTypeVector() + for t in resultType { + types.push_back(codegen(type: t)._t) + } + return self.__structTypeUnsafe(types) + } + } + + func codegen(type: ValueType) -> IRType { + switch type { + case .i32: + self.__i32TypeUnsafe() + case .i64: + self.__i64TypeUnsafe() + case .f32: + self.__f32TypeUnsafe() + case .f64: + self.__f64TypeUnsafe() + case .v128, .ref(_): + fatalError() + } + } + + func codegen(functionType: FunctionType) -> IRFunctionType { + var parameterTypes = IRTypeVector() + for parameterType in functionType.parameters { + parameterTypes.push_back(codegen(type: parameterType)._t) + } + + return self.__functionTypeUnsafe(parameterTypes, codegen(resultType: functionType.results)) + } +} diff --git a/Sources/LLVMBackend/IRFunctionVisitor.swift b/Sources/LLVMBackend/IRFunctionVisitor.swift new file mode 100644 index 00000000..2b508af6 --- /dev/null +++ b/Sources/LLVMBackend/IRFunctionVisitor.swift @@ -0,0 +1,430 @@ +import LLVMInterop +import WasmParser +import WasmTypes + +struct IRFunctionVisitor: InstructionVisitor, ~Copyable { + enum Error: Swift.Error { + case irFunctionUnknown(Int) + case irFunctionCreationFailed + case irFunctionStackNotEmpty + case irFunctionVerificationFailure(String) + } + + struct Local { + let type: IRType + let address: IRValue + } + private var locals: [Local] + + private var ir: IRContext + let function: IRFunction + + var binaryOffset = 0 + + private(set) var stack = [IRValue]() + private(set) var types = [FunctionType]() + private(set) var functionTypes = [TypeIndex]() + private(set) var functionNames = [Int: String]() + private(set) var importedFunctions = [IRValue]() + private(set) var memories = [Memory]() + + private let name: String + private let code: Code + private var blocks: [IRBlock] + private var currentBlock: IRBlock + + /// Blocks that haven't had their own instructions visited yet, but are needed as targets of `br` instructions + private var pendingBlocks = [IRBlock]() + + /// Trivial counter used for unique block names, incremented when new blocks are created. + private var blockCounter = 0 + + /// Representation of nested Wasm blocks to keep track of when converting to a linear sequence of LLVM IR basic blocks. + struct NestedWasmBlock { + enum Kind { + case `if`(condition: IRValue) + case loop + case plain + } + + let kind: Kind + + let parent: IRBlock + let resultType: IRType? + + var branchBlocks = [IRBlock]() + var phiValues = [IRValue]() + } + + private var nestedBlocks = [NestedWasmBlock]() + + mutating func push(_ value: IRValue) { + self.stack.append(value) + } + + mutating func pop() -> IRValue { + self.stack.removeLast() + } + + init(name: String, type: FunctionType, locals: [ValueType], code: Code, ir: IRContext) throws { + let functionType = ir.codegen(functionType: type) + + guard let function = ir.function(type: functionType, name: name) else { + throw Error.irFunctionCreationFailed + } + + self.name = name + self.code = code + self.ir = ir + + self.currentBlock = "entry".withStringRef { ir.__blockUnsafe(function, $0) } + self.blocks = [self.currentBlock] + self.ir.setInsertPoint(self.currentBlock) + + self.locals = locals.enumerated().map { + let type = ir.codegen(type: $1) + let address = ir.__createLocalUnsafe(type) + let result = IRFunctionVisitor.Local( + type: type, + address: address + ) + + ir.setLocal(address, function.getArgument(UInt32($0))) + + return result + } + + self.function = function + } + + mutating func createBlock() { + defer { self.blockCounter += 1 } + "bb\(blockCounter)".withStringRef { + self.currentBlock = self.ir.__blockUnsafe(self.function, $0) + } + self.blocks.append(self.currentBlock) + self.ir.setInsertPoint(self.currentBlock) + } + + mutating func visit( + _ types: [FunctionType], + _ functionTypes: [TypeIndex], + _ memories: [Memory], + importedFunctions: [IRValue], + functionNames: [Int: String] + ) throws { + self.types = types + self.functionTypes = functionTypes + self.memories = memories + self.importedFunctions = importedFunctions + self.functionNames = functionNames + + self.ir.setInsertPoint(self.currentBlock) + + try self.code.parseExpression(visitor: &self) + + // optimization passes don't like empty blocks + if self.currentBlock.isEmpty() && self.blocks.count > 1 { + self.currentBlock.eraseFromParent() + self.blocks.removeLast() + self.currentBlock = self.blocks.last! + self.ir.setInsertPoint(self.currentBlock) + } + + self.ir.createRet(pop()) + + guard self.stack.isEmpty else { + throw Error.irFunctionStackNotEmpty + } + + if let error = self.function.verify().value { + throw Error.irFunctionVerificationFailure(.init(error)) + } + + self.ir.optimize(self.function) + + if let error = self.function.verify().value { + throw Error.irFunctionVerificationFailure(.init(error)) + } + } + + mutating func visitCall(functionIndex: UInt32) throws { + let functionIndex = Int(functionIndex) + + guard + self.functionNames.count > functionIndex, + let f = self.functionNames[functionIndex]?.withStringRef({ ir.getFunction($0) }).value + else { + throw Error.irFunctionUnknown(functionIndex) + } + + let argTypes = self.types[Int(self.functionTypes[functionIndex])].parameters + var args = IRValueVector() + + for _ in 0.. 1 { + self.ir.__condBrUnsafe( + condition, lastNestedBlock.branchBlocks[0], lastNestedBlock.branchBlocks[1]) + } else { + self.ir.__condBrUnsafe(condition, lastNestedBlock.branchBlocks[0]) + } + } + + self.createBlock() + + if case .if = lastNestedBlock.kind, let resultType = lastNestedBlock.resultType { + // Make sure that `branchBlocks` have terminators. + for block in lastNestedBlock.branchBlocks { + self.ir.setInsertPoint(block) + + // Terminators of `if` blocks should branch to the current block we've just created. + self.ir.br(self.currentBlock) + } + // Set the insertion point back after it was moved for `branchBlocks`. + self.ir.setInsertPoint(self.currentBlock) + + var phi = self.ir.__phiUnsafe( + resultType, .init(lastNestedBlock.phiValues.count) + ) + + for (value, block) in zip(lastNestedBlock.phiValues, lastNestedBlock.branchBlocks) { + phi.addIncoming(value, block) + } + + self.push(IRValue(phi)) + } + + self.nestedBlocks.removeLast() + } + + mutating func visitBlock(blockType: BlockType) throws { fatalError() } + mutating func visitLoop(blockType: BlockType) throws { fatalError() } + + mutating func visitIf(blockType: BlockType) throws { + let stackTop = self.pop() + let conditionValue = self.ir.__i32ValueUnsafe(1) + let condition = self.ir.__bEqUnsafe(stackTop, conditionValue) + + switch blockType { + case .funcType(let index): + fatalError("multi-value blocks are currently not supported") + + case .empty: + self.nestedBlocks.append( + .init(kind: .if(condition: condition), parent: self.currentBlock, resultType: nil)) + + case .type(let type): + self.nestedBlocks.append( + .init( + kind: .if(condition: condition), parent: self.currentBlock, + resultType: self.ir.codegen(type: type)) + ) + } + + createBlock() + } + + mutating func visitElse() throws { + guard let lastBlock = self.nestedBlocks.last, case .if = lastBlock.kind else { + fatalError() + } + + if lastBlock.resultType != nil { + self.nestedBlocks[self.nestedBlocks.count - 1].phiValues.append(pop()) + } + + self.nestedBlocks[self.nestedBlocks.count - 1].branchBlocks.append(self.currentBlock) + + self.createBlock() + } + + mutating func visitBr(relativeDepth: UInt32) throws { fatalError() } + + mutating func visitBrIf(relativeDepth: UInt32) throws { fatalError() } + mutating func visitBrTable(targets: BrTable) throws { fatalError() } + mutating func visitReturn() throws { fatalError() } + mutating func visitCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws { fatalError() } + mutating func visitReturnCall(functionIndex: UInt32) throws { fatalError() } + mutating func visitReturnCallIndirect(typeIndex: UInt32, tableIndex: UInt32) throws { + fatalError() + } + mutating func visitSelect() throws { fatalError() } + mutating func visitTypedSelect(type: ValueType) throws { fatalError() } + mutating func visitLocalSet(localIndex: UInt32) throws { fatalError() } + mutating func visitLocalTee(localIndex: UInt32) throws { fatalError() } + mutating func visitGlobalGet(globalIndex: UInt32) throws { fatalError() } + mutating func visitGlobalSet(globalIndex: UInt32) throws { fatalError() } + mutating func visitLoad(_ load: Instruction.Load, memarg: MemArg) throws { fatalError() } + mutating func visitStore(_ store: Instruction.Store, memarg: MemArg) throws { fatalError() } + mutating func visitMemorySize(memory: UInt32) throws { fatalError() } + mutating func visitMemoryGrow(memory: UInt32) throws { fatalError() } + mutating func visitI64Const(value: Int64) throws { + self.push(self.ir.__i64ValueUnsafe(.init(bitPattern: value))) + } + mutating func visitF32Const(value: IEEE754.Float32) throws { fatalError() } + mutating func visitF64Const(value: IEEE754.Float64) throws { fatalError() } + + mutating func visitI32Eqz() throws { + let stackTop = self.pop() + let value = self.ir.__i32ValueUnsafe(0) + self.push(self.ir.__iEqUnsafe(stackTop, value)) + } + + mutating func visitI64Eqz() throws { + let stackTop = self.pop() + let value = self.ir.__i64ValueUnsafe(0) + self.push(self.ir.__iEqUnsafe(stackTop, value)) + } + + mutating func visitUnary(_ unary: Instruction.Unary) throws { fatalError() } + mutating func visitConversion(_ conversion: Instruction.Conversion) throws { fatalError() } + mutating func visitMemoryInit(dataIndex: UInt32) throws { fatalError() } + mutating func visitDataDrop(dataIndex: UInt32) throws { fatalError() } + mutating func visitMemoryCopy(dstMem: UInt32, srcMem: UInt32) throws { fatalError() } + mutating func visitMemoryFill(memory: UInt32) throws { fatalError() } + + mutating func visitRefNull(type: ReferenceType) throws { fatalError() } + mutating func visitRefIsNull() throws { fatalError() } + mutating func visitRefFunc(functionIndex: UInt32) throws { fatalError() } + + mutating func visitTableInit(elemIndex: UInt32, table: UInt32) throws { fatalError() } + mutating func visitElemDrop(elemIndex: UInt32) throws { fatalError() } + mutating func visitTableCopy(dstTable: UInt32, srcTable: UInt32) throws { fatalError() } + mutating func visitTableFill(table: UInt32) throws { fatalError() } + mutating func visitTableGet(table: UInt32) throws { fatalError() } + mutating func visitTableSet(table: UInt32) throws { fatalError() } + mutating func visitTableGrow(table: UInt32) throws { fatalError() } + mutating func visitTableSize(table: UInt32) throws { fatalError() } + + private mutating func codegen(value: Value) { + switch value { + case .f32(let f32): + push(ir.__f32ValueUnsafe(.init(bitPattern: f32))) + case .f64(let f64): + push(ir.__f64ValueUnsafe(.init(bitPattern: f64))) + case .i32(let i32): + push(ir.__i32ValueUnsafe(i32)) + case .i64(let i64): + push(ir.__i64ValueUnsafe(i64)) + case .ref: + fatalError() + } + } + + // mutating func codegen(conversion instruction: NumericInstruction.Conversion) { + // let v = pop() + // + // switch instruction { + // case .wrap: + // push(ir.__wrapUnsafe(v)) + // case .extendUnsigned: + // push(ir.__extendUnsignedUnsafe(v)) + // case .extendSigned: + // push(ir.__extendSignedUnsafe(v)) + // default: + // fatalError() + // } + // } +} + +extension IRFunction: @retroactive CustomStringConvertible { + public var description: String { + .init(self.print().value!) + } +} + +extension IRFunctionType: @retroactive CustomStringConvertible { + public var description: String { + .init(self.print()) + } +} + +extension IRType: @retroactive CustomStringConvertible { + public var description: String { + .init(self.print()) + } +} + +extension IRValue: @retroactive CustomStringConvertible { + public var description: String { + .init(self.print()) + } +} diff --git a/Sources/LLVMBackend/Linker.swift b/Sources/LLVMBackend/Linker.swift new file mode 100644 index 00000000..9b7a0240 --- /dev/null +++ b/Sources/LLVMBackend/Linker.swift @@ -0,0 +1,41 @@ +import Subprocess +import SystemPackage + +package enum Linker { + enum Error: Swift.Error { + case unexpectedTerminationStatus(TerminationStatus) + } + + package static func link(objectFilePath: FilePath) async throws -> FilePath { + var dylibPath = objectFilePath + let arguments: Arguments + #if os(macOS) + dylibPath.extension = "dylib" + arguments = [ + "-dylib", objectFilePath.string, + "-lSystem", "-L", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib", + "-o", dylibPath.string, + ] + #elseif os(Linux) + dylibPath.extension = "so" + arguments = [ + "-shared", objectFilePath.string, + "-o", dylibPath.string, + ] + #else + #error("Linking native binaries is currently only supported on macOS and Linux") + #endif + + let result = try await run( + .name("ld"), + arguments: arguments, + output: .standardError + ) + + guard result.terminationStatus.isSuccess else { + throw Error.unexpectedTerminationStatus(result.terminationStatus) + } + + return dylibPath + } +} diff --git a/Sources/LLVMBackend/Loader.swift b/Sources/LLVMBackend/Loader.swift new file mode 100644 index 00000000..fc209be4 --- /dev/null +++ b/Sources/LLVMBackend/Loader.swift @@ -0,0 +1,98 @@ +import SystemPackage + +#if canImport(Darwin) + import Darwin +#elseif canImport(Glibc) + import Glibc +#elseif canImport(Musl) + import Musl +#endif + +package protocol ImportedFunctionArguments { + associatedtype ResultType + + func apply(symbol: UnsafeMutableRawPointer) -> ResultType +} + +package struct U64Args1Result1: ImportedFunctionArguments { + typealias CType = @convention(c) (UInt64) -> UInt64 + + private let args: UInt64 + + package init(_ first: UInt64) { + self.args = first + } + + package func apply(symbol: UnsafeMutableRawPointer) -> UInt64 { + unsafeBitCast(symbol, to: CType.self)(self.args) + } +} + +package struct U64Args2Result1: ImportedFunctionArguments { + typealias CType = @convention(c) (UInt64, UInt64) -> UInt64 + + private let args: (UInt64, UInt64) + + package init(_ first: UInt64, _ second: UInt64) { + self.args = (first, second) + } + + package func apply(symbol: UnsafeMutableRawPointer) -> UInt64 { + unsafeBitCast(symbol, to: CType.self)(self.args.0, self.args.1) + } +} + +package struct U32Args2Result1: ImportedFunctionArguments { + typealias CType = @convention(c) (UInt32, UInt32) -> UInt32 + + private let args: (UInt32, UInt32) + + package init(_ first: UInt32, _ second: UInt32) { + self.args = (first, second) + } + + package func apply(symbol: UnsafeMutableRawPointer) -> UInt32 { + unsafeBitCast(symbol, to: CType.self)(self.args.0, self.args.1) + } +} + +package struct Loader: ~Copyable { + enum Error: Swift.Error { + case symbolNotFound(String) + case dlopenFailed + } + + package enum ClosureType { + case u32Args2Result1(UInt32, UInt32) + } + + let memory: Wasm32Memory? + + package init(memory: consuming Wasm32Memory?) { + self.memory = memory + } + + package func load( + library: FilePath, + entrypointSymbol: String, + arguments: T + ) throws -> T.ResultType { + #if canImport(Darwin) + let handle = dlopen(library.string, RTLD_LAZY) + #elseif canImport(Glibc) || canImport(Musl) + guard let handle = dlopen(library.string, RTLD_LAZY) else { + throw Error.dlopenFailed + } + #else + #error("Unsupported platform in dynamic loading code") + #endif + + defer { dlclose(handle) } + + guard let symbol = dlsym(handle, entrypointSymbol) else { + throw Error.symbolNotFound(entrypointSymbol) + } + + return arguments.apply(symbol: symbol) + } +} diff --git a/Sources/LLVMBackend/Wasm32Memory.swift b/Sources/LLVMBackend/Wasm32Memory.swift new file mode 100644 index 00000000..4dbdd2de --- /dev/null +++ b/Sources/LLVMBackend/Wasm32Memory.swift @@ -0,0 +1,10 @@ +package struct Wasm32Memory: ~Copyable { + // let memory: UnsafeMutableRawPointer + + init() { + // 1. fd = open(tmpfile) + // 2. unlink(tmpfile) + // 3. memory = mmap(nullptr, 8GB (4GB base + 4GB offset), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0) + // 4. fruncate(fd) to initialize or handle `memory.grow` + } +} diff --git a/Sources/LLVMBackendCLI/CMakeLists.txt b/Sources/LLVMBackendCLI/CMakeLists.txt new file mode 100644 index 00000000..8cff0046 --- /dev/null +++ b/Sources/LLVMBackendCLI/CMakeLists.txt @@ -0,0 +1,44 @@ +add_executable( + wasmkit-llvm + + Entrypoint.swift +) +target_compile_options( + wasmkit-llvm + PRIVATE + -parse-as-library + -package-name WasmKitPackage + -module-name LLVMBackendCLI +) +set(BUILD_TESTING OFF) + +find_package(ArgumentParser CONFIG) +if(NOT ArgumentParser_FOUND) + message("-- Vending ArgumentParser") + FetchContent_Declare( + ArgumentParser + GIT_REPOSITORY https://github.com/apple/swift-argument-parser + GIT_TAG 1.6.2 + ) + FetchContent_MakeAvailable(ArgumentParser) +endif() + +add_dependencies(wasmkit-llvm ArgumentParser CLICommands LLVMBackend) + +target_link_libraries( + wasmkit-llvm + + ArgumentParser + CLICommands + LLVMBackend +) + +install(TARGETS wasmkit-llvm + RUNTIME DESTINATION bin) + +if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_custom_command(TARGET wasmkit-llvm + POST_BUILD COMMAND + ${CMAKE_INSTALL_NAME_TOOL} -change "@rpath/libswiftCompatibilitySpan.dylib" "/Library/Developer/CommandLineTools/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib" + $) +endif() diff --git a/Sources/LLVMBackendCLI/Entrypoint.swift b/Sources/LLVMBackendCLI/Entrypoint.swift new file mode 100644 index 00000000..e11b77cb --- /dev/null +++ b/Sources/LLVMBackendCLI/Entrypoint.swift @@ -0,0 +1,61 @@ +import ArgumentParser +import CLICommands +import Foundation +import LLVMBackend +import SystemPackage + +@main +struct Entrypoint: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "wasmkit", + abstract: "WasmKit LLVM Backend", + subcommands: [ + Run.self, + Wat2wasm.self, + ] + ) +} + +struct Run: AsyncParsableCommand { + @Argument(help: "Path to a `.wasm` file to operate on.") + var path: String + + @Flag(name: [.long, .short]) + var verbose: Bool = false + + @Argument( + parsing: .captureForPassthrough, + help: ArgumentHelp( + "Name of an exported function to call with space-separated function arguments encoded as `:`, e.g. `i32:42`", + valueName: "arguments" + ) + ) + var arguments: [String] = [] + + func run() async throws { + let wasmPath = + if FilePath(self.path).isAbsolute { + FilePath(self.path) + } else { + FilePath(FileManager.default.currentDirectoryPath).appending(path) + } + + var context = CodegenContext(isVerbose: verbose) + let objectFilePath = try context.emitObjectFile(wasmPath: wasmPath) + let libraryFilePath = try await Linker.link(objectFilePath: objectFilePath) + + let (functionName, functionArguments) = CLICommands.Run.parseInvocation(arguments: self.arguments) + + guard let functionName, functionArguments.count == 1, case .i64(let functionArgument) = functionArguments.first else { + fatalError("Only single-parameter i64 entrypoint functions are currently supported. Arguments passed: \(functionArguments)") + } + + let result = try Loader(memory: nil).load( + library: libraryFilePath, + entrypointSymbol: functionName, + arguments: U64Args1Result1(functionArgument) + ) + + print(result) + } +} diff --git a/Sources/LLVMInterop/CMakeLists.txt b/Sources/LLVMInterop/CMakeLists.txt new file mode 100644 index 00000000..cd2d6786 --- /dev/null +++ b/Sources/LLVMInterop/CMakeLists.txt @@ -0,0 +1,43 @@ + + +find_package(LLVM CONFIG REQUIRED) +message(STATUS "Found LLVM headers: ${LLVM_MAIN_INCLUDE_DIR}") +message(STATUS "Found LLVM generated headers: ${LLVM_INCLUDE_DIR}") + +add_library( + LLVMInterop + + IRContext.cpp + IR/IRFunction.cpp + IR/IRPHINode.cpp +) + +target_compile_features(LLVMInterop PRIVATE cxx_std_17) + +set(LLVMINTEROP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") + +target_include_directories( + LLVMInterop + PUBLIC + "${LLVMINTEROP_INCLUDE_DIR}" + "${LLVM_MAIN_INCLUDE_DIR}" + "${LLVM_INCLUDE_DIR}" +) + +llvm_map_components_to_libnames(llvm_libs + support + core irreader mc passes targetparser target + ${WASMKIT_LLVM_BACKEND_TARGET}codegen ${WASMKIT_LLVM_BACKEND_TARGET}asmparser +) + +target_link_libraries(LLVMInterop ${llvm_libs}) + +install( + TARGETS LLVMInterop + # shared libraries + LIBRARY DESTINATION lib + # for static libraries + ARCHIVE DESTINATION lib + # public headers + INCLUDES DESTINATION include +) diff --git a/Sources/LLVMInterop/IR/IRFunction.cpp b/Sources/LLVMInterop/IR/IRFunction.cpp new file mode 100644 index 00000000..6b159978 --- /dev/null +++ b/Sources/LLVMInterop/IR/IRFunction.cpp @@ -0,0 +1,24 @@ +#include "IRFunction.h" + +optional IRFunction::print() const { + string string = ""; + raw_string_ostream stream(string); + + if (this->_isValid) { + this->_f->print(stream); + return string; + } else { + return nullopt; + } +} + +optional IRFunction::verify() const { + string string = ""; + raw_string_ostream stream(string); + + if (verifyFunction(*(this->_f), &stream)) { + return string; + } else { + return nullopt; + } +} diff --git a/Sources/LLVMInterop/IR/IRPHINode.cpp b/Sources/LLVMInterop/IR/IRPHINode.cpp new file mode 100644 index 00000000..1ab32381 --- /dev/null +++ b/Sources/LLVMInterop/IR/IRPHINode.cpp @@ -0,0 +1,7 @@ +#include "IRPHINode.h" +#include "IRBlock.h" +#include "IRValue.h" + +void IRPHINode::addIncoming(IRValue v, IRBlock b) { + this->_n->addIncoming(v._v, b._b); +} diff --git a/Sources/LLVMInterop/IRContext.cpp b/Sources/LLVMInterop/IRContext.cpp new file mode 100644 index 00000000..803d5daf --- /dev/null +++ b/Sources/LLVMInterop/IRContext.cpp @@ -0,0 +1,247 @@ +#include "IRContext.h" + +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +IRContext::IRContext() + : _context(make_shared()), + _module(make_shared("codegen", *_context)), + _builder(make_shared>(*_context)), + _fpm(make_shared()), + _lam(make_shared()), + _fam(make_shared()), + _cgam(make_shared()), + _mam(make_shared()), + _pic(make_shared()), + _si(make_shared(*_context, true)) { + _si->registerCallbacks(*_pic, _mam.get()); + + _fpm->addPass(PromotePass()); + _fpm->addPass(InstCombinePass()); + _fpm->addPass(ReassociatePass()); + _fpm->addPass(GVNPass()); + _fpm->addPass(SimplifyCFGPass()); + + // Register analysis passes used in these transform passes. + PassBuilder pb; + pb.registerModuleAnalyses(*_mam); + pb.registerFunctionAnalyses(*_fam); + pb.crossRegisterProxies(*_lam, *_fam, *_cgam, *_mam); + +#if __aarch64__ + LLVMInitializeAArch64TargetInfo(); + LLVMInitializeAArch64Target(); + LLVMInitializeAArch64TargetMC(); + LLVMInitializeAArch64AsmParser(); + LLVMInitializeAArch64AsmPrinter(); +#elif __x86_64__ + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86AsmParser(); + LLVMInitializeX86AsmPrinter(); +#endif +} + +bool IRContext::emitObjectFile(StringRef path) const { + auto targetTriple = sys::getDefaultTargetTriple(); + Triple triple(targetTriple); + _module->setTargetTriple(triple); + + std::string error; + auto target = TargetRegistry::lookupTarget(targetTriple, error); + + // Print an error and exit if we couldn't find the requested target. + // This generally occurs if we've forgotten to initialise the + // TargetRegistry or we have a bogus target triple. + if (!target) { + errs() << error; + return false; + } + + auto cpu = "generic"; + auto features = ""; + + TargetOptions opt; + auto targetMachine = target->createTargetMachine(triple, cpu, features, + opt, Reloc::PIC_); + + _module->setDataLayout(targetMachine->createDataLayout()); + + std::error_code ec; + raw_fd_ostream dest(path, ec, sys::fs::OpenFlags::OF_None); + + if (ec) { + errs() << "Could not open file: " << ec.message(); + return false; + } + + auto fileType = CodeGenFileType::ObjectFile; + + legacy::PassManager pass; + targetMachine->addPassesToEmitFile(pass, dest, nullptr, fileType); + pass.run(*_module); + dest.flush(); + + return true; +} + +std::string IRContext::printModule() const { + std::string string = ""; + raw_string_ostream stream(string); + + this->_module->print(stream, nullptr); + return string; +} + +IRValue IRContext::createImportedFunction(StringRef name, IRPointerType type) { + GlobalVariable *result = + new GlobalVariable(*this->_module, type._pt, false, + llvm::GlobalValue::ExternalLinkage, nullptr, name); + + return result; +} + +std::optional IRContext::getFunction(StringRef name) const { + Function *f = _module->getFunction(name); + if (f) { + return IRFunction(f); + } else { + return std::nullopt; + } +} + +IRValue IRContext::createLocal(IRType type) const { + return _builder->CreateAlloca(type._t); +} + +IRValue IRContext::getLocal(IRType type, IRValue address) const { + return _builder->CreateLoad(type._t, address._v); +} + +void IRContext::setLocal(IRValue address, IRValue value) const { + _builder->CreateStore(value._v, address._v); +} + +void IRContext::br(IRBlock successor) const { + _builder->CreateBr(successor._b); +} + +IRValue IRContext::condBr(IRValue condition, IRBlock trueBlock) const { + + return _builder->CreateCondBr(condition._v, trueBlock._b, nullptr); +} + +IRValue IRContext::condBr(IRValue condition, IRBlock trueBlock, + IRBlock falseBlock) const { + + return _builder->CreateCondBr(condition._v, trueBlock._b, falseBlock._b); +} + +IRValue IRContext::unreachable() const { + return _builder->CreateIntrinsic(Intrinsic::trap, {}); +} + +IRValue IRContext::bEq(IRValue lhs, IRValue rhs) const { + return _builder->CreateICmpEQ(lhs._v, rhs._v); +} + +IRValue IRContext::iAdd(IRValue lhs, IRValue rhs) const { + return _builder->CreateAdd(lhs._v, rhs._v); +} + +IRValue IRContext::fAdd(IRValue lhs, IRValue rhs) const { + return _builder->CreateFAdd(lhs._v, rhs._v); +} + +IRValue IRContext::iSub(IRValue lhs, IRValue rhs) const { + return _builder->CreateSub(lhs._v, rhs._v); +} + +IRValue IRContext::fSub(IRValue lhs, IRValue rhs) const { + return _builder->CreateFSub(lhs._v, rhs._v); +} + +IRValue IRContext::iMul(IRValue lhs, IRValue rhs) const { + return _builder->CreateMul(lhs._v, rhs._v); +} + +IRValue IRContext::fMul(IRValue lhs, IRValue rhs) const { + return _builder->CreateFMul(lhs._v, rhs._v); +} + +IRValue IRContext::iEq(IRValue lhs, IRValue rhs) const { + auto i32 = Type::getInt32Ty(*_context); + auto eqResult = _builder->CreateICmpEQ(lhs._v, rhs._v); + return _builder->CreateZExt(eqResult, i32); +} + +IRValue IRContext::fEq(IRValue lhs, IRValue rhs) const { + auto i32 = Type::getInt32Ty(*_context); + auto eqResult = _builder->CreateFCmpUEQ(lhs._v, rhs._v); + return _builder->CreateZExt(eqResult, i32); +} + +IRValue IRContext::iNe(IRValue lhs, IRValue rhs) const { + auto i32 = Type::getInt32Ty(*_context); + auto eqResult = _builder->CreateICmpEQ(lhs._v, rhs._v); + auto neResult = _builder->CreateNot(eqResult); + return _builder->CreateZExt(neResult, i32); +} + +IRValue IRContext::fNe(IRValue lhs, IRValue rhs) const { + auto i32 = Type::getInt32Ty(*_context); + auto eqResult = _builder->CreateFCmpUEQ(lhs._v, rhs._v); + auto neResult = _builder->CreateNot(eqResult); + return _builder->CreateZExt(neResult, i32); +} + +IRValue IRContext::wrap(IRValue value) const { + auto i32 = Type::getInt32Ty(*_context); + return _builder->CreateTrunc(value._v, i32); +} + +IRValue IRContext::extendUnsigned(IRValue value) const { + auto i64 = Type::getInt64Ty(*_context); + return _builder->CreateZExt(value._v, i64); +} + +IRValue IRContext::extendSigned(IRValue value) const { + auto i64 = Type::getInt64Ty(*_context); + return _builder->CreateSExt(value._v, i64); +} + +IRFunctionType IRContext::functionType(IRTypeVector parameters, + IRType result) const { + return FunctionType::get(result._t, parameters, false); +} + +IRPointerType IRContext::pointerType() const { + return PointerType::getUnqual(*_context); +} + +IRValue IRContext::call(IRFunction callee, IRValueVector args) { + return _builder->CreateCall(callee._f, args); +} + +IRPHINode IRContext::phi(IRType type, unsigned int incomingCount) const { + return _builder->CreatePHI(type._t, incomingCount); +} + +std::string IRFunctionType::print() const { + std::string string = ""; + raw_string_ostream stream(string); + + this->_ft->print(stream); + return string; +} diff --git a/Sources/LLVMInterop/include/IRBlock.h b/Sources/LLVMInterop/include/IRBlock.h new file mode 100644 index 00000000..ce498516 --- /dev/null +++ b/Sources/LLVMInterop/include/IRBlock.h @@ -0,0 +1,19 @@ +#ifndef IRBlock_h +#define IRBlock_h + +#include +#include + +using namespace llvm; + +class IRBlock { +public: + BasicBlock *_b; + + IRBlock(BasicBlock *b) : _b(b) {} + + bool isEmpty() { return _b->empty(); } + void eraseFromParent() { _b->eraseFromParent(); } +}; + +#endif /* IRBlock_h */ diff --git a/Sources/LLVMInterop/include/IRContext.h b/Sources/LLVMInterop/include/IRContext.h new file mode 100644 index 00000000..b0a3d37d --- /dev/null +++ b/Sources/LLVMInterop/include/IRContext.h @@ -0,0 +1,136 @@ +#ifndef IRContext_h +#define IRContext_h + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IRBlock.h" +#include "IRFunctionType.h" +#include "IRPointerType.h" +#include "IRFunction.h" +#include "IRPHINode.h" +#include "IRType.h" +#include "IRValue.h" + +using namespace llvm; +using namespace std; + +class IRContext { + shared_ptr _context; + shared_ptr _module; + shared_ptr> _builder; + + shared_ptr _fpm; + shared_ptr _lam; + shared_ptr _fam; + shared_ptr _cgam; + shared_ptr _mam; + shared_ptr _pic; + shared_ptr _si; + +public: + IRContext(); + + bool emitObjectFile(StringRef path) const; + + void optimize(IRFunction f) { _fpm->run(*f._f, *_fam); } + + string printModule() const; + + optional getFunction(StringRef name) const; + + IRValue createImportedFunction(StringRef name, IRPointerType type); + + IRValue f32Value(float f32) const { + return ConstantFP::get(*_context, APFloat(f32)); + } + + IRValue f64Value(double f64) const { + return ConstantFP::get(*_context, APFloat(f64)); + } + + IRValue i64Value(uint64_t i64) const { + return ConstantInt::get(*_context, APInt(64, i64)); + } + + IRValue i32Value(uint32_t i32) const { + return ConstantInt::get(*_context, APInt(32, i32)); + } + + IRValue createLocal(IRType type) const; + IRValue getLocal(IRType type, IRValue local) const; + void setLocal(IRValue local, IRValue value) const; + + IRValue call(IRFunction callee, IRValueVector args); + + void br(IRBlock successor) const; + IRValue condBr(IRValue condition, IRBlock trueBlock) const; + IRValue condBr(IRValue condition, IRBlock trueBlock, + IRBlock falseBlock) const; + IRPHINode phi(IRType type, unsigned int incomingCount) const; + + IRValue unreachable() const; + + IRValue bEq(IRValue lhs, IRValue rhs) const; + + IRValue iAdd(IRValue lhs, IRValue rhs) const; + IRValue fAdd(IRValue lhs, IRValue rhs) const; + IRValue iSub(IRValue lhs, IRValue rhs) const; + IRValue fSub(IRValue lhs, IRValue rhs) const; + IRValue iMul(IRValue lhs, IRValue rhs) const; + IRValue fMul(IRValue lhs, IRValue rhs) const; + IRValue iEq(IRValue lhs, IRValue rhs) const; + IRValue fEq(IRValue lhs, IRValue rhs) const; + IRValue iNe(IRValue lhs, IRValue rhs) const; + IRValue fNe(IRValue lhs, IRValue rhs) const; + + IRValue wrap(IRValue value) const; + IRValue extendUnsigned(IRValue value) const; + IRValue extendSigned(IRValue value) const; + + IRFunctionType functionType(IRTypeVector parameters, IRType result) const; + IRPointerType pointerType() const; + + IRType i32Type() const { return Type::getInt32Ty(*_context); } + IRType i64Type() const { return Type::getInt64Ty(*_context); } + IRType f32Type() const { return Type::getFloatTy(*_context); } + IRType f64Type() const { return Type::getDoubleTy(*_context); } + + IRType voidType() const { return Type::getVoidTy(*_context); } + + IRType structType(IRTypeVector types) const { + return StructType::create(types); + } + + IRBlock block(IRFunction function, StringRef name) const { + return BasicBlock::Create(*_context, name, function._f); + } + + void setInsertPoint(IRBlock block) { _builder->SetInsertPoint(block._b); } + + void createRet(IRValue value) { _builder->CreateRet(value._v); } + + IRFunction function(IRFunctionType type, StringRef name) const { + return Function::Create(type._ft, Function::ExternalLinkage, name, + _module.get()); + } +}; + +#endif /* IRContext_h */ diff --git a/Sources/LLVMInterop/include/IRFunction.h b/Sources/LLVMInterop/include/IRFunction.h new file mode 100644 index 00000000..0176d8c8 --- /dev/null +++ b/Sources/LLVMInterop/include/IRFunction.h @@ -0,0 +1,28 @@ +#ifndef IRFunction_h +#define IRFunction_h + + +#include +#include + +#include "IRValue.h" + +using namespace llvm; +using namespace std; + +class IRFunction { +public: + Function *_f; + + bool _isValid; + + IRFunction() : _f(nullptr), _isValid(false) {} + IRFunction(Function *f) : _f(f), _isValid(true) {} + + IRValue getArgument(uint32_t i) const { return _f->getArg(i); } + + optional print() const; + optional verify() const; +}; + +#endif /* IRFunction_h */ diff --git a/Sources/LLVMInterop/include/IRFunctionType.h b/Sources/LLVMInterop/include/IRFunctionType.h new file mode 100644 index 00000000..167204c0 --- /dev/null +++ b/Sources/LLVMInterop/include/IRFunctionType.h @@ -0,0 +1,14 @@ +#include + +using namespace llvm; +using namespace std; + + +class IRFunctionType { +public: + FunctionType *_ft; + + IRFunctionType(FunctionType *ft) : _ft(ft) {} + + string print() const; +}; diff --git a/Sources/LLVMInterop/include/IRPHINode.h b/Sources/LLVMInterop/include/IRPHINode.h new file mode 100644 index 00000000..16cd34b7 --- /dev/null +++ b/Sources/LLVMInterop/include/IRPHINode.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class IRValue; +class IRBlock; + +using namespace llvm; + +class IRPHINode { +public: + PHINode *_n; + + IRPHINode(PHINode *n) : _n(n) {} + + void addIncoming(IRValue v, IRBlock b); +}; diff --git a/Sources/LLVMInterop/include/IRPointerType.h b/Sources/LLVMInterop/include/IRPointerType.h new file mode 100644 index 00000000..dabc338d --- /dev/null +++ b/Sources/LLVMInterop/include/IRPointerType.h @@ -0,0 +1,11 @@ +#include + +using namespace llvm; +using namespace std; + +class IRPointerType { +public: + PointerType *_pt; + + IRPointerType(PointerType *pt): _pt(pt) {} +}; diff --git a/Sources/LLVMInterop/include/IRType.h b/Sources/LLVMInterop/include/IRType.h new file mode 100644 index 00000000..6fc178a5 --- /dev/null +++ b/Sources/LLVMInterop/include/IRType.h @@ -0,0 +1,24 @@ +#ifndef IRType_h +#define IRType_h + +#include + +using namespace llvm; + +class IRType { +public: + Type *_t; + IRType(Type *t) : _t(t) {} + + std::string print() const { + string string = ""; + raw_string_ostream stream(string); + + this->_t->print(stream); + return string; + } +}; + +using IRTypeVector = std::vector; + +#endif /* IRType_h */ diff --git a/Sources/LLVMInterop/include/IRValue.h b/Sources/LLVMInterop/include/IRValue.h new file mode 100644 index 00000000..4a448d80 --- /dev/null +++ b/Sources/LLVMInterop/include/IRValue.h @@ -0,0 +1,24 @@ +#pragma once + +#include "IRPHINode.h" +#include + +using namespace llvm; + +using IRValueVector = std::vector; + +class IRValue { +public: + Value *_v; + + IRValue(Value *v) : _v(v) {} + IRValue(IRPHINode phi) : _v(phi._n) {} + + std::string print() const { + std::string string = ""; + raw_string_ostream stream(string); + + this->_v->print(stream); + return string; + } +}; diff --git a/Sources/LLVMInterop/include/module.modulemap b/Sources/LLVMInterop/include/module.modulemap new file mode 100644 index 00000000..4ed80f8c --- /dev/null +++ b/Sources/LLVMInterop/include/module.modulemap @@ -0,0 +1,4 @@ +module LLVMInterop { + header "IRContext.h" + requires cplusplus +} diff --git a/Sources/WasmParser/WasmTypes.swift b/Sources/WasmParser/WasmTypes.swift index 597257c2..98cd03c3 100644 --- a/Sources/WasmParser/WasmTypes.swift +++ b/Sources/WasmParser/WasmTypes.swift @@ -52,7 +52,7 @@ public enum BlockType: Equatable { /// > Note: /// -public struct Limits: Equatable { +public struct Limits: Equatable, Sendable { public var min: UInt64 public var max: UInt64? public var isMemory64: Bool @@ -72,7 +72,7 @@ public typealias MemoryType = Limits /// > Note: /// -public struct TableType: Equatable { +public struct TableType: Equatable, Sendable { public var elementType: ReferenceType public var limits: Limits @@ -84,14 +84,14 @@ public struct TableType: Equatable { /// > Note: /// -public enum Mutability: Equatable { +public enum Mutability: Equatable, Sendable { case constant case variable } /// > Note: /// -public struct GlobalType: Equatable { +public struct GlobalType: Equatable, Sendable { public let mutability: Mutability public let valueType: ValueType @@ -255,7 +255,7 @@ public enum DataSegment: Equatable { /// Exported entity in a module /// > Note: /// -public struct Export: Equatable { +public struct Export: Equatable, Sendable { /// Name of the export public let name: String /// Descriptor of the export @@ -268,7 +268,7 @@ public struct Export: Equatable { } /// Export descriptor -public enum ExportDescriptor: Equatable { +public enum ExportDescriptor: Equatable, Sendable { /// Function export case function(FunctionIndex) /// Table export @@ -282,7 +282,7 @@ public enum ExportDescriptor: Equatable { /// Import entity in a module /// > Note: /// -public struct Import: Equatable { +public struct Import: Equatable, Sendable { /// Module name imported from public let module: String /// Name of the import @@ -298,7 +298,7 @@ public struct Import: Equatable { } /// Import descriptor -public enum ImportDescriptor: Equatable { +public enum ImportDescriptor: Equatable, Sendable { /// Function import case function(TypeIndex) /// Table import