mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
codegen an llvm test wrapper similar to the wasm one
This commit is contained in:
parent
c961d08b7e
commit
e36f6f0908
2 changed files with 169 additions and 46 deletions
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue