Merge pull request #518 from rtfeldman/c-abi-progress

C abi progress
This commit is contained in:
Richard Feldman 2020-09-14 18:28:20 -04:00 committed by GitHub
commit 099d9e35f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 333 additions and 88 deletions

View file

@ -1,7 +1,6 @@
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine; use inkwell::execution_engine::ExecutionEngine;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_builtins::unique::uniq_stdlib; use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
@ -15,7 +14,6 @@ use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Imp
use roc_fmt::annotation::{Formattable, Newlines, Parens}; use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_gen::layout_id::LayoutIds; use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_mono::ir::Procs; use roc_mono::ir::Procs;
@ -223,10 +221,6 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
// in --release mode and then trying to eval anything in the repl. // in --release mode and then trying to eval anything in the repl.
ExecutionEngine::link_in_mc_jit(); ExecutionEngine::link_in_mc_jit();
let main_fn_type = basic_type_from_layout(&arena, &context, &main_ret_layout, ptr_bytes)
.fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let mut env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
@ -322,23 +316,10 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
} }
} }
// Add main to the module. let (main_fn_name, main_fn) = roc_gen::llvm::build::make_main_function(
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
main_fn.set_call_conventions(cc);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
roc_gen::llvm::build::build_exp_stmt(
&env, &env,
&mut layout_ids, &mut layout_ids,
&mut roc_gen::llvm::build::Scope::default(), &main_ret_layout,
main_fn,
&main_body, &main_body,
); );

View file

@ -118,20 +118,67 @@ fn jit_to_ast_help<'a>(
} }
}; };
// Functions can return structs of either 8 or 16 bytes, depending
// on whether we're compiling for a 64-bit or 32-bit target.
match env.ptr_bytes { match env.ptr_bytes {
// 64-bit target (8-byte pointers, 16-byte structs) // 64-bit target (8-byte pointers, 16-byte structs)
8 => jit_map!( 8 => match layout.stack_size(env.ptr_bytes) {
execution_engine, 8 => {
main_fn_name, // just one eightbyte, returned as-is
[u8; 16], jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
|bytes: [u8; 16]| { ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) } ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
), })
}
16 => {
// two eightbytes, returned as-is
jit_map!(
execution_engine,
main_fn_name,
[u8; 16],
|bytes: [u8; 16]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
}
)
}
_ => {
// anything more than 2 eightbytes
// the return "value" is a pointer to the result
jit_map!(
execution_engine,
main_fn_name,
*const u8,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
)
}
},
// 32-bit target (4-byte pointers, 8-byte structs) // 32-bit target (4-byte pointers, 8-byte structs)
4 => jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { 4 => {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) // TODO what are valid return sizes here?
}), // this is just extrapolated from the 64-bit case above
// and not (yet) actually tested on a 32-bit system
match layout.stack_size(env.ptr_bytes) {
4 => {
// just one fourbyte, returned as-is
jit_map!(execution_engine, main_fn_name, [u8; 4], |bytes: [u8; 4]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
}
8 => {
// just one fourbyte, returned as-is
jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
}
_ => {
// anything more than 2 fourbytes
// the return "value" is a pointer to the result
jit_map!(
execution_engine,
main_fn_name,
*const u8,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
)
}
}
}
other => { other => {
panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other); panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other);
} }
@ -280,7 +327,7 @@ fn struct_to_ast<'a>(
output.push(loc_field); output.push(loc_field);
// Advance the field pointer to the next field. // Advance the field pointer to the next field.
field_ptr = unsafe { ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; field_ptr = unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
} }
Expr::Record { Expr::Record {

View file

@ -232,6 +232,15 @@ mod repl_eval {
); );
} }
#[test]
fn three_element_record() {
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
expect_success(
"{ a: 1, b: 2, c: 3 }",
"{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }",
);
}
// #[test] // #[test]
// fn multiline_string() { // fn multiline_string() {
// // If a string contains newlines, format it as a multiline string in the output // // If a string contains newlines, format it as a multiline string in the output

View file

