[clang-repl] Support wasm execution (#86402)

This commit introduces support for running clang-repl and executing C++
code interactively inside a Javascript engine using WebAssembly when
built with Emscripten. This is achieved by producing WASM "shared
libraries" that can be loaded by the Emscripten runtime using dlopen()

More discussion is available in https://reviews.llvm.org/D158140

Co-authored-by: Anubhab Ghosh <anubhabghosh.me@gmail.com>
This commit is contained in:
Vassil Vassilev
2024-07-02 13:29:31 +03:00
committed by GitHub
parent 253a762619
commit 9a9546e30c
6 changed files with 179 additions and 4 deletions

View File

@@ -12,6 +12,10 @@ set(LLVM_LINK_COMPONENTS
TargetParser
)
if (EMSCRIPTEN AND "lld" IN_LIST LLVM_ENABLE_PROJECTS)
set(WASM_SRC Wasm.cpp)
endif()
add_clang_library(clangInterpreter
DeviceOffload.cpp
CodeCompletion.cpp
@@ -20,6 +24,8 @@ add_clang_library(clangInterpreter
Interpreter.cpp
InterpreterUtils.cpp
Value.cpp
${WASM_SRC}
PARTIAL_SOURCES_INTENDED
DEPENDS
intrinsics_gen

View File

@@ -36,6 +36,8 @@ LLVM_ATTRIBUTE_USED void linkComponents() {
}
namespace clang {
IncrementalExecutor::IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC)
: TSCtx(TSC) {}
llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
IncrementalExecutor::createDefaultJITBuilder(

View File

@@ -43,16 +43,19 @@ class IncrementalExecutor {
llvm::DenseMap<const PartialTranslationUnit *, llvm::orc::ResourceTrackerSP>
ResourceTrackers;
protected:
IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC);
public:
enum SymbolNameKind { IRName, LinkerName };
IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC,
llvm::orc::LLJITBuilder &JITBuilder, llvm::Error &Err);
~IncrementalExecutor();
virtual ~IncrementalExecutor();
llvm::Error addModule(PartialTranslationUnit &PTU);
llvm::Error removeModule(PartialTranslationUnit &PTU);
llvm::Error runCtors() const;
virtual llvm::Error addModule(PartialTranslationUnit &PTU);
virtual llvm::Error removeModule(PartialTranslationUnit &PTU);
virtual llvm::Error runCtors() const;
llvm::Error cleanUp();
llvm::Expected<llvm::orc::ExecutorAddr>
getSymbolAddress(llvm::StringRef Name, SymbolNameKind NameKind) const;

View File

@@ -15,6 +15,9 @@
#include "IncrementalExecutor.h"
#include "IncrementalParser.h"
#include "InterpreterUtils.h"
#ifdef __EMSCRIPTEN__
#include "Wasm.h"
#endif // __EMSCRIPTEN__
#include "clang/AST/ASTContext.h"
#include "clang/AST/Mangle.h"
@@ -186,6 +189,12 @@ IncrementalCompilerBuilder::CreateCpp() {
std::vector<const char *> Argv;
Argv.reserve(5 + 1 + UserArgs.size());
Argv.push_back("-xc++");
#ifdef __EMSCRIPTEN__
Argv.push_back("-target");
Argv.push_back("wasm32-unknown-emscripten");
Argv.push_back("-pie");
Argv.push_back("-shared");
#endif
Argv.insert(Argv.end(), UserArgs.begin(), UserArgs.end());
std::string TT = TargetTriple ? *TargetTriple : llvm::sys::getProcessTriple();
@@ -426,8 +435,12 @@ llvm::Error Interpreter::CreateExecutor() {
}
llvm::Error Err = llvm::Error::success();
#ifdef __EMSCRIPTEN__
auto Executor = std::make_unique<WasmIncrementalExecutor>(*TSCtx);
#else
auto Executor =
std::make_unique<IncrementalExecutor>(*TSCtx, *JITBuilder, Err);
#endif
if (!Err)
IncrExecutor = std::move(Executor);

View File

@@ -0,0 +1,114 @@
//===----------------- Wasm.cpp - Wasm Interpreter --------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements interpreter support for code execution in WebAssembly.
//
//===----------------------------------------------------------------------===//
#include "Wasm.h"
#include "IncrementalExecutor.h"
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/Module.h>
#include <llvm/MC/TargetRegistry.h>
#include <llvm/Target/TargetMachine.h>
#include <clang/Interpreter/Interpreter.h>
#include <string>
namespace lld {
namespace wasm {
bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
} // namespace wasm
} // namespace lld
#include <dlfcn.h>
namespace clang {
WasmIncrementalExecutor::WasmIncrementalExecutor(
llvm::orc::ThreadSafeContext &TSC)
: IncrementalExecutor(TSC) {}
llvm::Error WasmIncrementalExecutor::addModule(PartialTranslationUnit &PTU) {
std::string ErrorString;
const llvm::Target *Target = llvm::TargetRegistry::lookupTarget(
PTU.TheModule->getTargetTriple(), ErrorString);
if (!Target) {
return llvm::make_error<llvm::StringError>("Failed to create Wasm Target: ",
llvm::inconvertibleErrorCode());
}
llvm::TargetOptions TO = llvm::TargetOptions();
llvm::TargetMachine *TargetMachine = Target->createTargetMachine(
PTU.TheModule->getTargetTriple(), "", "", TO, llvm::Reloc::Model::PIC_);
PTU.TheModule->setDataLayout(TargetMachine->createDataLayout());
std::string OutputFileName = PTU.TheModule->getName().str() + ".wasm";
std::error_code Error;
llvm::raw_fd_ostream OutputFile(llvm::StringRef(OutputFileName), Error);
llvm::legacy::PassManager PM;
if (TargetMachine->addPassesToEmitFile(PM, OutputFile, nullptr,
llvm::CodeGenFileType::ObjectFile)) {
return llvm::make_error<llvm::StringError>(
"Wasm backend cannot produce object.", llvm::inconvertibleErrorCode());
}
if (!PM.run(*PTU.TheModule)) {
return llvm::make_error<llvm::StringError>("Failed to emit Wasm object.",
llvm::inconvertibleErrorCode());
}
OutputFile.close();
std::vector<const char *> LinkerArgs = {"wasm-ld",
"-pie",
"--import-memory",
"--no-entry",
"--export-all",
"--experimental-pic",
"--no-export-dynamic",
"--stack-first",
OutputFileName.c_str(),
"-o",
OutputFileName.c_str()};
int Result =
lld::wasm::link(LinkerArgs, llvm::outs(), llvm::errs(), false, false);
if (!Result)
return llvm::make_error<llvm::StringError>(
"Failed to link incremental module", llvm::inconvertibleErrorCode());
void *LoadedLibModule =
dlopen(OutputFileName.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (LoadedLibModule == nullptr) {
llvm::errs() << dlerror() << '\n';
return llvm::make_error<llvm::StringError>(
"Failed to load incremental module", llvm::inconvertibleErrorCode());
}
return llvm::Error::success();
}
llvm::Error WasmIncrementalExecutor::removeModule(PartialTranslationUnit &PTU) {
return llvm::make_error<llvm::StringError>("Not implemented yet",
llvm::inconvertibleErrorCode());
}
llvm::Error WasmIncrementalExecutor::runCtors() const {
// This seems to be automatically done when using dlopen()
return llvm::Error::success();
}
WasmIncrementalExecutor::~WasmIncrementalExecutor() = default;
} // namespace clang

View File

@@ -0,0 +1,37 @@
//===------------------ Wasm.h - Wasm Interpreter ---------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements interpreter support for code execution in WebAssembly.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_INTERPRETER_WASM_H
#define LLVM_CLANG_LIB_INTERPRETER_WASM_H
#ifndef __EMSCRIPTEN__
#error "This requires emscripten."
#endif // __EMSCRIPTEN__
#include "IncrementalExecutor.h"
namespace clang {
class WasmIncrementalExecutor : public IncrementalExecutor {
public:
WasmIncrementalExecutor(llvm::orc::ThreadSafeContext &TSC);
llvm::Error addModule(PartialTranslationUnit &PTU) override;
llvm::Error removeModule(PartialTranslationUnit &PTU) override;
llvm::Error runCtors() const override;
~WasmIncrementalExecutor() override;
};
} // namespace clang
#endif // LLVM_CLANG_LIB_INTERPRETER_WASM_H