mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-09 06:08:29 +00:00
Fix make lint
This commit is contained in:
@@ -326,7 +326,7 @@ fn test_run() {
|
|||||||
if !config.wasmer_dir.is_empty() {
|
if !config.wasmer_dir.is_empty() {
|
||||||
command.arg("-L");
|
command.arg("-L");
|
||||||
command.arg(&format!("{}/lib/", config.wasmer_dir));
|
command.arg(&format!("{}/lib/", config.wasmer_dir));
|
||||||
command.arg(&"-lwasmer".to_string());
|
command.arg("-lwasmer");
|
||||||
command.arg(&format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
|
command.arg(&format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
|
||||||
}
|
}
|
||||||
command.arg("-o");
|
command.arg("-o");
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ fn test_ok() {
|
|||||||
let libwasmer_so_path = format!("{}/lib/libwasmer.so", config.wasmer_dir);
|
let libwasmer_so_path = format!("{}/lib/libwasmer.so", config.wasmer_dir);
|
||||||
let exe_dir = format!("{manifest_dir}/../wasm-c-api/example");
|
let exe_dir = format!("{manifest_dir}/../wasm-c-api/example");
|
||||||
let path = std::env::var("PATH").unwrap_or_default();
|
let path = std::env::var("PATH").unwrap_or_default();
|
||||||
let newpath = format!("{};{path}", wasmer_dll_dir.to_string().replace('/', "\\"));
|
let newpath = format!("{};{path}", wasmer_dll_dir.replace('/', "\\"));
|
||||||
|
|
||||||
if target.contains("msvc") {
|
if target.contains("msvc") {
|
||||||
for test in CAPI_BASE_TESTS.iter() {
|
for test in CAPI_BASE_TESTS.iter() {
|
||||||
@@ -347,7 +347,7 @@ fn test_ok() {
|
|||||||
if !config.wasmer_dir.is_empty() {
|
if !config.wasmer_dir.is_empty() {
|
||||||
command.arg("-L");
|
command.arg("-L");
|
||||||
command.arg(&format!("{}/lib/", config.wasmer_dir));
|
command.arg(&format!("{}/lib/", config.wasmer_dir));
|
||||||
command.arg(&"-lwasmer".to_string());
|
command.arg("-lwasmer");
|
||||||
command.arg(&format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
|
command.arg(&format!("-Wl,-rpath,{}/lib/", config.wasmer_dir));
|
||||||
}
|
}
|
||||||
command.arg("-o");
|
command.arg("-o");
|
||||||
|
|||||||
@@ -5,71 +5,75 @@ use wasmer_types::ModuleInfo;
|
|||||||
use wasmer_types::{Symbol, SymbolRegistry};
|
use wasmer_types::{Symbol, SymbolRegistry};
|
||||||
|
|
||||||
/// Helper functions to simplify the usage of the static artifact.
|
/// Helper functions to simplify the usage of the static artifact.
|
||||||
const HELPER_FUNCTIONS: &str = r#"
|
fn gen_helper_functions(atom_name: &str, module_name: &str) -> String {
|
||||||
wasm_byte_vec_t generate_serialized_data() {
|
format!("
|
||||||
|
wasm_byte_vec_t generate_serialized_data_{atom_name}() {{
|
||||||
// We need to pass all the bytes as one big buffer so we have to do all this logic to memcpy
|
// We need to pass all the bytes as one big buffer so we have to do all this logic to memcpy
|
||||||
// the various pieces together from the generated header file.
|
// the various pieces together from the generated header file.
|
||||||
//
|
//
|
||||||
// We should provide a `deseralize_vectored` function to avoid requiring this extra work.
|
// We should provide a `deseralize_vectored` function to avoid requiring this extra work.
|
||||||
|
|
||||||
char* byte_ptr = (char*)&WASMER_METADATA[0];
|
char* byte_ptr = (char*)&{module_name}[0];
|
||||||
|
|
||||||
size_t num_function_pointers
|
size_t num_function_pointers
|
||||||
= sizeof(function_pointers) / sizeof(void*);
|
= sizeof(function_pointers_{atom_name}) / sizeof(void*);
|
||||||
size_t num_function_trampolines
|
size_t num_function_trampolines
|
||||||
= sizeof(function_trampolines) / sizeof(void*);
|
= sizeof(function_trampolines_{atom_name}) / sizeof(void*);
|
||||||
size_t num_dynamic_function_trampoline_pointers
|
size_t num_dynamic_function_trampoline_pointers
|
||||||
= sizeof(dynamic_function_trampoline_pointers) / sizeof(void*);
|
= sizeof(dynamic_function_trampoline_pointers_{atom_name}) / sizeof(void*);
|
||||||
|
|
||||||
|
|
||||||
size_t buffer_size = module_bytes_len
|
size_t buffer_size = module_bytes_len_{atom_name}
|
||||||
+ sizeof(size_t) + sizeof(function_pointers)
|
+ sizeof(size_t) + sizeof(function_pointers_{atom_name})
|
||||||
+ sizeof(size_t) + sizeof(function_trampolines)
|
+ sizeof(size_t) + sizeof(function_trampolines_{atom_name})
|
||||||
+ sizeof(size_t) + sizeof(dynamic_function_trampoline_pointers);
|
+ sizeof(size_t) + sizeof(dynamic_function_trampoline_pointers_{atom_name});
|
||||||
|
|
||||||
char* memory_buffer = (char*) malloc(buffer_size);
|
char* memory_buffer = (char*) malloc(buffer_size);
|
||||||
size_t current_offset = 0;
|
size_t current_offset = 0;
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, byte_ptr, module_bytes_len);
|
memcpy(memory_buffer + current_offset, byte_ptr, module_bytes_len_{atom_name});
|
||||||
current_offset += module_bytes_len;
|
current_offset += module_bytes_len_{atom_name};
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, (void*)&num_function_pointers, sizeof(size_t));
|
memcpy(memory_buffer + current_offset, (void*)&num_function_pointers, sizeof(size_t));
|
||||||
current_offset += sizeof(size_t);
|
current_offset += sizeof(size_t);
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, (void*)&function_pointers[0], sizeof(function_pointers));
|
memcpy(memory_buffer + current_offset, (void*)&function_pointers_{atom_name}[0], sizeof(function_pointers_{atom_name}));
|
||||||
current_offset += sizeof(function_pointers);
|
current_offset += sizeof(function_pointers_{atom_name});
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, (void*)&num_function_trampolines, sizeof(size_t));
|
memcpy(memory_buffer + current_offset, (void*)&num_function_trampolines, sizeof(size_t));
|
||||||
current_offset += sizeof(size_t);
|
current_offset += sizeof(size_t);
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, (void*)&function_trampolines[0], sizeof(function_trampolines));
|
memcpy(memory_buffer + current_offset, (void*)&function_trampolines_{atom_name}[0], sizeof(function_trampolines_{atom_name}));
|
||||||
current_offset += sizeof(function_trampolines);
|
current_offset += sizeof(function_trampolines_{atom_name});
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, (void*)&num_dynamic_function_trampoline_pointers, sizeof(size_t));
|
memcpy(memory_buffer + current_offset, (void*)&num_dynamic_function_trampoline_pointers, sizeof(size_t));
|
||||||
current_offset += sizeof(size_t);
|
current_offset += sizeof(size_t);
|
||||||
|
|
||||||
memcpy(memory_buffer + current_offset, (void*)&dynamic_function_trampoline_pointers[0], sizeof(dynamic_function_trampoline_pointers));
|
memcpy(memory_buffer + current_offset, (void*)&dynamic_function_trampoline_pointers_{atom_name}[0], sizeof(dynamic_function_trampoline_pointers_{atom_name}));
|
||||||
current_offset += sizeof(dynamic_function_trampoline_pointers);
|
current_offset += sizeof(dynamic_function_trampoline_pointers_{atom_name});
|
||||||
|
|
||||||
wasm_byte_vec_t module_byte_vec = {
|
wasm_byte_vec_t module_byte_vec = {{
|
||||||
.size = buffer_size,
|
.size = buffer_size,
|
||||||
.data = memory_buffer,
|
.data = memory_buffer,
|
||||||
};
|
}};
|
||||||
return module_byte_vec;
|
return module_byte_vec;
|
||||||
}
|
}}
|
||||||
|
|
||||||
wasm_module_t* wasmer_object_module_new(wasm_store_t* store, const char* wasm_name) {
|
wasm_module_t* wasmer_object_module_new_{atom_name}(wasm_store_t* store, const char* wasm_name) {{
|
||||||
// wasm_name intentionally unused for now: will be used in the future.
|
// wasm_name intentionally unused for now: will be used in the future.
|
||||||
wasm_byte_vec_t module_byte_vec = generate_serialized_data();
|
wasm_byte_vec_t module_byte_vec = generate_serialized_data_{atom_name}();
|
||||||
wasm_module_t* module = wasm_module_deserialize(store, &module_byte_vec);
|
wasm_module_t* module = wasm_module_deserialize(store, &module_byte_vec);
|
||||||
free(module_byte_vec.data);
|
free(module_byte_vec.data);
|
||||||
|
|
||||||
return module;
|
return module;
|
||||||
|
}}
|
||||||
|
")
|
||||||
}
|
}
|
||||||
"#;
|
|
||||||
|
|
||||||
/// Generate the header file that goes with the generated object file.
|
/// Generate the header file that goes with the generated object file.
|
||||||
pub fn generate_header_file(
|
pub fn generate_header_file(
|
||||||
|
atom_name: &str,
|
||||||
|
module_name: &str,
|
||||||
module_info: &ModuleInfo,
|
module_info: &ModuleInfo,
|
||||||
symbol_registry: &dyn SymbolRegistry,
|
symbol_registry: &dyn SymbolRegistry,
|
||||||
metadata_length: usize,
|
metadata_length: usize,
|
||||||
@@ -83,7 +87,7 @@ pub fn generate_header_file(
|
|||||||
value: "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n".to_string(),
|
value: "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n".to_string(),
|
||||||
},
|
},
|
||||||
CStatement::Declaration {
|
CStatement::Declaration {
|
||||||
name: "module_bytes_len".to_string(),
|
name: format!("module_bytes_len_{atom_name}"),
|
||||||
is_extern: false,
|
is_extern: false,
|
||||||
is_const: true,
|
is_const: true,
|
||||||
ctype: CType::U32,
|
ctype: CType::U32,
|
||||||
@@ -92,10 +96,7 @@ pub fn generate_header_file(
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
CStatement::Declaration {
|
CStatement::Declaration {
|
||||||
name: match module_info.name.as_deref() {
|
name: module_name.to_string(),
|
||||||
Some(s) => format!("WASMER_METADATA_{}", s.to_uppercase()),
|
|
||||||
None => "WASMER_METADATA".to_string(),
|
|
||||||
},
|
|
||||||
is_extern: true,
|
is_extern: true,
|
||||||
is_const: true,
|
is_const: true,
|
||||||
ctype: CType::Array {
|
ctype: CType::Array {
|
||||||
@@ -157,7 +158,7 @@ pub fn generate_header_file(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
c_statements.push(CStatement::Declaration {
|
c_statements.push(CStatement::Declaration {
|
||||||
name: "function_pointers".to_string(),
|
name: format!("function_pointers_{atom_name}"),
|
||||||
is_extern: false,
|
is_extern: false,
|
||||||
is_const: true,
|
is_const: true,
|
||||||
ctype: CType::Array {
|
ctype: CType::Array {
|
||||||
@@ -213,7 +214,7 @@ pub fn generate_header_file(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
c_statements.push(CStatement::Declaration {
|
c_statements.push(CStatement::Declaration {
|
||||||
name: "function_trampolines".to_string(),
|
name: format!("function_trampolines_{atom_name}"),
|
||||||
is_extern: false,
|
is_extern: false,
|
||||||
is_const: true,
|
is_const: true,
|
||||||
ctype: CType::Array {
|
ctype: CType::Array {
|
||||||
@@ -277,7 +278,7 @@ pub fn generate_header_file(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
c_statements.push(CStatement::Declaration {
|
c_statements.push(CStatement::Declaration {
|
||||||
name: "dynamic_function_trampoline_pointers".to_string(),
|
name: format!("dynamic_function_trampoline_pointers_{atom_name}"),
|
||||||
is_extern: false,
|
is_extern: false,
|
||||||
is_const: true,
|
is_const: true,
|
||||||
ctype: CType::Array {
|
ctype: CType::Array {
|
||||||
@@ -290,7 +291,7 @@ pub fn generate_header_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
c_statements.push(CStatement::LiteralConstant {
|
c_statements.push(CStatement::LiteralConstant {
|
||||||
value: HELPER_FUNCTIONS.to_string(),
|
value: gen_helper_functions(atom_name, module_name),
|
||||||
});
|
});
|
||||||
|
|
||||||
c_statements.push(CStatement::LiteralConstant {
|
c_statements.push(CStatement::LiteralConstant {
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ use anyhow::{Context, Result};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt::Write as _;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use wasmer::*;
|
use wasmer::*;
|
||||||
use wasmer_object::{emit_serialized, get_object_for_target};
|
use wasmer_object::{emit_serialized, get_object_for_target};
|
||||||
use wasmer_types::compilation::target;
|
|
||||||
use webc::{ParseOptions, WebCMmap};
|
use webc::{ParseOptions, WebCMmap};
|
||||||
|
|
||||||
/// The `prefixer` returns the a String to prefix each of the
|
/// The `prefixer` returns the a String to prefix each of the
|
||||||
@@ -130,7 +129,7 @@ pub struct Volume {
|
|||||||
impl CreateExe {
|
impl CreateExe {
|
||||||
/// Runs logic for the `compile` subcommand
|
/// Runs logic for the `compile` subcommand
|
||||||
pub fn execute(&self) -> Result<()> {
|
pub fn execute(&self) -> Result<()> {
|
||||||
let target_triple = self.target_triple.clone().unwrap_or_else(|| Triple::host());
|
let target_triple = self.target_triple.clone().unwrap_or_else(Triple::host);
|
||||||
let mut cc = CrossCompile {
|
let mut cc = CrossCompile {
|
||||||
library_path: self.cross_compile.library_path.clone(),
|
library_path: self.cross_compile.library_path.clone(),
|
||||||
zig_binary_path: self.cross_compile.zig_binary_path.clone(),
|
zig_binary_path: self.cross_compile.zig_binary_path.clone(),
|
||||||
@@ -146,7 +145,7 @@ impl CreateExe {
|
|||||||
|
|
||||||
if input_path.is_dir() {
|
if input_path.is_dir() {
|
||||||
// assumes that the input directory has been created with create-obj
|
// assumes that the input directory has been created with create-obj
|
||||||
link_exe_from_dir(&input_path, output_path, &cross_compilation)?;
|
link_exe_from_dir(&input_path, output_path, &cross_compilation, true)?;
|
||||||
} else if let Ok(pirita) = WebCMmap::parse(input_path.clone(), &ParseOptions::default()) {
|
} else if let Ok(pirita) = WebCMmap::parse(input_path.clone(), &ParseOptions::default()) {
|
||||||
// pirita file
|
// pirita file
|
||||||
let temp = tempdir::TempDir::new("pirita-compile")?;
|
let temp = tempdir::TempDir::new("pirita-compile")?;
|
||||||
@@ -163,7 +162,12 @@ impl CreateExe {
|
|||||||
&cross_compilation.target,
|
&cross_compilation.target,
|
||||||
self.object_format.unwrap_or_default(),
|
self.object_format.unwrap_or_default(),
|
||||||
)?;
|
)?;
|
||||||
link_exe_from_dir(&tempdir, output_path, &cross_compilation)?;
|
link_exe_from_dir(
|
||||||
|
&tempdir,
|
||||||
|
output_path,
|
||||||
|
&cross_compilation,
|
||||||
|
self.debug_dir.is_some(),
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
// wasm file
|
// wasm file
|
||||||
let temp = tempdir::TempDir::new("pirita-compile")?;
|
let temp = tempdir::TempDir::new("pirita-compile")?;
|
||||||
@@ -180,7 +184,12 @@ impl CreateExe {
|
|||||||
&self.cpu_features,
|
&self.cpu_features,
|
||||||
self.object_format.unwrap_or_default(),
|
self.object_format.unwrap_or_default(),
|
||||||
)?;
|
)?;
|
||||||
link_exe_from_dir(&tempdir, output_path, &cross_compilation)?;
|
link_exe_from_dir(
|
||||||
|
&tempdir,
|
||||||
|
output_path,
|
||||||
|
&cross_compilation,
|
||||||
|
self.debug_dir.is_some(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.target_triple.is_some() {
|
if self.target_triple.is_some() {
|
||||||
@@ -213,7 +222,7 @@ pub(super) fn compile_pirita_into_directory(
|
|||||||
.map_err(|e| anyhow::anyhow!("cannot create / dir in {}: {e}", target_dir.display()))?;
|
.map_err(|e| anyhow::anyhow!("cannot create / dir in {}: {e}", target_dir.display()))?;
|
||||||
|
|
||||||
let target_dir = target_dir.canonicalize()?;
|
let target_dir = target_dir.canonicalize()?;
|
||||||
let target = &utils::target_triple_to_target(&triple, cpu_features);
|
let target = &utils::target_triple_to_target(triple, cpu_features);
|
||||||
|
|
||||||
std::fs::create_dir_all(target_dir.join("volumes")).map_err(|e| {
|
std::fs::create_dir_all(target_dir.join("volumes")).map_err(|e| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
@@ -225,7 +234,7 @@ pub(super) fn compile_pirita_into_directory(
|
|||||||
let volume_bytes = pirita.get_volumes_as_fileblock();
|
let volume_bytes = pirita.get_volumes_as_fileblock();
|
||||||
let volume_name = "VOLUMES";
|
let volume_name = "VOLUMES";
|
||||||
let volume_path = target_dir.join("volumes").join("volume.o");
|
let volume_path = target_dir.join("volumes").join("volume.o");
|
||||||
write_volume_obj(&volume_bytes, &volume_name, &volume_path, &target)?;
|
write_volume_obj(&volume_bytes, volume_name, &volume_path, target)?;
|
||||||
let volume_path = volume_path.canonicalize()?;
|
let volume_path = volume_path.canonicalize()?;
|
||||||
let volume_path = pathdiff::diff_paths(&volume_path, &target_dir).unwrap();
|
let volume_path = pathdiff::diff_paths(&volume_path, &target_dir).unwrap();
|
||||||
|
|
||||||
@@ -237,8 +246,10 @@ pub(super) fn compile_pirita_into_directory(
|
|||||||
let mut target_paths = Vec::new();
|
let mut target_paths = Vec::new();
|
||||||
|
|
||||||
for (atom_name, atom_bytes) in pirita.get_all_atoms() {
|
for (atom_name, atom_bytes) in pirita.get_all_atoms() {
|
||||||
atoms_from_file.push((atom_name.clone(), atom_bytes.to_vec()));
|
atoms_from_file.push((utils::normalize_atom_name(&atom_name), atom_bytes.to_vec()));
|
||||||
let atom_path = target_dir.join("atoms").join(format!("{atom_name}.o"));
|
let atom_path = target_dir
|
||||||
|
.join("atoms")
|
||||||
|
.join(format!("{}.o", utils::normalize_atom_name(&atom_name)));
|
||||||
let header_path = match object_format {
|
let header_path = match object_format {
|
||||||
ObjectFormat::Symbols => {
|
ObjectFormat::Symbols => {
|
||||||
std::fs::create_dir_all(target_dir.join("include")).map_err(|e| {
|
std::fs::create_dir_all(target_dir.join("include")).map_err(|e| {
|
||||||
@@ -248,15 +259,19 @@ pub(super) fn compile_pirita_into_directory(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Some(
|
Some(target_dir.join("include").join(format!(
|
||||||
target_dir
|
"static_defs_{}.h",
|
||||||
.join("include")
|
utils::normalize_atom_name(&atom_name)
|
||||||
.join(format!("static_defs_{atom_name}.h")),
|
)))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ObjectFormat::Serialized => None,
|
ObjectFormat::Serialized => None,
|
||||||
};
|
};
|
||||||
target_paths.push((atom_name, atom_path, header_path));
|
target_paths.push((
|
||||||
|
atom_name.clone(),
|
||||||
|
utils::normalize_atom_name(&atom_name),
|
||||||
|
atom_path,
|
||||||
|
header_path,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
conpile_atoms(
|
conpile_atoms(
|
||||||
@@ -270,20 +285,21 @@ pub(super) fn compile_pirita_into_directory(
|
|||||||
|
|
||||||
// target_dir
|
// target_dir
|
||||||
let mut atoms = Vec::new();
|
let mut atoms = Vec::new();
|
||||||
for (atom_name, atom_path, opt_header_path) in target_paths {
|
for (command_name, atom_name, a, opt_header_path) in target_paths {
|
||||||
if let Ok(atom_path) = atom_path.canonicalize() {
|
let mut atom_path = a;
|
||||||
let header_path = opt_header_path.and_then(|p| p.canonicalize().ok());
|
let mut header_path = opt_header_path;
|
||||||
let atom_path =
|
if let Ok(a) = atom_path.canonicalize() {
|
||||||
pathdiff::diff_paths(&atom_path, &target_dir).unwrap_or_else(|| atom_path.clone());
|
let opt_header_path = header_path.and_then(|p| p.canonicalize().ok());
|
||||||
let header_path = header_path.and_then(|h| pathdiff::diff_paths(&h, &target_dir));
|
atom_path = pathdiff::diff_paths(&a, &target_dir).unwrap_or_else(|| a.clone());
|
||||||
atoms.push(CommandEntrypoint {
|
header_path = opt_header_path.and_then(|h| pathdiff::diff_paths(&h, &target_dir));
|
||||||
// TODO: improve, "--command pip" should be able to invoke atom "python" with args "-m pip"
|
|
||||||
command: atom_name.clone(),
|
|
||||||
atom: atom_name.clone(),
|
|
||||||
path: atom_path,
|
|
||||||
header: header_path,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
atoms.push(CommandEntrypoint {
|
||||||
|
// TODO: improve, "--command pip" should be able to invoke atom "python" with args "-m pip"
|
||||||
|
command: command_name,
|
||||||
|
atom: atom_name,
|
||||||
|
path: atom_path,
|
||||||
|
header: header_path,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let entrypoint = Entrypoint {
|
let entrypoint = Entrypoint {
|
||||||
@@ -324,6 +340,7 @@ fn conpile_atoms(
|
|||||||
for (a, data) in atoms {
|
for (a, data) in atoms {
|
||||||
let (store, _) = compiler.get_store_for_target(target.clone())?;
|
let (store, _) = compiler.get_store_for_target(target.clone())?;
|
||||||
let atom_name = utils::normalize_atom_name(a);
|
let atom_name = utils::normalize_atom_name(a);
|
||||||
|
let atom_name_uppercase = atom_name.to_uppercase();
|
||||||
let output_object_path = output_dir.join(format!("{atom_name}.o"));
|
let output_object_path = output_dir.join(format!("{atom_name}.o"));
|
||||||
let module_name = format!(
|
let module_name = format!(
|
||||||
"WASMER_MODULE_{}",
|
"WASMER_MODULE_{}",
|
||||||
@@ -341,19 +358,20 @@ fn conpile_atoms(
|
|||||||
let prefixer: Option<PrefixerFn> = Some(Box::new(move |_| {
|
let prefixer: Option<PrefixerFn> = Some(Box::new(move |_| {
|
||||||
utils::normalize_atom_name(&atom_name_copy)
|
utils::normalize_atom_name(&atom_name_copy)
|
||||||
}));
|
}));
|
||||||
println!("generating object file {module_name}");
|
|
||||||
let (module_info, obj, metadata_length, symbol_registry) =
|
let (module_info, obj, metadata_length, symbol_registry) =
|
||||||
Artifact::generate_object(
|
Artifact::generate_object(
|
||||||
compiler,
|
compiler,
|
||||||
&data,
|
data,
|
||||||
&module_name,
|
&module_name,
|
||||||
prefixer,
|
prefixer,
|
||||||
&target,
|
target,
|
||||||
tunables,
|
tunables,
|
||||||
features,
|
features,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
|
let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
|
||||||
|
&atom_name,
|
||||||
|
&format!("WASMER_MODULE_{atom_name_uppercase}"),
|
||||||
&module_info,
|
&module_info,
|
||||||
&*symbol_registry,
|
&*symbol_registry,
|
||||||
metadata_length,
|
metadata_length,
|
||||||
@@ -370,8 +388,7 @@ fn conpile_atoms(
|
|||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
}
|
}
|
||||||
ObjectFormat::Serialized => {
|
ObjectFormat::Serialized => {
|
||||||
let module =
|
let module = Module::from_binary(&store, data).context("failed to compile Wasm")?;
|
||||||
Module::from_binary(&store, &data).context("failed to compile Wasm")?;
|
|
||||||
let bytes = module.serialize()?;
|
let bytes = module.serialize()?;
|
||||||
let mut obj = get_object_for_target(target.triple())?;
|
let mut obj = get_object_for_target(target.triple())?;
|
||||||
emit_serialized(&mut obj, &bytes, target.triple(), &module_name)?;
|
emit_serialized(&mut obj, &bytes, target.triple(), &module_name)?;
|
||||||
@@ -468,7 +485,7 @@ pub(super) fn prepare_directory_from_single_wasm_file(
|
|||||||
object_format: ObjectFormat,
|
object_format: ObjectFormat,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let bytes = std::fs::read(wasm_file)?;
|
let bytes = std::fs::read(wasm_file)?;
|
||||||
let target = &utils::target_triple_to_target(&triple, cpu_features);
|
let target = &utils::target_triple_to_target(triple, cpu_features);
|
||||||
|
|
||||||
std::fs::create_dir_all(target_dir)
|
std::fs::create_dir_all(target_dir)
|
||||||
.map_err(|e| anyhow::anyhow!("cannot create / dir in {}: {e}", target_dir.display()))?;
|
.map_err(|e| anyhow::anyhow!("cannot create / dir in {}: {e}", target_dir.display()))?;
|
||||||
@@ -557,6 +574,7 @@ fn link_exe_from_dir(
|
|||||||
directory: &Path,
|
directory: &Path,
|
||||||
output_path: PathBuf,
|
output_path: PathBuf,
|
||||||
cross_compilation: &CrossCompileSetup,
|
cross_compilation: &CrossCompileSetup,
|
||||||
|
debug: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let entrypoint_json =
|
let entrypoint_json =
|
||||||
std::fs::read_to_string(directory.join("entrypoint.json")).map_err(|e| {
|
std::fs::read_to_string(directory.join("entrypoint.json")).map_err(|e| {
|
||||||
@@ -609,10 +627,6 @@ fn link_exe_from_dir(
|
|||||||
.filter_map(|v| directory.join(&v.obj_file).canonicalize().ok()),
|
.filter_map(|v| directory.join(&v.obj_file).canonicalize().ok()),
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("linking volume objects: {:#?}", entrypoint.volumes);
|
|
||||||
println!("object_paths: {:#?}", object_paths);
|
|
||||||
println!("object_paths: {:#?}", entrypoint.atoms);
|
|
||||||
|
|
||||||
let zig_triple = utils::triple_to_zig_triple(&cross_compilation.target);
|
let zig_triple = utils::triple_to_zig_triple(&cross_compilation.target);
|
||||||
let include_dirs = entrypoint
|
let include_dirs = entrypoint
|
||||||
.atoms
|
.atoms
|
||||||
@@ -715,8 +729,12 @@ fn link_exe_from_dir(
|
|||||||
cmd.args(files_winsdk);
|
cmd.args(files_winsdk);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{cmd:?}");
|
if debug {
|
||||||
let compilation = cmd.output().context("Could not execute `zig`")?;
|
println!("{cmd:?}");
|
||||||
|
}
|
||||||
|
let compilation = cmd
|
||||||
|
.output()
|
||||||
|
.context(anyhow!("Could not execute `zig`: {cmd:?}"))?;
|
||||||
|
|
||||||
if !compilation.status.success() {
|
if !compilation.status.success() {
|
||||||
return Err(anyhow::anyhow!(String::from_utf8_lossy(
|
return Err(anyhow::anyhow!(String::from_utf8_lossy(
|
||||||
@@ -725,6 +743,8 @@ fn link_exe_from_dir(
|
|||||||
.to_string()));
|
.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove file if it exists - if not done, can lead to errors on copy
|
||||||
|
let _ = std::fs::remove_file(&output_path);
|
||||||
std::fs::copy(&out_path, &output_path).map_err(|e| {
|
std::fs::copy(&out_path, &output_path).map_err(|e| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"could not copy from {} to {}: {e}",
|
"could not copy from {} to {}: {e}",
|
||||||
@@ -744,21 +764,11 @@ fn link_c_compilation() -> Result<(), anyhow::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn generate_wasmer_main_c_static(entrypoint: &Entrypoint) -> Result<String, anyhow::Error> {
|
|
||||||
generate_wasmer_main_c_inner(entrypoint, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_wasmer_main_c(entrypoint: &Entrypoint) -> Result<String, anyhow::Error> {
|
|
||||||
generate_wasmer_main_c_inner(entrypoint, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate the wasmer_main.c that links all object files together
|
/// Generate the wasmer_main.c that links all object files together
|
||||||
/// (depending on the object format / atoms number)
|
/// (depending on the object format / atoms number)
|
||||||
fn generate_wasmer_main_c_inner(
|
fn generate_wasmer_main_c(entrypoint: &Entrypoint) -> Result<String, anyhow::Error> {
|
||||||
entrypoint: &Entrypoint,
|
use std::fmt::Write;
|
||||||
compile_static: bool,
|
|
||||||
) -> Result<String, anyhow::Error> {
|
|
||||||
const WASMER_MAIN_C_SOURCE: &str = include_str!("wasmer_create_exe_main.c");
|
const WASMER_MAIN_C_SOURCE: &str = include_str!("wasmer_create_exe_main.c");
|
||||||
const WASMER_STATIC_MAIN_C_SOURCE: &str = include_str!("wasmer_static_create_exe_main.c");
|
const WASMER_STATIC_MAIN_C_SOURCE: &str = include_str!("wasmer_static_create_exe_main.c");
|
||||||
|
|
||||||
@@ -767,6 +777,8 @@ fn generate_wasmer_main_c_inner(
|
|||||||
Ok(WASMER_MAIN_C_SOURCE.replace("// WASI_DEFINES", "#define WASI"))
|
Ok(WASMER_MAIN_C_SOURCE.replace("// WASI_DEFINES", "#define WASI"))
|
||||||
}
|
}
|
||||||
ObjectFormat::Symbols => {
|
ObjectFormat::Symbols => {
|
||||||
|
let compile_static = true;
|
||||||
|
// always with compile zig + static_defs.h
|
||||||
let atom_names = entrypoint
|
let atom_names = entrypoint
|
||||||
.atoms
|
.atoms
|
||||||
.iter()
|
.iter()
|
||||||
@@ -776,30 +788,34 @@ fn generate_wasmer_main_c_inner(
|
|||||||
let mut c_code_to_add = String::new();
|
let mut c_code_to_add = String::new();
|
||||||
let mut c_code_to_instantiate = String::new();
|
let mut c_code_to_instantiate = String::new();
|
||||||
let mut deallocate_module = String::new();
|
let mut deallocate_module = String::new();
|
||||||
|
let mut extra_headers = Vec::new();
|
||||||
|
|
||||||
for a in atom_names.iter() {
|
for a in atom_names.iter() {
|
||||||
let atom_name = utils::normalize_atom_name(&a);
|
let atom_name = utils::normalize_atom_name(a);
|
||||||
let atom_name_uppercase = atom_name.to_uppercase();
|
let atom_name_uppercase = atom_name.to_uppercase();
|
||||||
let module_name = format!("WASMER_MODULE_{atom_name_uppercase}");
|
let module_name = format!("WASMER_MODULE_{atom_name_uppercase}");
|
||||||
|
|
||||||
c_code_to_add.push_str(&format!(
|
extra_headers.push(format!("#include \"static_defs_{atom_name}.h\""));
|
||||||
|
|
||||||
|
write!(
|
||||||
|
c_code_to_add,
|
||||||
"
|
"
|
||||||
extern size_t {module_name}_LENGTH asm(\"{module_name}_LENGTH\");
|
extern size_t {module_name}_LENGTH asm(\"{module_name}_LENGTH\");
|
||||||
extern char {module_name}_DATA asm(\"{module_name}_DATA\");
|
extern char {module_name}_DATA asm(\"{module_name}_DATA\");
|
||||||
"
|
"
|
||||||
));
|
)?;
|
||||||
|
|
||||||
if compile_static {
|
if compile_static {
|
||||||
c_code_to_instantiate.push_str(&format!("
|
write!(c_code_to_instantiate, "
|
||||||
wasm_module_t *atom_{atom_name} = wasmer_object_module_new(store, \"{atom_name}\");
|
wasm_module_t *atom_{atom_name} = wasmer_object_module_new_{atom_name}(store, \"{atom_name}\");
|
||||||
if (!atom_{atom_name}) {{
|
if (!atom_{atom_name}) {{
|
||||||
fprintf(stderr, \"Failed to create module from atom \\\"{atom_name}\\\"\\n\");
|
fprintf(stderr, \"Failed to create module from atom \\\"{atom_name}\\\"\\n\");
|
||||||
print_wasmer_error();
|
print_wasmer_error();
|
||||||
return -1;
|
return -1;
|
||||||
}}
|
}}
|
||||||
"));
|
")?;
|
||||||
} else {
|
} else {
|
||||||
c_code_to_instantiate.push_str(&format!("
|
write!(c_code_to_instantiate, "
|
||||||
wasm_byte_vec_t atom_{atom_name}_byte_vec = {{
|
wasm_byte_vec_t atom_{atom_name}_byte_vec = {{
|
||||||
.size = {module_name}_LENGTH,
|
.size = {module_name}_LENGTH,
|
||||||
.data = &{module_name}_DATA,
|
.data = &{module_name}_DATA,
|
||||||
@@ -810,13 +826,12 @@ fn generate_wasmer_main_c_inner(
|
|||||||
print_wasmer_error();
|
print_wasmer_error();
|
||||||
return -1;
|
return -1;
|
||||||
}}
|
}}
|
||||||
"));
|
")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
deallocate_module.push_str(&format!("wasm_module_delete(atom_{atom_name});"));
|
write!(deallocate_module, "wasm_module_delete(atom_{atom_name});")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("entrypoint volumes: {:?}", entrypoint.volumes);
|
|
||||||
let volumes_str = entrypoint
|
let volumes_str = entrypoint
|
||||||
.volumes
|
.volumes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -848,36 +863,39 @@ fn generate_wasmer_main_c_inner(
|
|||||||
)
|
)
|
||||||
.replace("// DECLARE_MODULES", &c_code_to_add)
|
.replace("// DECLARE_MODULES", &c_code_to_add)
|
||||||
.replace("// DECLARE_VOLUMES", &volumes_str)
|
.replace("// DECLARE_VOLUMES", &volumes_str)
|
||||||
|
.replace("// EXTRA_HEADERS", &extra_headers.join("\r\n"))
|
||||||
.replace("wasm_module_delete(module);", &deallocate_module);
|
.replace("wasm_module_delete(module);", &deallocate_module);
|
||||||
|
|
||||||
if atom_names.len() == 1 {
|
if atom_names.len() == 1 {
|
||||||
let atom_to_run = &atom_names[0];
|
let atom_to_run = &atom_names[0];
|
||||||
c_code_to_instantiate.push_str(&format!("module = atom_{atom_to_run};"));
|
write!(c_code_to_instantiate, "module = atom_{atom_to_run};")?;
|
||||||
} else {
|
} else {
|
||||||
for a in atom_names.iter() {
|
for a in atom_names.iter() {
|
||||||
c_code_to_instantiate.push_str(&format!(
|
writeln!(
|
||||||
"if (strcmp(selected_atom, \"{a}\") == 0) {{ module = atom_{}; }}\n",
|
c_code_to_instantiate,
|
||||||
|
"if (strcmp(selected_atom, \"{a}\") == 0) {{ module = atom_{}; }}",
|
||||||
utils::normalize_atom_name(a)
|
utils::normalize_atom_name(a)
|
||||||
));
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c_code_to_instantiate.push_str(&format!(
|
write!(
|
||||||
|
c_code_to_instantiate,
|
||||||
"
|
"
|
||||||
if (!module) {{
|
if (!module) {{
|
||||||
fprintf(stderr, \"No --command given, available commands are:\\n\");
|
fprintf(stderr, \"No --command given, available commands are:\\n\");
|
||||||
fprintf(stderr, \"\\n\");
|
fprintf(stderr, \"\\n\");
|
||||||
{commands}
|
{commands}
|
||||||
print_wasmer_error();
|
fprintf(stderr, \"\\n\");
|
||||||
return -1;
|
return -1;
|
||||||
}}
|
}}
|
||||||
",
|
",
|
||||||
commands = atom_names
|
commands = atom_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| format!("fprintf(stderr, \"{a}\\n\");"))
|
.map(|a| format!("fprintf(stderr, \" {a}\\n\");"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
));
|
)?;
|
||||||
|
|
||||||
Ok(return_str.replace("// INSTANTIATE_MODULES", &c_code_to_instantiate))
|
Ok(return_str.replace("// INSTANTIATE_MODULES", &c_code_to_instantiate))
|
||||||
}
|
}
|
||||||
@@ -898,10 +916,7 @@ pub(super) mod utils {
|
|||||||
target_triple: &Triple,
|
target_triple: &Triple,
|
||||||
cpu_features: &[CpuFeature],
|
cpu_features: &[CpuFeature],
|
||||||
) -> Target {
|
) -> Target {
|
||||||
let mut features = cpu_features
|
let mut features = cpu_features.iter().fold(CpuFeature::set(), |a, b| a | *b);
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.fold(CpuFeature::set(), |a, b| a | *b);
|
|
||||||
// Cranelift requires SSE2, so we have this "hack" for now to facilitate
|
// Cranelift requires SSE2, so we have this "hack" for now to facilitate
|
||||||
// usage
|
// usage
|
||||||
if target_triple.architecture == Architecture::X86_64 {
|
if target_triple.architecture == Architecture::X86_64 {
|
||||||
@@ -947,7 +962,7 @@ pub(super) mod utils {
|
|||||||
Some(v.canonicalize().unwrap_or(v))
|
Some(v.canonicalize().unwrap_or(v))
|
||||||
} else {
|
} else {
|
||||||
if let Some(local_tarball) = cross_subc.tarball.as_ref() {
|
if let Some(local_tarball) = cross_subc.tarball.as_ref() {
|
||||||
find_filename(local_tarball, &target)
|
find_filename(local_tarball, target)
|
||||||
} else {
|
} else {
|
||||||
// check if the tarball for the target already exists locally
|
// check if the tarball for the target already exists locally
|
||||||
let local_tarball = std::fs::read_dir(get_libwasmer_cache_path()?)?
|
let local_tarball = std::fs::read_dir(get_libwasmer_cache_path()?)?
|
||||||
@@ -960,15 +975,15 @@ pub(super) mod utils {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter_map(|p| filter_tarballs(&p, &target))
|
.filter_map(|p| filter_tarballs(&p, target))
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
if let Some(local_tarball) = local_tarball.as_ref() {
|
if let Some(local_tarball) = local_tarball.as_ref() {
|
||||||
find_filename(local_tarball, &target)
|
find_filename(local_tarball, target)
|
||||||
} else {
|
} else {
|
||||||
let release = super::http_fetch::get_latest_release()?;
|
let release = super::http_fetch::get_latest_release()?;
|
||||||
let tarball = super::http_fetch::download_release(release, target.clone())?;
|
let tarball = super::http_fetch::download_release(release, target.clone())?;
|
||||||
find_filename(&tarball, &target)
|
find_filename(&tarball, target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ok()
|
.ok()
|
||||||
@@ -1054,7 +1069,7 @@ pub(super) mod utils {
|
|||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
if char::is_alphabetic(c) {
|
if char::is_alphabetic(c) {
|
||||||
Some(c)
|
Some(c)
|
||||||
} else if c == '-' {
|
} else if c == '-' || c == '_' {
|
||||||
Some('_')
|
Some('_')
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -2,18 +2,15 @@
|
|||||||
//! Create a standalone native executable for a given Wasm file.
|
//! Create a standalone native executable for a given Wasm file.
|
||||||
|
|
||||||
use super::ObjectFormat;
|
use super::ObjectFormat;
|
||||||
use crate::{commands::PrefixerFn, store::CompilerOptions};
|
use crate::store::CompilerOptions;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::io::BufWriter;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
|
||||||
use wasmer::*;
|
use wasmer::*;
|
||||||
use wasmer_object::{emit_serialized, get_object_for_target};
|
|
||||||
#[cfg(feature = "webc_runner")]
|
#[cfg(feature = "webc_runner")]
|
||||||
use webc::{ParseOptions, WebCMmap};
|
use webc::{ParseOptions, WebCMmap};
|
||||||
|
|
||||||
@@ -61,7 +58,7 @@ pub struct CreateObj {
|
|||||||
impl CreateObj {
|
impl CreateObj {
|
||||||
/// Runs logic for the `create-obj` subcommand
|
/// Runs logic for the `create-obj` subcommand
|
||||||
pub fn execute(&self) -> Result<()> {
|
pub fn execute(&self) -> Result<()> {
|
||||||
let target_triple = self.target_triple.clone().unwrap_or_else(|| Triple::host());
|
let target_triple = self.target_triple.clone().unwrap_or_else(Triple::host);
|
||||||
let starting_cd = env::current_dir()?;
|
let starting_cd = env::current_dir()?;
|
||||||
let input_path = starting_cd.join(&self.path);
|
let input_path = starting_cd.join(&self.path);
|
||||||
let output_path = starting_cd.join(&self.output);
|
let output_path = starting_cd.join(&self.output);
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
#include "wasmer.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern size_t WASMER_MODULE_LENGTH asm("WASMER_MODULE_LENGTH");
|
|
||||||
extern char WASMER_MODULE_DATA asm("WASMER_MODULE_DATA");
|
|
||||||
|
|
||||||
wasm_module_t* wasmer_object_module_new(wasm_store_t* store, const char* wasm_name) {
|
|
||||||
wasm_byte_vec_t module_byte_vec = {
|
|
||||||
.size = WASMER_MODULE_LENGTH,
|
|
||||||
.data = (const char*)&WASMER_MODULE_DATA,
|
|
||||||
};
|
|
||||||
wasm_module_t* module = wasm_module_deserialize(store, &module_byte_vec);
|
|
||||||
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -93,6 +93,7 @@ int main(int argc, char *argv[]) {
|
|||||||
wasm_config_t *config = wasm_config_new();
|
wasm_config_t *config = wasm_config_new();
|
||||||
wasm_engine_t *engine = wasm_engine_new_with_config(config);
|
wasm_engine_t *engine = wasm_engine_new_with_config(config);
|
||||||
wasm_store_t *store = wasm_store_new(engine);
|
wasm_store_t *store = wasm_store_new(engine);
|
||||||
|
wasm_module_t *module = NULL;
|
||||||
|
|
||||||
const char* selected_atom = "main";
|
const char* selected_atom = "main";
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ fn create_exe_works_multi_command() -> anyhow::Result<()> {
|
|||||||
.run()
|
.run()
|
||||||
.context("Failed to create-exe wasm with Wasmer")?;
|
.context("Failed to create-exe wasm with Wasmer")?;
|
||||||
|
|
||||||
let result = run_code(
|
let _result = run_code(
|
||||||
&operating_dir,
|
&operating_dir,
|
||||||
&executable_path,
|
&executable_path,
|
||||||
&["--command".to_string(), "wabt".to_string()],
|
&["--command".to_string(), "wabt".to_string()],
|
||||||
|
|||||||
Reference in New Issue
Block a user