From d660bf5a2fefb2819d22863be3193db5ff6f0494 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Fri, 19 Jun 2020 15:43:55 +0200 Subject: [PATCH] wasmtime: Enable parallel compilation only when cache is enabled When running in embedded environments, threads creation is often undesirable. This adds a means to disable wasmtime's internal thread creation for parallel compilation and caches, by reusing the cache configuration as the indication. --- crates/environ/src/cranelift.rs | 259 +++++++++++++++++--------------- 1 file changed, 138 insertions(+), 121 deletions(-) diff --git a/crates/environ/src/cranelift.rs b/crates/environ/src/cranelift.rs index 89a7dfd6f6fb..3acb49291912 100644 --- a/crates/environ/src/cranelift.rs +++ b/crates/environ/src/cranelift.rs @@ -301,6 +301,7 @@ impl crate::compilation::Compiler for Cranelift { function_body_inputs: &translation.function_body_inputs, isa: Isa(isa), tunables: &translation.tunables, + enable_parallel_compilation: cache_config.enabled(), }, compile, )?; @@ -318,132 +319,147 @@ fn compile(env: CompileEnv<'_>) -> Result)>>() - .par_iter() - .map_init(FuncTranslator::new, |func_translator, (i, input)| { - let func_index = env.local.func_index(*i); - let mut context = Context::new(); - context.func.name = get_func_name(func_index); - context.func.signature = env.local.native_func_signature(func_index).clone(); - if env.tunables.debug_info { - context.func.collect_debug_info(); - } + type FunctionBodyInput<'a> = (DefinedFuncIndex, &'a FunctionBodyData<'a>); - let mut func_env = FuncEnvironment::new(isa.frontend_config(), env.local, env.tunables); - - // We use these as constant offsets below in - // `stack_limit_from_arguments`, so assert their values here. This - // allows the closure below to get coerced to a function pointer, as - // needed by `ir::Function`. - // - // Otherwise our stack limit is specially calculated from the vmctx - // argument, where we need to load the `*const VMInterrupts` - // pointer, and then from that pointer we need to load the stack - // limit itself. Note that manual register allocation is needed here - // too due to how late in the process this codegen happens. - // - // For more information about interrupts and stack checks, see the - // top of this file. - let vmctx = context - .func - .create_global_value(ir::GlobalValueData::VMContext); - let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load { - base: vmctx, - offset: i32::try_from(func_env.offsets.vmctx_interrupts()) - .unwrap() - .into(), - global_type: isa.pointer_type(), - readonly: true, - }); - let stack_limit = context.func.create_global_value(ir::GlobalValueData::Load { - base: interrupts_ptr, - offset: i32::try_from(func_env.offsets.vminterrupts_stack_limit()) - .unwrap() - .into(), - global_type: isa.pointer_type(), - readonly: false, - }); - context.func.stack_limit = Some(stack_limit); - func_translator.translate( - env.module_translation.0, - input.data, - input.module_offset, - &mut context.func, - &mut func_env, - )?; - - let mut code_buf: Vec = Vec::new(); - let mut reloc_sink = RelocSink::new(func_index); - let mut trap_sink = TrapSink::new(); - let mut stack_map_sink = StackMapSink::default(); - context - .compile_and_emit( - isa, - &mut code_buf, - &mut reloc_sink, - &mut trap_sink, - &mut stack_map_sink, - ) - .map_err(|error| { - CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) - })?; - - let unwind_info = context.create_unwind_info(isa).map_err(|error| { + let compile_function = |func_translator: &mut FuncTranslator, + (i, input): &FunctionBodyInput| { + let func_index = env.local.func_index(*i); + let mut context = Context::new(); + context.func.name = get_func_name(func_index); + context.func.signature = env.local.native_func_signature(func_index).clone(); + if env.tunables.debug_info { + context.func.collect_debug_info(); + } + + let mut func_env = FuncEnvironment::new(isa.frontend_config(), env.local, env.tunables); + + // We use these as constant offsets below in + // `stack_limit_from_arguments`, so assert their values here. This + // allows the closure below to get coerced to a function pointer, as + // needed by `ir::Function`. + // + // Otherwise our stack limit is specially calculated from the vmctx + // argument, where we need to load the `*const VMInterrupts` + // pointer, and then from that pointer we need to load the stack + // limit itself. Note that manual register allocation is needed here + // too due to how late in the process this codegen happens. + // + // For more information about interrupts and stack checks, see the + // top of this file. + let vmctx = context + .func + .create_global_value(ir::GlobalValueData::VMContext); + let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load { + base: vmctx, + offset: i32::try_from(func_env.offsets.vmctx_interrupts()) + .unwrap() + .into(), + global_type: isa.pointer_type(), + readonly: true, + }); + let stack_limit = context.func.create_global_value(ir::GlobalValueData::Load { + base: interrupts_ptr, + offset: i32::try_from(func_env.offsets.vminterrupts_stack_limit()) + .unwrap() + .into(), + global_type: isa.pointer_type(), + readonly: false, + }); + context.func.stack_limit = Some(stack_limit); + func_translator.translate( + env.module_translation.0, + input.data, + input.module_offset, + &mut context.func, + &mut func_env, + )?; + + let mut code_buf: Vec = Vec::new(); + let mut reloc_sink = RelocSink::new(func_index); + let mut trap_sink = TrapSink::new(); + let mut stack_map_sink = StackMapSink::default(); + context + .compile_and_emit( + isa, + &mut code_buf, + &mut reloc_sink, + &mut trap_sink, + &mut stack_map_sink, + ) + .map_err(|error| { CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) })?; - let address_transform = get_function_address_map(&context, input, code_buf.len(), isa); - - let ranges = if env.tunables.debug_info { - let ranges = context.build_value_labels_ranges(isa).map_err(|error| { - CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) - })?; - Some(ranges) - } else { - None - }; - - Ok(( - code_buf, - context.func.jt_offsets, - reloc_sink.func_relocs, - address_transform, - ranges, - context.func.stack_slots, - trap_sink.traps, - unwind_info, - stack_map_sink.finish(), - )) - }) - .collect::, CompileError>>()? - .into_iter() - .for_each( - |( - function, - func_jt_offsets, - relocs, - address_transform, - ranges, - sss, - function_traps, + let unwind_info = context.create_unwind_info(isa).map_err(|error| { + CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) + })?; + + let address_transform = get_function_address_map(&context, input, code_buf.len(), isa); + + let ranges = if env.tunables.debug_info { + let ranges = context.build_value_labels_ranges(isa).map_err(|error| { + CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) + })?; + Some(ranges) + } else { + None + }; + + Ok(( + code_buf, + context.func.jt_offsets, + reloc_sink.func_relocs, + address_transform, + ranges, + context.func.stack_slots, + trap_sink.traps, + unwind_info, + stack_map_sink.finish(), + )) + }; + + let inputs: Vec = env.function_body_inputs.into_iter().collect(); + + let results: Result, CompileError> = if env.enable_parallel_compilation { + inputs + .par_iter() + .map_init(FuncTranslator::new, compile_function) + .collect() + } else { + inputs + .iter() + .map(|input| { + let mut func_translator = FuncTranslator::new(); + compile_function(&mut func_translator, input) + }) + .collect() + }; + + results?.into_iter().for_each( + |( + function, + func_jt_offsets, + relocs, + address_transform, + ranges, + sss, + function_traps, + unwind_info, + stack_map, + )| { + functions.push(CompiledFunction { + body: function, + jt_offsets: func_jt_offsets, unwind_info, - stack_map, - )| { - functions.push(CompiledFunction { - body: function, - jt_offsets: func_jt_offsets, - unwind_info, - }); - relocations.push(relocs); - address_transforms.push(address_transform); - value_ranges.push(ranges.unwrap_or_default()); - stack_slots.push(sss); - traps.push(function_traps); - stack_maps.push(stack_map); - }, - ); + }); + relocations.push(relocs); + address_transforms.push(address_transform); + value_ranges.push(ranges.unwrap_or_default()); + stack_slots.push(sss); + traps.push(function_traps); + stack_maps.push(stack_map); + }, + ); // TODO: Reorganize where we create the Vec for the resolved imports. @@ -465,6 +481,7 @@ struct CompileEnv<'a> { function_body_inputs: &'a PrimaryMap>, isa: Isa<'a, 'a>, tunables: &'a Tunables, + enable_parallel_compilation: bool, } /// This is a wrapper struct to hash the specific bits of `TargetIsa` that