codegen an llvm test wrapper similar to the wasm one

This commit is contained in:
Folkert 2022-07-10 15:08:41 +02:00
parent c961d08b7e
commit e36f6f0908
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
2 changed files with 169 additions and 46 deletions

View file

@ -766,6 +766,122 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
(main_fn_name, main_fn)
}
fn promote_to_wasm_test_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
mod_solutions: &'a ModSolutions,
symbol: Symbol,
top_level: ProcLayout<'a>,
) -> (&'static str, FunctionValue<'ctx>) {
// generates roughly
//
// fn $Test.wasm_test_wrapper() -> *T {
// result = roc_main();
// ptr = roc_malloc(size_of::<T>)
// *ptr = result
// ret ptr;
// }
let main_fn_name = "$Test.wasm_test_wrapper";
let it = top_level.arguments.iter().copied();
let bytes = roc_alias_analysis::func_name_bytes_help(
symbol,
it,
CapturesNiche::no_niche(),
&top_level.result,
);
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs();
let func_spec = it.next().unwrap();
debug_assert!(
it.next().is_none(),
"we expect only one specialization of this symbol"
);
// NOTE fake layout; it is only used for debug prints
let roc_main_fn = function_value_by_func_spec(
env,
*func_spec,
symbol,
&[],
CapturesNiche::no_niche(),
&Layout::UNIT,
);
let main_fn = {
let c_function_spec = {
match roc_main_fn.get_type().get_return_type() {
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::Generic);
FunctionSpec::cconv(env, CCReturn::Return, Some(output_type.into()), &[])
}
None => todo!(),
}
};
let c_function = add_func(
env.context,
env.module,
main_fn_name,
c_function_spec,
Linkage::External,
);
let subprogram = env.new_subprogram(main_fn_name);
c_function.set_subprogram(subprogram);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
let entry = context.append_basic_block(c_function, "entry");
builder.position_at_end(entry);
// call the main roc function
let roc_main_fn_result = call_roc_function(env, roc_main_fn, &top_level.result, &[]);
// reserve space for the result on the heap
// pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
let (size, alignment) = top_level.result.stack_size_and_alignment(env.target_info);
let roc_alloc = env.module.get_function("roc_alloc").unwrap();
let call = builder.build_call(
roc_alloc,
&[
env.ptr_int().const_int(size as _, false).into(),
env.context
.i32_type()
.const_int(alignment as _, false)
.into(),
],
"result_ptr",
);
call.set_call_convention(C_CALL_CONV);
let void_ptr = call.try_as_basic_value().left().unwrap();
let ptr = builder.build_pointer_cast(
void_ptr.into_pointer_value(),
c_function
.get_type()
.get_return_type()
.unwrap()
.into_pointer_type(),
"cast_ptr",
);
builder.build_store(ptr, roc_main_fn_result);
builder.build_return(Some(&ptr));
c_function
};
(main_fn_name, main_fn)
}
fn int_with_precision<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: i128,
@ -4160,6 +4276,23 @@ pub fn build_procedures<'a, 'ctx, 'env>(
build_procedures_help(env, opt_level, procedures, entry_point, debug_output_file);
}
pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
opt_level: OptLevel,
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
) -> (&'static str, FunctionValue<'ctx>) {
let mod_solutions = build_procedures_help(
env,
opt_level,
procedures,
entry_point,
Some(Path::new("/tmp/test.ll")),
);
promote_to_wasm_test_wrapper(env, mod_solutions, entry_point.symbol, entry_point.layout)
}
pub fn build_procedures_return_main<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
opt_level: OptLevel,

View file

