diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index 49c448bca63..60aff07fe46 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -75,4 +75,6 @@ scope: { buildPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.buildPhase; installPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.installPhase; }); + + wasmtime = pkgs.callPackage ./wasmtime.nix { }; } diff --git a/packaging/wasmtime.nix b/packaging/wasmtime.nix new file mode 100644 index 00000000000..88034f51d7c --- /dev/null +++ b/packaging/wasmtime.nix @@ -0,0 +1,101 @@ +{ + lib, + stdenv, + rust_1_89, + fetchFromGitHub, + buildPackages, + cmake, + installShellFiles, + nix-update-script, + enableShared ? !stdenv.hostPlatform.isStatic, + enableStatic ? stdenv.hostPlatform.isStatic, +}: +rust_1_89.packages.stable.rustPlatform.buildRustPackage (finalAttrs: { + pname = "wasmtime"; + version = "40.0.0"; + + src = fetchFromGitHub { + owner = "bytecodealliance"; + repo = "wasmtime"; + tag = "v${finalAttrs.version}"; + hash = "sha256-d5j+QSEWIVwVRMT/QGc6x3cVBTFZBpoxiBagmpLV1e8="; + fetchSubmodules = true; + }; + + # Disable cargo-auditable until https://github.com/rust-secure-code/cargo-auditable/issues/124 is solved. + auditable = false; + + cargoHash = "sha256-PIUJHkeGi8gao7n+SLzcxNYTl2KxKiwJZPW+sFYf0AY="; + cargoBuildFlags = [ + "--package" + "wasmtime-c-api" + "--no-default-features" + "--features cranelift,wasi,pooling-allocator,wat,demangle,gc-null" + ]; + + outputs = [ + "out" + "lib" + ]; + + nativeBuildInputs = [ + cmake + installShellFiles + ]; + + doCheck = + with stdenv.buildPlatform; + # SIMD tests are only executed on platforms that support all + # required processor features (e.g. SSE3, SSSE3 and SSE4.1 on x86_64): + # https://github.com/bytecodealliance/wasmtime/blob/v9.0.0/cranelift/codegen/src/isa/x64/mod.rs#L220 + (isx86_64 -> sse3Support && ssse3Support && sse4_1Support) + && + # The dependency `wasi-preview1-component-adapter` fails to build because of: + # error: linker `rust-lld` not found + !isAarch64; + + postInstall = + let + inherit (stdenv.hostPlatform.rust) cargoShortTarget; + in + '' + moveToOutput lib $lib + ${lib.optionalString (!enableShared) "rm -f $lib/lib/*.so{,.*}"} + ${lib.optionalString (!enableStatic) "rm -f $lib/lib/*.a"} + + # copy the build.rs generated c-api headers + # https://github.com/rust-lang/cargo/issues/9661 + mkdir -p $out + cp -r target/${cargoShortTarget}/release/build/wasmtime-c-api-impl-*/out/include $out/include + '' + + lib.optionalString stdenv.hostPlatform.isDarwin '' + install_name_tool -id \ + $lib/lib/libwasmtime.dylib \ + $lib/lib/libwasmtime.dylib + ''; + + passthru = { + updateScript = nix-update-script { + extraArgs = [ + "--version-regex" + "^v(\\d+\\.\\d+\\.\\d+)$" + ]; + }; + }; + + meta = { + description = "Standalone JIT-style runtime for WebAssembly, using Cranelift"; + homepage = "https://wasmtime.dev/"; + license = [ + lib.licenses.asl20 + lib.licenses.llvm-exception + ]; + mainProgram = "wasmtime"; + maintainers = with lib.maintainers; [ + ereslibre + nekowinston + ]; + platforms = lib.platforms.unix; + changelog = "https://github.com/bytecodealliance/wasmtime/blob/v${finalAttrs.version}/RELEASES.md"; + }; +}) diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build index 3724db9e2cb..0f0d7b28a7f 100644 --- a/src/libexpr/meson.build +++ b/src/libexpr/meson.build @@ -239,7 +239,7 @@ this_library = library( soversion : nix_soversion, dependencies : deps_public + deps_private + deps_other, include_directories : include_dirs, - link_args : linker_export_flags, + link_args : linker_export_flags + [ '-lwasmtime' ], link_whole : [ parser_library ], prelink : true, # For C++ static initializers install : true, diff --git a/src/libexpr/package.nix b/src/libexpr/package.nix index c82e56de8ff..bd2ca6b4b02 100644 --- a/src/libexpr/package.nix +++ b/src/libexpr/package.nix @@ -14,6 +14,7 @@ boehmgc, nlohmann_json, toml11, + wasmtime, # Configuration Options @@ -64,6 +65,7 @@ mkMesonLibrary (finalAttrs: { buildInputs = [ toml11 + wasmtime ]; propagatedBuildInputs = [ diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index a8b5e87c304..e40994f6cb3 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -165,7 +165,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS return res; } -static SourcePath realisePath( +SourcePath realisePath( EvalState & state, const PosIdx pos, Value & v, diff --git a/src/libexpr/primops/meson.build b/src/libexpr/primops/meson.build index b8abc6409af..ad48eb162de 100644 --- a/src/libexpr/primops/meson.build +++ b/src/libexpr/primops/meson.build @@ -9,4 +9,5 @@ sources += files( 'fetchMercurial.cc', 'fetchTree.cc', 'fromTOML.cc', + 'wasm.cc', ) diff --git a/src/libexpr/primops/wasm.cc b/src/libexpr/primops/wasm.cc new file mode 100644 index 00000000000..18a0120c4bc --- /dev/null +++ b/src/libexpr/primops/wasm.cc @@ -0,0 +1,441 @@ +#include "nix/expr/primops.hh" +#include "nix/expr/eval-inline.hh" + +#include + +using namespace wasmtime; + +namespace nix { + +// FIXME +SourcePath realisePath( + EvalState & state, + const PosIdx pos, + Value & v, + std::optional resolveSymlinks = SymlinkResolution::Full); + +using ValueId = uint32_t; + +template +T unwrap(Result && res) +{ + if (res) + return res.ok(); + throw Error(res.err().message()); +} + +static Engine & getEngine() +{ + static Engine engine = []() { + wasmtime::Config config; + config.pooling_allocation_strategy(PoolAllocationConfig()); + config.memory_init_cow(true); + return Engine(std::move(config)); + }(); + return engine; +} + +static std::span string2span(std::string_view s) +{ + return std::span((uint8_t *) s.data(), s.size()); +} + +static std::string_view span2string(std::span s) +{ + return std::string_view((char *) s.data(), s.size()); +} + +template +static std::span subspan(std::span s, size_t len) +{ + assert(s.size() >= len * sizeof(T)); + return std::span((T *) s.data(), len); +} + +// FIXME: move to wasmtime C++ wrapper. +class InstancePre +{ + WASMTIME_OWN_WRAPPER(InstancePre, wasmtime_instance_pre); + +public: + TrapResult instantiate(wasmtime::Store::Context cx) + { + wasmtime_instance_t instance; + wasm_trap_t * trap = nullptr; + auto * error = wasmtime_instance_pre_instantiate(ptr.get(), cx.capi(), &instance, &trap); + if (error != nullptr) { + return TrapError(wasmtime::Error(error)); + } + if (trap != nullptr) { + return TrapError(Trap(trap)); + } + return Instance(instance); + } +}; + +TrapResult instantiate_pre(Linker & linker, const Module & m) +{ + wasmtime_instance_pre_t * instance_pre; + auto * error = wasmtime_linker_instantiate_pre(linker.capi(), m.capi(), &instance_pre); + if (error != nullptr) { + return TrapError(wasmtime::Error(error)); + } + return InstancePre(instance_pre); +} + +void regFuns(Linker & linker); + +struct NixWasmInstancePre +{ + Engine & engine; + SourcePath wasmPath; + InstancePre instancePre; + + NixWasmInstancePre(SourcePath _wasmPath) + : engine(getEngine()) + , wasmPath(_wasmPath) + , instancePre(({ + Linker linker(engine); + regFuns(linker); + unwrap(instantiate_pre(linker, unwrap(Module::compile(engine, string2span(wasmPath.readFile()))))); + })) + { + } +}; + +struct NixWasmInstance +{ + EvalState & state; + ref pre; + wasmtime::Store wasmStore; + wasmtime::Store::Context wasmCtx; + Instance instance; + Memory memory_; + + ValueVector values; + std::exception_ptr ex; + + std::optional functionName; + + NixWasmInstance(EvalState & _state, ref _pre) + : state(_state) + , pre(_pre) + , wasmStore(pre->engine) + , wasmCtx(wasmStore) + , instance(unwrap(pre->instancePre.instantiate(wasmCtx))) + , memory_(std::get(*instance.get(wasmCtx, "memory"))) + { + wasmCtx.set_data(this); + } + + ValueId addValue(Value * v) + { + auto id = values.size(); + values.emplace_back(v); + return id; + } + + std::pair allocValue() + { + auto v = state.allocValue(); + auto id = addValue(v); + return {id, *v}; + } + + Func getFunction(std::string_view name) + { + auto ext = instance.get(wasmCtx, name); + if (!ext) + throw Error("WASM module '%s' does not export function '%s'", pre->wasmPath, name); + auto fun = std::get_if(&*ext); + if (!fun) + throw Error("export '%s' of WASM module '%s' is not a function", name, pre->wasmPath); + return *fun; + } + + std::vector runFunction(std::string_view name, const std::vector & args) + { + functionName = name; + return unwrap(getFunction(name).call(wasmCtx, args)); + } + + auto memory() + { + return memory_.data(wasmCtx); + } + + std::monostate panic(uint32_t ptr, uint32_t len) + { + throw Error("WASM panic: %s", Uncolored(span2string(memory().subspan(ptr, len)))); + } + + std::monostate warn(uint32_t ptr, uint32_t len) + { + nix::warn( + "'%s' function '%s': %s", + pre->wasmPath, + functionName.value_or(""), + span2string(memory().subspan(ptr, len))); + return {}; + } + + uint32_t get_type(ValueId valueId) + { + auto & value = *values.at(valueId); + state.forceValue(value, noPos); + auto t = value.type(); + return t == nInt ? 1 + : t == nFloat ? 2 + : t == nBool ? 3 + : t == nString ? 4 + : t == nPath ? 5 + : t == nNull ? 6 + : t == nAttrs ? 7 + : t == nList ? 8 + : t == nFunction ? 9 + : []() -> int { throw Error("unsupported type"); }(); + } + + ValueId make_int(int64_t n) + { + auto [valueId, value] = allocValue(); + value.mkInt(n); + return valueId; + } + + int64_t get_int(ValueId valueId) + { + return state.forceInt(*values.at(valueId), noPos, "while evaluating a value from WASM").value; + } + + ValueId make_float(double x) + { + auto [valueId, value] = allocValue(); + value.mkFloat(x); + return valueId; + } + + double get_float(ValueId valueId) + { + return state.forceFloat(*values.at(valueId), noPos, "while evaluating a value from WASM"); + } + + ValueId make_string(uint32_t ptr, uint32_t len) + { + auto [valueId, value] = allocValue(); + value.mkString(span2string(memory().subspan(ptr, len)), state.mem); + return valueId; + } + + uint32_t copy_string(ValueId valueId, uint32_t ptr, uint32_t maxLen) + { + auto s = state.forceString(*values.at(valueId), noPos, "while evaluating a value from WASM"); + if (s.size() <= maxLen) { + auto buf = memory().subspan(ptr, maxLen); + memcpy(buf.data(), s.data(), s.size()); + } + return s.size(); + } + + ValueId make_bool(int32_t b) + { + return addValue(state.getBool(b)); + } + + int32_t get_bool(ValueId valueId) + { + return state.forceBool(*values.at(valueId), noPos, "while evaluating a value from WASM"); + } + + ValueId make_null() + { + return addValue(&Value::vNull); + } + + ValueId make_list(uint32_t ptr, uint32_t len) + { + auto vs = subspan(memory().subspan(ptr), len); + + auto [valueId, value] = allocValue(); + + auto list = state.buildList(len); + for (const auto & [n, v] : enumerate(list)) + v = values.at(vs[n]); // FIXME: endianness + value.mkList(list); + + return valueId; + } + + uint32_t copy_list(ValueId valueId, uint32_t ptr, uint32_t maxLen) + { + auto & value = *values.at(valueId); + state.forceList(value, noPos, "while getting a list from WASM"); + + if (value.listSize() <= maxLen) { + auto out = subspan(memory().subspan(ptr), value.listSize()); + + for (const auto & [n, elem] : enumerate(value.listView())) + out[n] = addValue(elem); + } + + return value.listSize(); + } + + ValueId make_attrset(uint32_t ptr, uint32_t len) + { + auto mem = memory(); + + struct Attr + { + // FIXME: endianness + uint32_t attrNamePtr; + uint32_t attrNameLen; + ValueId value; + }; + + auto attrs = subspan(mem.subspan(ptr), len); + + auto [valueId, value] = allocValue(); + auto builder = state.buildBindings(len); + for (auto & attr : attrs) + builder.insert( + state.symbols.create(span2string(mem.subspan(attr.attrNamePtr, attr.attrNameLen))), + values.at(attr.value)); + value.mkAttrs(builder); + + return valueId; + } + + uint32_t copy_attrset(ValueId valueId, uint32_t ptr, uint32_t maxLen) + { + auto & value = *values.at(valueId); + state.forceAttrs(value, noPos, "while copying an attrset into WASM"); + + if (value.attrs()->size() <= maxLen) { + // FIXME: endianness. + struct Attr + { + ValueId value; + uint32_t nameLen; + }; + + auto buf = subspan(memory().subspan(ptr), maxLen); + + // FIXME: for determinism, we should return attributes in lexicographically sorted order. + for (const auto & [n, attr] : enumerate(*value.attrs())) { + buf[n].value = addValue(attr.value); + buf[n].nameLen = state.symbols[attr.name].size(); + } + } + + return value.attrs()->size(); + } + + std::monostate copy_attrname(ValueId valueId, uint32_t attrIdx, uint32_t ptr, uint32_t len) + { + auto & value = *values.at(valueId); + state.forceAttrs(value, noPos, "while copying an attr name into WASM"); + + auto & attrs = *value.attrs(); + + assert((size_t) attrIdx < attrs.size()); + + std::string_view name = state.symbols[attrs[attrIdx].name]; + + assert((size_t) len == name.size()); + + memcpy(memory().subspan(ptr, len).data(), name.data(), name.size()); + + return {}; + } + + ValueId call_function(ValueId funId, uint32_t ptr, uint32_t len) + { + auto & fun = *values.at(funId); + state.forceFunction(fun, noPos, "while calling a function from WASM"); + + ValueVector args; + for (auto argId : subspan(memory().subspan(ptr), len)) + args.push_back(values.at(argId)); + + auto [valueId, value] = allocValue(); + + state.callFunction(fun, args, value, noPos); + + return valueId; + } +}; + +template +static void regFun(Linker & linker, std::string_view name, R (NixWasmInstance::*f)(Args...)) +{ + unwrap(linker.func_wrap("env", name, [f](Caller caller, Args... args) -> Result { + try { + auto instance = std::any_cast(caller.context().get_data()); + return (*instance.*f)(args...); + } catch (Error & e) { + return Trap(e.what()); + } + })); +} + +void regFuns(Linker & linker) +{ + regFun(linker, "panic", &NixWasmInstance::panic); + regFun(linker, "warn", &NixWasmInstance::warn); + regFun(linker, "get_type", &NixWasmInstance::get_type); + regFun(linker, "make_int", &NixWasmInstance::make_int); + regFun(linker, "get_int", &NixWasmInstance::get_int); + regFun(linker, "make_float", &NixWasmInstance::make_float); + regFun(linker, "get_float", &NixWasmInstance::get_float); + regFun(linker, "make_string", &NixWasmInstance::make_string); + regFun(linker, "copy_string", &NixWasmInstance::copy_string); + regFun(linker, "make_bool", &NixWasmInstance::make_bool); + regFun(linker, "get_bool", &NixWasmInstance::get_bool); + regFun(linker, "make_null", &NixWasmInstance::make_null); + regFun(linker, "make_list", &NixWasmInstance::make_list); + regFun(linker, "copy_list", &NixWasmInstance::copy_list); + regFun(linker, "make_attrset", &NixWasmInstance::make_attrset); + regFun(linker, "copy_attrset", &NixWasmInstance::copy_attrset); + regFun(linker, "copy_attrname", &NixWasmInstance::copy_attrname); + regFun(linker, "call_function", &NixWasmInstance::call_function); +} + +void prim_wasm(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + auto wasmPath = realisePath(state, pos, *args[0]); + std::string functionName = + std::string(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument of `builtins.wasm`")); + + try { + // FIXME: make thread-safe. + // FIXME: make this a weak Boehm GC pointer so that it can be freed during GC. + static std::unordered_map> instancesPre; + + auto instancePre = instancesPre.find(wasmPath); + if (instancePre == instancesPre.end()) + instancePre = instancesPre.emplace(wasmPath, make_ref(wasmPath)).first; + + debug("calling wasm module"); + + NixWasmInstance instance{state, instancePre->second}; + + // FIXME: use the "start" function if present. + instance.runFunction("nix_wasm_init_v1", {}); + + v = *instance.values.at(instance.runFunction(functionName, {(int32_t) instance.addValue(args[2])}).at(0).i32()); + } catch (Error & e) { + e.addTrace(state.positions[pos], "while executing the WASM function '%s' from '%s'", functionName, wasmPath); + throw; + } +} + +static RegisterPrimOp primop_fromTOML( + {.name = "wasm", + .args = {"wasm", "entry", "arg"}, + .doc = R"( + Call a WASM function with the specified argument. + )", + .fun = prim_wasm}); + +} // namespace nix diff --git a/src/libstore/derivation-options.cc b/src/libstore/derivation-options.cc index 2ead0c444c9..5403db288ac 100644 --- a/src/libstore/derivation-options.cc +++ b/src/libstore/derivation-options.cc @@ -396,8 +396,8 @@ StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivat template bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivation & drv) const { - if (drv.platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv.platform) - && !drv.isBuiltin()) + if (drv.platform != settings.thisSystem.get() && drv.platform != "wasm32-wasip1" + && !settings.extraPlatforms.get().count(drv.platform) && !drv.isBuiltin()) return false; if (settings.maxBuildJobs.get() == 0 && !drv.isBuiltin()) diff --git a/src/libstore/meson.build b/src/libstore/meson.build index 0a0d2b8cac6..6556adc27d8 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -380,7 +380,7 @@ this_library = library( soversion : nix_soversion, dependencies : deps_public + deps_private + deps_other, include_directories : include_dirs, - link_args : linker_export_flags, + link_args : linker_export_flags + [ '-lwasmtime' ], prelink : true, # For C++ static initializers install : true, cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], diff --git a/src/libstore/package.nix b/src/libstore/package.nix index 44f43fdad36..5a27506e19b 100644 --- a/src/libstore/package.nix +++ b/src/libstore/package.nix @@ -13,6 +13,7 @@ libseccomp, nlohmann_json, sqlite, + wasmtime, busybox-sandbox-shell ? null, @@ -64,6 +65,7 @@ mkMesonLibrary (finalAttrs: { boost curl sqlite + wasmtime ] ++ lib.optional stdenv.hostPlatform.isLinux libseccomp ++ lib.optional withAWS aws-crt-cpp; diff --git a/src/libstore/unix/build/chroot-derivation-builder.cc b/src/libstore/unix/build/chroot-derivation-builder.cc index 354a604f535..a7bf94cf934 100644 --- a/src/libstore/unix/build/chroot-derivation-builder.cc +++ b/src/libstore/unix/build/chroot-derivation-builder.cc @@ -153,7 +153,7 @@ struct ChrootDerivationBuilder : virtual DerivationBuilderImpl return Strings({store.printStorePath(drvPath), chrootRootDir}); } - Path realPathInSandbox(const Path & p) override + Path realPathInHost(const Path & p) override { // FIXME: why the needsHashRewrite() conditional? return !needsHashRewrite() ? chrootRootDir + p : store.toRealPath(p); diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc index 5841b414680..96f502a798a 100644 --- a/src/libstore/unix/build/derivation-builder.cc +++ b/src/libstore/unix/build/derivation-builder.cc @@ -291,7 +291,7 @@ class DerivationBuilderImpl : public DerivationBuilder, public DerivationBuilder return Strings({store.printStorePath(drvPath)}); } - virtual Path realPathInSandbox(const Path & p) + virtual Path realPathInHost(const Path & p) { return store.toRealPath(p); } @@ -1450,7 +1450,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs() for (auto & [outputName, _] : drv.outputs) { auto scratchOutput = get(scratchOutputs, outputName); assert(scratchOutput); - auto actualPath = realPathInSandbox(store.printStorePath(*scratchOutput)); + auto actualPath = realPathInHost(store.printStorePath(*scratchOutput)); outputsToSort.insert(outputName); @@ -1570,7 +1570,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs() auto output = get(drv.outputs, outputName); auto scratchPath = get(scratchOutputs, outputName); assert(output && scratchPath); - auto actualPath = realPathInSandbox(store.printStorePath(*scratchPath)); + auto actualPath = realPathInHost(store.printStorePath(*scratchPath)); auto finish = [&](StorePath finalStorePath) { /* Store the final path */ @@ -1997,6 +1997,7 @@ StorePath DerivationBuilderImpl::makeFallbackPath(const StorePath & path) #include "linux-derivation-builder.cc" #include "darwin-derivation-builder.cc" #include "external-derivation-builder.cc" +#include "wasi-derivation-builder.cc" namespace nix { @@ -2028,6 +2029,9 @@ std::unique_ptr makeDerivationBuilder( useSandbox = params.drv.type().isSandboxed() && !params.drvOptions.noChroot; } + if (params.drv.platform == "wasm32-wasip1") + return std::make_unique(store, std::move(miscMethods), std::move(params)); + if (store.storeDir != store.config->realStoreDir.get()) { #ifdef __linux__ useSandbox = true; diff --git a/src/libstore/unix/build/wasi-derivation-builder.cc b/src/libstore/unix/build/wasi-derivation-builder.cc new file mode 100644 index 00000000000..2c9a4ec813c --- /dev/null +++ b/src/libstore/unix/build/wasi-derivation-builder.cc @@ -0,0 +1,75 @@ +#include + +namespace nix { + +// FIXME: cut&paste +template +T unwrap(wasmtime::Result && res) +{ + if (res) + return res.ok(); + throw Error(res.err().message()); +} + +// FIXME: cut&paste +static std::span string2span(std::string_view s) +{ + return std::span((uint8_t *) s.data(), s.size()); +} + +struct WasiDerivationBuilder : DerivationBuilderImpl +{ + WasiDerivationBuilder( + LocalStore & store, std::unique_ptr miscMethods, DerivationBuilderParams params) + : DerivationBuilderImpl(store, std::move(miscMethods), std::move(params)) + { + // experimentalFeatureSettings.require(Xp::WasiBuilders); + } + + void execBuilder(const Strings & args, const Strings & envStrs) override + { + using namespace wasmtime; + + Engine engine; + Linker linker(engine); + unwrap(linker.define_wasi()); + + WasiConfig wasiConfig; + wasiConfig.inherit_stdin(); + wasiConfig.inherit_stdout(); + wasiConfig.inherit_stderr(); + wasiConfig.argv(std::vector(args.begin(), args.end())); + { + std::vector> env2; + for (auto & [k, v] : env) + env2.emplace_back(k, rewriteStrings(v, inputRewrites)); + wasiConfig.env(env2); + } + if (!wasiConfig.preopen_dir( + store.config->realStoreDir.get(), + store.storeDir, + WASMTIME_WASI_DIR_PERMS_READ | WASMTIME_WASI_DIR_PERMS_WRITE, + WASMTIME_WASI_FILE_PERMS_READ | WASMTIME_WASI_FILE_PERMS_WRITE)) + throw Error("cannot add store directory to WASI config"); + // FIXME: add temp dir + + auto module = unwrap(Module::compile(engine, string2span(readFile(realPathInHost(drv.builder))))); + wasmtime::Store wasmStore(engine); + unwrap(wasmStore.context().set_wasi(std::move(wasiConfig))); + auto instance = unwrap(linker.instantiate(wasmStore, module)); + + auto startName = "_start"; + auto ext = instance.get(wasmStore, startName); + if (!ext) + throw Error("WASM module '%s' does not export function '%s'", drv.builder, startName); + auto fun = std::get_if(&*ext); + if (!fun) + throw Error("export '%s' of WASM module '%s' is not a function", startName, drv.builder); + + unwrap(fun->call(wasmStore.context(), {})); + + _exit(0); + } +}; + +} // namespace nix