@ -219,6 +219,120 @@ pub fn construct_optimization_passes<'a>(
(mpm, fpm) (mpm, fpm)
} }
/// For communication with C (tests and platforms) we need to abide by the C calling convention
///
/// While small values are just returned like with the fast CC, larger structures need to
/// be written into a pointer (into the callers stack)
enum PassVia {
Register,
Memory,
}
impl PassVia {
fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self {
if layout.stack_size(ptr_bytes) > 16 {
PassVia::Memory
} else {
PassVia::Register
}
}
}
pub fn make_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
use inkwell::types::BasicType;
use PassVia::*;
let context = env.context;
let builder = env.builder;
let arena = env.arena;
let ptr_bytes = env.ptr_bytes;
let return_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes);
let roc_main_fn_name = "$Test.roc_main";
// make the roc main function
let roc_main_fn_type = return_type.fn_type(&[], false);
// Add main to the module.
let roc_main_fn = env
.module
.add_function(roc_main_fn_name, roc_main_fn_type, None);
// our exposed main function adheres to the C calling convention
roc_main_fn.set_call_conventions(FAST_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(roc_main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
build_exp_stmt(
env,
layout_ids,
&mut Scope::default(),
roc_main_fn,
main_body,
);
// build the C calling convention wrapper
let main_fn_name = "$Test.main";
let register_or_memory = PassVia::from_layout(env.ptr_bytes, layout);
let main_fn_type = match register_or_memory {
Memory => {
let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into();
context.void_type().fn_type(&[return_value_ptr], false)
}
Register => return_type.fn_type(&[], false),
};
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
// our exposed main function adheres to the C calling convention
main_fn.set_call_conventions(C_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
let call = builder.build_call(roc_main_fn, &[], "call_roc_main");
call.set_call_convention(FAST_CALL_CONV);
let call_result = call.try_as_basic_value().left().unwrap();
match register_or_memory {
Memory => {
// write the result into the supplied pointer
// this is a void function, therefore return None
let ptr_return_type = return_type.ptr_type(AddressSpace::Generic);
let ptr_as_int = main_fn.get_first_param().unwrap();
let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr");
builder.build_store(ptr.into_pointer_value(), call_result);
builder.build_return(None);
}
Register => {
// construct a normal return
// values are passed to the caller via registers
builder.build_return(Some(&call_result));
}
}
(main_fn_name, env.arena.alloc(main_fn))
}
fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace { fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace {
match layout { match layout {
Layout::Builtin(Builtin::EmptyList) => InPlace::InPlace, Layout::Builtin(Builtin::EmptyList) => InPlace::InPlace,

View file

@ -533,4 +533,147 @@ mod gen_records {
i64 i64
); );
} }
#[test]
fn return_record_2() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
[3, 5],
[i64; 2]
);
}
#[test]
fn return_record_3() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 4 }
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
fn return_record_4() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2 }
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
#[test]
fn return_record_5() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1 }
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
#[test]
fn return_record_6() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
#[test]
fn return_record_7() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
fn return_record_float_int() {
assert_evals_to!(
indoc!(
r#"
{ a: 3.14, b: 0x1 }
"#
),
(3.14, 0x1),
(f64, i64)
);
}
#[test]
fn return_record_int_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 0x1, b: 3.14 }
"#
),
(0x1, 3.14),
(i64, f64)
);
}
#[test]
fn return_record_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14 }
"#
),
(6.28, 3.14),
(f64, f64)
);
}
#[test]
fn return_record_float_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14, c: 0.1 }
"#
),
(6.28, 3.14, 0.1),
(f64, f64, f64)
);
}
#[test]
fn just_to_be_sure() {
assert_evals_to!(
indoc!(
r#"
{ a: 1, b : 2, c : 3 }
"#
),
[1, 2, 3],
[i64; 3]
);
}
} }

View file

@ -8,11 +8,8 @@ pub fn helper_without_uniqueness<'a>(
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { ) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{can_expr, infer_expr, CanExprOut}; use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::types::BasicType;
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_gen::llvm::build::Scope;
use roc_gen::llvm::build::{build_proc, build_proc_header}; use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout; use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host(); let target = target_lexicon::Triple::host();
@ -66,7 +63,7 @@ pub fn helper_without_uniqueness<'a>(
roc_gen::llvm::build::construct_optimization_passes(module, opt_level); roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| { let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!( panic!(
"Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}", "Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}",
err err
@ -76,10 +73,6 @@ pub fn helper_without_uniqueness<'a>(
.create_jit_execution_engine(OptimizationLevel::None) .create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test"); .expect("Error creating JIT execution engine for test");
let main_fn_type =
basic_type_from_layout(&arena, context, &layout, ptr_bytes).fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let mut env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
@ -163,25 +156,8 @@ pub fn helper_without_uniqueness<'a>(
} }
} }
// Add main to the module. let (main_fn_name, main_fn) =
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
main_fn.set_call_conventions(cc);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&mut Scope::default(),
main_fn,
&main_body,
);
// Uncomment this to see the module's un-optimized LLVM instruction output: // Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
@ -212,11 +188,8 @@ pub fn helper_with_uniqueness<'a>(
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { ) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{infer_expr, uniq_expr}; use crate::helpers::{infer_expr, uniq_expr};
use inkwell::types::BasicType;
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_gen::llvm::build::Scope;
use roc_gen::llvm::build::{build_proc, build_proc_header}; use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout; use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host(); let target = target_lexicon::Triple::host();
@ -258,7 +231,7 @@ pub fn helper_with_uniqueness<'a>(
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level); let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| { let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!( panic!(
"Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}", "Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}",
err err
@ -269,11 +242,6 @@ pub fn helper_with_uniqueness<'a>(
.create_jit_execution_engine(OptimizationLevel::None) .create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test"); .expect("Error creating JIT execution engine for test");
let main_fn_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes)
.fn_type(&[], false)
.clone();
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env { let mut env = roc_gen::llvm::build::Env {
arena: &arena, arena: &arena,
@ -357,25 +325,8 @@ pub fn helper_with_uniqueness<'a>(
} }
} }
// Add main to the module. let (main_fn_name, main_fn) =
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
let cc = roc_gen::llvm::build::FAST_CALL_CONV;
main_fn.set_call_conventions(cc);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
// builds the function body (return statement included)
roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&mut Scope::default(),
main_fn,
&main_body,
);
// you're in the version with uniqueness! // you're in the version with uniqueness!