@ -16,7 +16,7 @@ use roc_region::all::LineInfo;
use roc_reporting::report::RenderTarget;
use target_lexicon::Triple;
const TEST_WRAPPER_NAME: &str = "test_wrapper";
const TEST_WRAPPER_NAME: &str = "$Test.wasm_test_wrapper";
pub const OPT_LEVEL: OptLevel = if cfg!(debug_assertions) {
OptLevel::Normal
@ -223,12 +223,21 @@ fn create_llvm_module<'a>(
// platform to provide them.
add_default_roc_externs(&env);
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
config.opt_level,
procedures,
entry_point,
);
let (main_fn_name, main_fn) = match config.mode {
LlvmBackendMode::Binary => unreachable!(),
LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper(
&env,
config.opt_level,
procedures,
entry_point,
),
LlvmBackendMode::GenTest => roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
config.opt_level,
procedures,
entry_point,
),
};
env.dibuilder.finalize();
@ -414,32 +423,6 @@ fn llvm_module_to_wasm_file(
test_wasm_path
}
#[allow(dead_code)]
fn wasm_roc_panic(address: u32, tag_id: u32) {
match tag_id {
0 => {
let mut string = "";
// MEMORY.with(|f| {
// let memory = f.borrow().unwrap();
//
// let memory_bytes: &[u8] = unsafe { memory.data_unchecked() };
// let index = address as usize;
// let slice = &memory_bytes[index..];
// let c_ptr: *const u8 = slice.as_ptr();
//
// use std::ffi::CStr;
// use std::os::raw::c_char;
// let slice = unsafe { CStr::from_ptr(c_ptr as *const c_char) };
// string = slice.to_str().unwrap();
// });
panic!("Roc failed with message: {:?}", string)
}
_ => todo!(),
}
}
#[allow(dead_code)]
fn fake_wasm_main_function(_: u32, _: u32) -> u32 {
panic!("wasm entered the main function; this should never happen!")
@ -453,6 +436,24 @@ fn get_memory(rt: &wasm3::Runtime) -> &[u8] {
}
}
fn link_module(module: &mut wasm3::Module, panic_msg: Rc<Mutex<Option<(i32, i32)>>>) {
module.link_wasi().unwrap();
let try_link_panic = module.link_closure(
"env",
"send_panic_msg_to_rust",
move |_call_context, args: (i32, i32)| {
let mut w = panic_msg.lock().unwrap();
*w = Some(args);
Ok(())
},
);
match try_link_panic {
Ok(()) => {}
Err(wasm3::error::Error::FunctionNotFound) => {}
Err(e) => panic!("{:?}", e),
}
}
#[allow(dead_code)]
pub fn assert_wasm_evals_to_help<T>(src: &str, ignore_problems: bool) -> Result<T, String>
where
@ -480,7 +481,7 @@ where
let parsed = Module::parse(&env, &wasm_bytes[..]).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let panic_msg: Rc<Mutex<Option<(i32, i32)>>> = Default::default();
// link_module(&mut module, panic_msg.clone());
link_module(&mut module, panic_msg.clone());
let test_wrapper = module
.find_function::<(), i32>(TEST_WRAPPER_NAME)
@ -501,17 +502,6 @@ where
Ok(address) => {
let memory: &[u8] = get_memory(&rt);
// if false {
// println!("test_wrapper returned 0x{:x}", address);
// println!("Stack:");
// crate::helpers::wasm::debug_memory_hex(memory, address, std::mem::size_of::<T>());
// }
// if false {
// println!("Heap:");
// // Manually provide address and size based on printf in wasm_test_platform.c
// crate::helpers::wasm::debug_memory_hex(memory, 0x11440, 24);
// }
let output = <T as FromWasm32Memory>::decode(memory, address as u32);
Ok(output)
@ -523,7 +513,7 @@ where
macro_rules! assert_wasm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => {
match $crate::helpers::llvm::assert_wasm_evals_to_help::<$ty>($src, $ignore_problems) {
Err(msg) => panic!("Wasm test failed: {:?}", msg),
Err(msg) => panic!("Wasm test failed: {}", msg),
Ok(actual) => {
assert_eq!($transform(actual), $expected, "Wasm test failed")
}