Open redradist opened 2 years ago
Personally I think it's best to stick pretty close to the C API without adding too many extra layers of abstraction, but if you'd like I think this would probably make for a good example program?
Okay, I mean that using C-API it is hard to link big program consistent with some pieces 100 wasm files ...
wasi_app.load_app(app_name);
just simplifies it
@alexcrichton One additional question, could you explain of provide link to documentation for case where wasmtime
loads application with runtime dependencies, for example:
wasmtime some_app.wasm # It depends on library.wasm that located in the same directory
The question, how to provide to wasmtime
path to this dependency ?
Sorry I'm not sure what you mean by "pieces 100 wasm files" or what you mean by the path dependencies that wasmtime
loads. The CLI of the wasmtime
executable is quite primitive and it doesn't really work for any nontrivial wasms in terms of dependencies, the only thing that automatically works is WASI itself.
@alexcrichton
Lets see my example for WasiApp
:
class WasiApp {
public:
explicit WasiApp(std::vector<fs::path> linkdirs,
std::optional<fs::path> workdir = std::nullopt)
: link_dirs_{std::move(linkdirs)}
, workdir_{std::move(workdir)} {
linker_.define_wasi().unwrap();
}
void set_config(ws::WasiConfig&& config) {
store_.context().set_wasi(std::move(config)).unwrap();
}
void load_app(const std::string& app_name) {
reset_app();
auto workdir = workdir_.value_or(fs::current_path());
std::optional<fs::path> filename = find_file(workdir, app_name);
ws::Module module = compile(filename.value());
app_instance_ = link(app_name, module);
}
void run(const std::string& entry_point, const std::vector<ws::Val> ¶ms) {
ws::Func f = std::get<ws::Func>(*app_instance_.value().get(store_, entry_point));
f.call(store_, {}).unwrap();
}
private:
[[nodiscard]]
static std::string read_wat_file(const char* name) {
std::ifstream watFile;
watFile.open(name);
std::stringstream strStream;
strStream << watFile.rdbuf();
return strStream.str();
}
[[nodiscard]]
static std::vector<uint8_t> read_wasm_file(const char* name) {
std::ifstream bin_file(name, std::ios::binary);
return {(std::istreambuf_iterator<char>(bin_file)), (std::istreambuf_iterator<char>())};
}
[[nodiscard]]
static std::optional<fs::path> find_file(const fs::path& search_dir,
const std::string& app_name) {
std::optional<fs::path> filename;
for (auto &p : fs::directory_iterator(search_dir)) {
if (is_regular_file(p.path())) {
if (p.path().stem() == app_name &&
(p.path().extension() == ".wasm" ||
p.path().extension() == ".wat")) {
filename = p.path();
break;
}
}
}
return filename;
}
[[nodiscard]]
ws::Instance link(const std::string& name, const ws::Module& module) {
std::unordered_map<std::string, std::vector<std::string>> imports;
for (auto i : module.imports()) {
imports[std::string(i.module())].push_back(std::string(i.name()));
}
for (auto& [import_file, import_symbols] : imports) {
std::sort(import_symbols.begin(), import_symbols.end());
}
load_imports(imports);
ws::Instance linking_instance = linker_.instantiate(store_, module).unwrap();
linker_.define_instance(store_, name, linking_instance);
imported_modules_.push_back(module);
return linking_instance;
}
[[nodiscard]]
ws::Module compile(const fs::path& path) {
if (path.extension() == ".wat") {
auto linking_wat = read_wat_file(path.string().c_str());
return ws::Module::compile(engine_, linking_wat).unwrap();
} else {
auto linking_wasm = read_wasm_file(path.string().c_str());
return ws::Module::compile(engine_, linking_wasm).unwrap();
}
}
void load_imports(const std::unordered_map<std::string, std::vector<std::string>>& imports) {
for (const auto& [import_file, import_symbols] : imports) {
if (import_file.find("wasi_snapshot") != std::string::npos) {
continue;
}
bool is_found_wasm = false;
for (const auto& link_dirs : link_dirs_) {
std::optional<fs::path> link_filename = find_file(link_dirs, import_file);
if (link_filename) {
ws::Module linking_module = compile(link_filename.value());
std::vector<std::string> exports;
for (const auto& e : linking_module.exports()) {
exports.emplace_back(e.name());
}
std::sort(exports.begin(), exports.end());
if (exports != import_symbols) {
continue;
}
ws::Instance linking_instance = link(import_file, linking_module);
is_found_wasm = true;
break;
}
}
if (!is_found_wasm) {
fprintf(stderr, "error: Cannot link module %s\n", import_file.c_str());
std::abort();
}
}
}
void reset_app() {
app_instance_.reset();
imported_modules_.clear();
}
ws::Engine engine_;
ws::Store store_{engine_};
ws::Linker linker_{engine_};
std::vector<fs::path> link_dirs_;
std::optional<fs::path> workdir_;
std::optional<ws::Instance> app_instance_;
std::vector<ws::Module> imported_modules_;
};
Check how load_app
works !!
It works like runtime loader that loads properly all dependencies (shared libraries, but in case of wasi - wasm files)
It accomplish it by reading imports with name of wasm module that should be loaded and searching the same file in search path. It makes working with wasmtime much much simpler for complex program that consists of many wasm files
Ah so the wasmtime
CLI does not do what you rexample application is doing, which is using the first level of the two-level imports to load modules from the filesystem. In general while that seems like it should work it tends to not work for most applications today since it requires everything to be coordinated in terms of linear memory otherwise. I think this might be a neat example to add still but I don't think it would make sense to add it natively to the header.
@alexcrichton
Ah so the
wasmtime
CLI does not do what you rexample application is doing, which is using the first level of the two-level imports to load modules from the filesystem. In general while that seems like it should work it tends to not work for most applications today since it requires everything to be coordinated in terms of linear memory otherwise. I think this might be a neat example to add still but I don't think it would make sense to add it natively to the header.
What I did is actually elf loader for wasmtime programs that could find libraries in other predefined places. I personally think it should be part of wasmtime to simplify working of big program that consists of multiple wasm files. I think comment regarding memory alignment should be the responsibility of user that will provide search path for wasm parts. Do you think it would be more productive to start disscussion regarding adding this functionality in main wamtime repo ?
Yeah if you'd like to add this to Wasmtime itself it would be best to discuss over there.
I don't think this is really ready at this time though because wasm modules referring to each other by name and doing nontrivial things isn't really supported by toolchains today. That's sort of the "dynamic linking" story for wasm modules which currently is supported by Emscripten with JS glue but I don't believe anyone's worked on getting something working off the web.
@alexcrichton Lets see my example for
WasiApp
:class WasiApp { public: explicit WasiApp(std::vector<fs::path> linkdirs, std::optional<fs::path> workdir = std::nullopt) : link_dirs_{std::move(linkdirs)} , workdir_{std::move(workdir)} { linker_.define_wasi().unwrap(); } void set_config(ws::WasiConfig&& config) { store_.context().set_wasi(std::move(config)).unwrap(); } void load_app(const std::string& app_name) { reset_app(); auto workdir = workdir_.value_or(fs::current_path()); std::optional<fs::path> filename = find_file(workdir, app_name); ws::Module module = compile(filename.value()); app_instance_ = link(app_name, module); } void run(const std::string& entry_point, const std::vector<ws::Val> ¶ms) { ws::Func f = std::get<ws::Func>(*app_instance_.value().get(store_, entry_point)); f.call(store_, {}).unwrap(); } private: [[nodiscard]] static std::string read_wat_file(const char* name) { std::ifstream watFile; watFile.open(name); std::stringstream strStream; strStream << watFile.rdbuf(); return strStream.str(); } [[nodiscard]] static std::vector<uint8_t> read_wasm_file(const char* name) { std::ifstream bin_file(name, std::ios::binary); return {(std::istreambuf_iterator<char>(bin_file)), (std::istreambuf_iterator<char>())}; } [[nodiscard]] static std::optional<fs::path> find_file(const fs::path& search_dir, const std::string& app_name) { std::optional<fs::path> filename; for (auto &p : fs::directory_iterator(search_dir)) { if (is_regular_file(p.path())) { if (p.path().stem() == app_name && (p.path().extension() == ".wasm" || p.path().extension() == ".wat")) { filename = p.path(); break; } } } return filename; } [[nodiscard]] ws::Instance link(const std::string& name, const ws::Module& module) { std::unordered_map<std::string, std::vector<std::string>> imports; for (auto i : module.imports()) { imports[std::string(i.module())].push_back(std::string(i.name())); } for (auto& [import_file, import_symbols] : imports) { std::sort(import_symbols.begin(), import_symbols.end()); } load_imports(imports); ws::Instance linking_instance = linker_.instantiate(store_, module).unwrap(); linker_.define_instance(store_, name, linking_instance); imported_modules_.push_back(module); return linking_instance; } [[nodiscard]] ws::Module compile(const fs::path& path) { if (path.extension() == ".wat") { auto linking_wat = read_wat_file(path.string().c_str()); return ws::Module::compile(engine_, linking_wat).unwrap(); } else { auto linking_wasm = read_wasm_file(path.string().c_str()); return ws::Module::compile(engine_, linking_wasm).unwrap(); } } void load_imports(const std::unordered_map<std::string, std::vector<std::string>>& imports) { for (const auto& [import_file, import_symbols] : imports) { if (import_file.find("wasi_snapshot") != std::string::npos) { continue; } bool is_found_wasm = false; for (const auto& link_dirs : link_dirs_) { std::optional<fs::path> link_filename = find_file(link_dirs, import_file); if (link_filename) { ws::Module linking_module = compile(link_filename.value()); std::vector<std::string> exports; for (const auto& e : linking_module.exports()) { exports.emplace_back(e.name()); } std::sort(exports.begin(), exports.end()); if (exports != import_symbols) { continue; } ws::Instance linking_instance = link(import_file, linking_module); is_found_wasm = true; break; } } if (!is_found_wasm) { fprintf(stderr, "error: Cannot link module %s\n", import_file.c_str()); std::abort(); } } } void reset_app() { app_instance_.reset(); imported_modules_.clear(); } ws::Engine engine_; ws::Store store_{engine_}; ws::Linker linker_{engine_}; std::vector<fs::path> link_dirs_; std::optional<fs::path> workdir_; std::optional<ws::Instance> app_instance_; std::vector<ws::Module> imported_modules_; };
Check how
load_app
works !! It works like runtime loader that loads properly all dependencies (shared libraries, but in case of wasi - wasm files) It accomplish it by reading imports with name of wasm module that should be loaded and searching the same file in search path. It makes working with wasmtime much much simpler for complex program that consists of many wasm files
@alexcrichton If I will add this functionality, will you accept PR ?
Hi all,
Recently I wrote small wrapper that simplify loading and linking wasi application with all dependencies, see example:
The benefits is that this class encapsulate all complexity for properly loading application. For example, underhood
wasi_app.load_app(app_name)
loads all dependencies that are describe inimport
sectionsDo you think such functionality would be useful in this repo ? (I could create PR) Or do you think it should be provided as functionality from wasmtime ? Or not needed at all ?
Please, share your ideas ...