Fix quicksort benchmark

This commit is contained in:
Richard Feldman 2020-08-09 23:40:55 -04:00
parent 1c828f7811
commit 35e132e6d7
4 changed files with 90 additions and 16 deletions

View file

@ -1,7 +1,11 @@
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::module::Linkage; use inkwell::module::Linkage;
use inkwell::targets::{
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
};
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_collections::all::default_hasher;
use roc_gen::layout_id::LayoutIds; use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{ use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
@ -9,10 +13,7 @@ use roc_gen::llvm::build::{
use roc_load::file::LoadedModule; use roc_load::file::LoadedModule;
use roc_mono::ir::{Env, PartialProc, Procs}; use roc_mono::ir::{Env, PartialProc, Procs};
use roc_mono::layout::LayoutCache; use roc_mono::layout::LayoutCache;
use std::collections::HashSet;
use inkwell::targets::{
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor};
@ -95,6 +96,12 @@ pub fn gen(
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
}; };
let mut exposed_symbols =
HashSet::with_capacity_and_hasher(loaded.exposed_vars_by_symbol.len(), default_hasher());
for (symbol, _) in loaded.exposed_vars_by_symbol {
exposed_symbols.insert(symbol);
}
// Add modules' decls to Procs // Add modules' decls to Procs
for (_, mut decls) in decls_by_id for (_, mut decls) in decls_by_id
@ -113,6 +120,34 @@ pub fn gen(
Closure(annotation, _, _, loc_args, boxed_body) => { Closure(annotation, _, _, loc_args, boxed_body) => {
let (loc_body, ret_var) = *boxed_body; let (loc_body, ret_var) = *boxed_body;
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
// never gets called by Roc code, it will never
// get specialized!
if exposed_symbols.contains(&symbol) {
let mut pattern_vars =
bumpalo::collections::Vec::with_capacity_in(
loc_args.len(),
arena,
);
for (var, _) in loc_args.iter() {
pattern_vars.push(*var);
}
let layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err|
todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err)
);
procs.insert_exposed(
symbol,
layout,
pattern_vars, //: Vec<'a, Variable>,
annotation,
ret_var,
);
}
procs.insert_named( procs.insert_named(
&mut mono_env, &mut mono_env,
&mut layout_cache, &mut layout_cache,
@ -197,7 +232,7 @@ pub fn gen(
// NOTE: This is here to be uncommented in case verification fails. // NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.) // (This approach means we don't have to defensively clone name here.)
// //
// println!("\n\nBuilding and then verifying function {}\n\n", name); // println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
@ -215,15 +250,13 @@ pub fn gen(
let cc = get_call_conventions(target.default_calling_convention().unwrap()); let cc = get_call_conventions(target.default_calling_convention().unwrap());
let interns = &env.interns; let interns = &env.interns;
for (symbol, _) in loaded.exposed_vars_by_symbol { for symbol in exposed_symbols {
let fn_name = format!( // Since it was exposed, it will be monomorphic, so its LLVM name
"{}.{}", // will be ___#1 (e.g. "main#1")
symbol.module_string(interns), let fn_name = format!("{}#1", symbol.ident_string(interns));
symbol.ident_string(interns)
);
let fn_val = env.module.get_function(&fn_name).unwrap(); let fn_val = env.module.get_function(&fn_name).unwrap();
fn_val.set_linkage(Linkage::AvailableExternally); fn_val.set_linkage(Linkage::External);
fn_val.set_call_conventions(cc); fn_val.set_call_conventions(cc);
} }
} }

View file

@ -1365,10 +1365,10 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
} }
let fn_type = get_fn_type(&ret_type, &arg_basic_types); let fn_type = get_fn_type(&ret_type, &arg_basic_types);
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let fn_val = env let fn_val = env
.module .module
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private)); .add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));

View file

@ -108,6 +108,41 @@ impl<'a> Procs<'a> {
} }
} }
/// Add a named function that will be publicly exposed to the host
pub fn insert_exposed(
&mut self,
name: Symbol,
layout: Layout<'a>,
pattern_vars: Vec<'a, Variable>,
fn_var: Variable,
ret_var: Variable,
) {
let tuple = (name, layout);
// If we've already specialized this one, no further work is needed.
if self.specialized.contains_key(&tuple) {
return;
}
// We're done with that tuple, so move layout back out to avoid cloning it.
let (name, layout) = tuple;
let pending = PendingSpecialization {
pattern_vars,
ret_var,
fn_var,
};
// This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations {
Some(pending_specializations) => {
// register the pending specialization, so this gets code genned later
add_pending(pending_specializations, name, layout, pending)
}
None => unreachable!("insert_exposed was called after the pending specializations phase had already completed!"),
}
}
// TODO trim these down // TODO trim these down
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn insert_anonymous( pub fn insert_anonymous(

View file

@ -7,7 +7,7 @@ use std::time::SystemTime;
#[link(name = "roc_app", kind = "static")] #[link(name = "roc_app", kind = "static")]
extern "C" { extern "C" {
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link_name = "0#1"] #[link_name = "main#1"]
fn quicksort(list: Box<[i64]>) -> Box<[i64]>; fn quicksort(list: Box<[i64]>) -> Box<[i64]>;
} }
@ -46,13 +46,19 @@ pub fn main() {
} }
}; };
// TODO FIXME don't truncate! This is just for testing.
nums.truncate(1000);
let nums: Box<[i64]> = nums.into();
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now(); let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) }; let _answer = unsafe { quicksort(nums) };
let end_time = SystemTime::now(); let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap(); let duration = end_time.duration_since(start_time).unwrap();
println!( println!(
"Roc quicksort took {:?} ms to compute this answer: {:?}", "Roc quicksort took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0, duration.as_secs_f64() * 1000.0,
list list
); );