blob: 0738ad0375fe991eb8b2f39a385de85b32f032c3 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use teaclave_executor_context::context::reset_thread_context;
use teaclave_executor_context::context::set_thread_context;
use teaclave_executor_context::context::Context;
use teaclave_executor_context::context::{
wasm_close_file, wasm_create_output, wasm_open_input, wasm_read_file, wasm_write_file,
};
use std::ffi::{c_void, CStr, CString};
use std::os::raw::{c_char, c_int};
use teaclave_types::{FunctionArguments, FunctionRuntime, TeaclaveExecutor};
// TVM example needs 20MB heap to run in WAMR
const DEFAULT_HEAP_SIZE: u32 = 20971520;
const DEFAULT_STACK_SIZE: u32 = 163840;
const DEFAULT_ERROR_BUF_SIZE: usize = 128;
#[repr(C)]
#[derive(Debug)]
struct NativeSymbol {
symbol: *const c_char,
func_ptr: *const c_void,
signature: *const c_char,
attachment: *const c_void,
}
extern "C" {
fn wasm_runtime_init() -> bool;
fn wasm_runtime_load(
buf: *const u8,
size: u32,
error_buf: *mut u8,
error_buf_size: u32,
) -> *const c_void;
fn wasm_runtime_instantiate(
module: *const c_void,
stack_size: u32,
heap_size: u32,
error_buf: *mut u8,
error_buf_size: u32,
) -> *const c_void;
fn wasm_runtime_lookup_function(
module_inst: *const c_void,
name: *const c_char,
signature: *const u8,
) -> *const c_void;
fn wasm_runtime_create_exec_env(module_inst: *const c_void, stack_size: u32) -> *const c_void;
fn wasm_runtime_call_wasm(
exec_env: *const c_void,
function: *const c_void,
argc: u32,
argv: *const u32,
) -> bool;
fn wasm_runtime_module_dup_data(module_inst: *const c_void, src: *const u8, size: u32) -> u32;
fn wasm_runtime_module_free(module_inst: *const c_void, ptr: u32);
fn wasm_runtime_register_natives(
module_name: *const c_char,
native_symbols: *const NativeSymbol,
n_native_symbols: u32,
) -> bool;
fn wasm_runtime_get_exception(module_inst: *const c_void) -> *const c_char;
fn wasm_runtime_deinstantiate(module_inst: *const c_void);
fn wasm_runtime_unload(module: *const c_void);
fn wasm_runtime_destroy();
}
#[derive(Default)]
pub struct WAMicroRuntime;
impl TeaclaveExecutor for WAMicroRuntime {
fn execute(
&self,
_name: String,
arguments: FunctionArguments,
payload: Vec<u8>,
runtime: FunctionRuntime,
) -> anyhow::Result<String> {
let wa_argv: Vec<_> = arguments.into_vec();
let mut error_buf = [0u8; DEFAULT_ERROR_BUF_SIZE];
let entry_name = CString::new("entrypoint").expect("CString::new failed");
set_thread_context(Context::new(runtime))?;
let ret = unsafe { wasm_runtime_init() };
assert!(ret);
// export native function
let export_symbols: [NativeSymbol; 5] = [
NativeSymbol {
symbol: b"teaclave_open_input\0".as_ptr() as _,
func_ptr: wasm_open_input as *const c_void,
signature: b"($)i\0".as_ptr() as _,
attachment: std::ptr::null(),
},
NativeSymbol {
symbol: b"teaclave_create_output\0".as_ptr() as _,
func_ptr: wasm_create_output as *const c_void,
signature: b"($)i\0".as_ptr() as _,
attachment: std::ptr::null(),
},
NativeSymbol {
symbol: b"teaclave_read_file\0".as_ptr() as _,
func_ptr: wasm_read_file as *const c_void,
signature: b"(i*~)i\0".as_ptr() as _,
attachment: std::ptr::null(),
},
NativeSymbol {
symbol: b"teaclave_write_file\0".as_ptr() as _,
func_ptr: wasm_write_file as *const c_void,
signature: b"(i*~)i\0".as_ptr() as _,
attachment: std::ptr::null(),
},
NativeSymbol {
symbol: b"teaclave_close_file\0".as_ptr() as _,
func_ptr: wasm_close_file as *const c_void,
signature: b"(i)i\0".as_ptr() as _,
attachment: std::ptr::null(),
},
];
let register_succeeded = unsafe {
wasm_runtime_register_natives(
b"env\0".as_ptr() as _,
export_symbols.as_ptr(),
export_symbols.len() as u32,
)
};
assert!(register_succeeded);
let module = unsafe {
wasm_runtime_load(
payload.as_ptr(),
payload.len() as u32,
error_buf.as_mut_ptr(),
error_buf.len() as u32,
)
};
assert!((module as usize) != 0);
error_buf = [0u8; DEFAULT_ERROR_BUF_SIZE];
let module_instance = unsafe {
wasm_runtime_instantiate(
module,
DEFAULT_STACK_SIZE,
DEFAULT_HEAP_SIZE,
error_buf.as_mut_ptr(),
error_buf.len() as u32,
)
};
assert!((module_instance as usize) != 0);
let entry_func = unsafe {
wasm_runtime_lookup_function(
module_instance,
entry_name.as_ptr() as _,
std::ptr::null(),
)
};
assert!((entry_func as usize) != 0);
let exec_env = unsafe { wasm_runtime_create_exec_env(module_instance, DEFAULT_STACK_SIZE) };
assert!((exec_env as usize) != 0);
// prepare the arguments
// for best compatibility with Teaclave, the function signature is `int entrypoint(int argc, char* argv[])`
let cstr_argv: Vec<_> = wa_argv
.iter()
.map(|arg| CString::new(arg.as_str()).unwrap())
.collect();
let wasm_argc = 2;
let p_argv: Vec<u32> = cstr_argv
.iter() // do NOT into_iter()
.map(|arg| unsafe {
wasm_runtime_module_dup_data(
module_instance,
arg.as_ptr() as *const u8,
arg.to_bytes_with_nul().len() as u32,
)
})
.collect();
let func_argv = unsafe {
wasm_runtime_module_dup_data(
module_instance,
p_argv.as_ptr() as *const u8,
(p_argv.len() * 4) as u32,
)
};
let wasm_argv: [u32; 2] = [p_argv.len() as u32, func_argv];
let result =
unsafe { wasm_runtime_call_wasm(exec_env, entry_func, wasm_argc, wasm_argv.as_ptr()) };
// clean WAMR allocated memory
let _ = p_argv
.iter()
.map(|addr| unsafe { wasm_runtime_module_free(module_instance, *addr) });
unsafe { wasm_runtime_module_free(module_instance, func_argv) };
let result = if result {
let rv = wasm_argv[0] as c_int;
log::debug!(
"IN WAMicroRuntime::execute after `wasm_runtime_call_wasm`, {:?}",
rv
);
Ok(rv.to_string())
} else {
let error = unsafe { CStr::from_ptr(wasm_runtime_get_exception(module_instance)) };
log::debug!("WAMR ERROR: {:?}", error);
Ok(error.to_str().unwrap().to_string())
};
unsafe { wasm_runtime_deinstantiate(module_instance) };
unsafe { wasm_runtime_unload(module) };
unsafe { wasm_runtime_destroy() };
reset_thread_context()?;
result
}
}
#[cfg(feature = "enclave_unit_test")]
pub mod tests {
use super::*;
use std::collections::HashMap;
use std::io::Read;
use std::untrusted::fs;
use teaclave_crypto::*;
use teaclave_runtime::*;
use teaclave_test_utils::*;
use teaclave_types::*;
pub fn run_tests() -> bool {
run_tests!(test_wamr_millionaire,)
}
// FIXME: see https://github.com/apache/incubator-teaclave/issues/688
#[allow(dead_code)]
fn test_wamr_tvm_mnist() {
let mut args = HashMap::new();
args.insert("input_img".to_string(), "input_img".to_string());
let args = FunctionArguments::from_map(args);
let mut file =
fs::File::open("../../examples/python/wasm_tvm_mnist_payload/mnist.wasm").unwrap();
let mut wa_payload = Vec::new();
file.read_to_end(&mut wa_payload).unwrap();
let input_img = "fixtures/functions/wamr_tvm_mnist/img_10.jpg";
let input_img_info =
StagedFileInfo::new(input_img, TeaclaveFile128Key::random(), FileAuthTag::mock());
let input_files = StagedFiles::new(hashmap!("input_img" => input_img_info));
let output_files = StagedFiles::default();
let runtime = Box::new(RawIoRuntime::new(input_files, output_files));
let function = WAMicroRuntime::default();
let summary = function
.execute("".to_string(), args, wa_payload, runtime)
.unwrap();
log::debug!(
"IN TEST test_wamr_tvm_mnist: AFTER execution, summary: {:?}",
summary
);
assert_eq!(summary, "3");
}
fn test_wamr_millionaire() {
let mut args = HashMap::new();
args.insert("input_file_id1".to_string(), "pf_in_a".to_string());
args.insert("input_file_id2".to_string(), "pf_in_b".to_string());
args.insert("output_file_id".to_string(), "pf_out".to_string());
let args = FunctionArguments::from_map(args);
let mut file = fs::File::open(
"../../examples/python/wasm_c_millionaire_problem_payload/millionaire_problem.wasm",
)
.unwrap();
let mut wa_payload = Vec::new();
file.read_to_end(&mut wa_payload).unwrap();
let input_a = "fixtures/functions/wamr_c_millionaire_problem/input_a.txt";
let input_b = "fixtures/functions/wamr_c_millionaire_problem/input_b.txt";
let output = "fixtures/functions/wamr_c_millionaire_problem/output.txt";
let expected_output = "fixtures/functions/wamr_c_millionaire_problem/expected_output.txt";
let input_a_info =
StagedFileInfo::new(input_a, TeaclaveFile128Key::random(), FileAuthTag::mock());
let input_b_info =
StagedFileInfo::new(input_b, TeaclaveFile128Key::random(), FileAuthTag::mock());
let output_info =
StagedFileInfo::new(output, TeaclaveFile128Key::random(), FileAuthTag::mock());
let input_files =
StagedFiles::new(hashmap!("pf_in_a" => input_a_info, "pf_in_b" => input_b_info));
let output_files = StagedFiles::new(hashmap!("pf_out" => output_info));
let runtime = Box::new(RawIoRuntime::new(input_files, output_files));
let function = WAMicroRuntime::default();
let summary = function
.execute("".to_string(), args, wa_payload, runtime)
.unwrap();
log::debug!(
"IN TEST test_wamr_millionaire: AFTER execution, summary: {:?}",
summary
);
assert_eq!(summary, "7");
let output = fs::read_to_string(output).unwrap();
let expected = fs::read_to_string(expected_output).unwrap();
assert_eq!(&output[..], &expected[..]);
}
}