near_vm_compiler_singlepass/
compiler.rs

1//! Support for compiling with Singlepass.
2// Allow unused imports while developing.
3#![allow(unused_imports, dead_code)]
4
5use crate::codegen_x64::{
6    CodegenError, FuncGen, gen_import_call_trampoline, gen_std_dynamic_import_trampoline,
7    gen_std_trampoline,
8};
9use crate::config::Singlepass;
10use near_vm_compiler::{
11    Architecture, CallingConvention, Compilation, CompileError, CompileModuleInfo,
12    CompiledFunction, Compiler, CompilerConfig, CpuFeature, FunctionBody, FunctionBodyData,
13    ModuleTranslationState, OperatingSystem, SectionIndex, Target, TrapInformation,
14};
15use near_vm_types::entity::{EntityRef, PrimaryMap};
16use near_vm_types::{
17    FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, ModuleInfo, TableIndex,
18};
19use near_vm_vm::{TrapCode, VMOffsets};
20use rayon::prelude::{IntoParallelIterator, ParallelIterator};
21use std::sync::Arc;
22
23/// A compiler that compiles a WebAssembly module with Singlepass.
24/// It does the compilation in one pass
25pub struct SinglepassCompiler {
26    config: Singlepass,
27}
28
29impl SinglepassCompiler {
30    /// Creates a new Singlepass compiler
31    pub fn new(config: Singlepass) -> Self {
32        Self { config }
33    }
34
35    /// Gets the config for this Compiler
36    fn config(&self) -> &Singlepass {
37        &self.config
38    }
39}
40
41impl Compiler for SinglepassCompiler {
42    /// Compile the module using Singlepass, producing a compilation result with
43    /// associated relocations.
44    #[tracing::instrument(target = "near_vm", level = "debug", skip_all)]
45    fn compile_module(
46        &self,
47        target: &Target,
48        compile_info: &CompileModuleInfo,
49        function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
50        tunables: &dyn near_vm_vm::Tunables,
51        instrumentation: &finite_wasm::AnalysisOutcome,
52    ) -> Result<Compilation, CompileError> {
53        /*if target.triple().operating_system == OperatingSystem::Windows {
54            return Err(CompileError::UnsupportedTarget(
55                OperatingSystem::Windows.to_string(),
56            ));
57        }*/
58        if target.triple().architecture != Architecture::X86_64 {
59            return Err(CompileError::UnsupportedTarget(target.triple().architecture.to_string()));
60        }
61        if !target.cpu_features().contains(CpuFeature::AVX) {
62            return Err(CompileError::UnsupportedTarget("x86_64 without AVX".to_string()));
63        }
64        if compile_info.features.multi_value {
65            return Err(CompileError::UnsupportedFeature("multivalue".to_string()));
66        }
67        let calling_convention = match target.triple().default_calling_convention() {
68            Ok(CallingConvention::WindowsFastcall) => CallingConvention::WindowsFastcall,
69            Ok(CallingConvention::SystemV) => CallingConvention::SystemV,
70            //Ok(CallingConvention::AppleAarch64) => AppleAarch64,
71            _ => panic!("Unsupported Calling convention for Singlepass compiler"),
72        };
73
74        let table_styles = &compile_info.table_styles;
75        let module = &compile_info.module;
76        let pointer_width = target
77            .triple()
78            .pointer_width()
79            .map_err(|()| {
80                CompileError::UnsupportedTarget("target with unknown pointer width".into())
81            })?
82            .bytes();
83        let vmoffsets = VMOffsets::new(pointer_width).with_module_info(&module);
84        let make_assembler = || {
85            const KB: usize = 1024;
86            dynasmrt::VecAssembler::new_with_capacity(0, 128 * KB, 0, 0, KB, 0, KB)
87        };
88        let import_idxs = 0..module.import_counts.functions as usize;
89        let import_trampolines: PrimaryMap<SectionIndex, _> =
90            tracing::trace_span!(target: "near_vm", "import_trampolines", n_imports = import_idxs.len()).in_scope(
91                || {
92                    import_idxs
93                        .into_par_iter()
94                        .map_init(make_assembler, |assembler, i| {
95                            let i = FunctionIndex::new(i);
96                            gen_import_call_trampoline(
97                                &vmoffsets,
98                                i,
99                                &module.signatures[module.functions[i]],
100                                calling_convention,
101                                assembler,
102                            )
103                        })
104                        .collect::<Vec<_>>()
105                        .into_iter()
106                        .collect()
107                },
108            );
109        let functions = function_body_inputs
110            .iter()
111            .collect::<Vec<(LocalFunctionIndex, &FunctionBodyData<'_>)>>()
112            .into_par_iter()
113            .map_init(make_assembler, |assembler, (i, input)| {
114                tracing::trace_span!(target: "near_vm", "function", i = i.index()).in_scope(|| {
115                    let reader =
116                        near_vm_compiler::FunctionReader::new(input.module_offset, input.data);
117                    let stack_init_gas_cost = tunables
118                        .stack_init_gas_cost(instrumentation.function_frame_sizes[i.index()]);
119                    let stack_size = instrumentation.function_frame_sizes[i.index()]
120                        .checked_add(instrumentation.function_operand_stack_sizes[i.index()])
121                        .ok_or_else(|| {
122                            CompileError::Codegen(String::from(
123                                "got function with frame size going beyond u64::MAX",
124                            ))
125                        })?;
126                    let mut generator = FuncGen::new(
127                        assembler,
128                        module,
129                        &self.config,
130                        &target,
131                        &vmoffsets,
132                        &table_styles,
133                        i,
134                        calling_convention,
135                        stack_init_gas_cost,
136                        &instrumentation.gas_offsets[i.index()],
137                        &instrumentation.gas_costs[i.index()],
138                        &instrumentation.gas_kinds[i.index()],
139                        stack_size,
140                    )
141                    .map_err(to_compile_error)?;
142
143                    let mut local_reader = reader.get_locals_reader()?;
144                    for _ in 0..local_reader.get_count() {
145                        let (count, ty) = local_reader.read()?;
146                        // Overflows feeding a local here have most likely already been caught by the
147                        // validator, but it is possible that the validator hasn't been run at all, or
148                        // that the validator does not impose any limits on the number of locals.
149                        generator.feed_local(count, ty);
150                    }
151
152                    generator.emit_head().map_err(to_compile_error)?;
153
154                    let mut operator_reader =
155                        reader.get_operators_reader()?.into_iter_with_offsets();
156                    while generator.has_control_frames() {
157                        let (op, pos) =
158                            tracing::trace_span!(target: "near_vm", "parsing-next-operator")
159                                .in_scope(|| operator_reader.next().unwrap())?;
160                        generator.set_srcloc(pos as u32);
161                        generator.feed_operator(op).map_err(to_compile_error)?;
162                    }
163
164                    Ok(generator.finalize(&input))
165                })
166            })
167            .collect::<Result<Vec<CompiledFunction>, CompileError>>()?
168            .into_iter() // TODO: why not just collect to PrimaryMap directly?
169            .collect::<PrimaryMap<LocalFunctionIndex, CompiledFunction>>();
170
171        let function_call_trampolines =
172            tracing::trace_span!(target: "near_vm", "function_call_trampolines").in_scope(|| {
173                module
174                    .signatures
175                    .values()
176                    .collect::<Vec<_>>()
177                    .into_par_iter()
178                    .map_init(make_assembler, |assembler, func_type| {
179                        gen_std_trampoline(&func_type, calling_convention, assembler)
180                    })
181                    .collect::<Vec<_>>()
182                    .into_iter()
183                    .collect::<PrimaryMap<_, _>>()
184            });
185
186        let dynamic_function_trampolines =
187            tracing::trace_span!(target: "near_vm", "dynamic_function_trampolines").in_scope(
188                || {
189                    module
190                        .imported_function_types()
191                        .collect::<Vec<_>>()
192                        .into_par_iter()
193                        .map_init(make_assembler, |assembler, func_type| {
194                            gen_std_dynamic_import_trampoline(
195                                &vmoffsets,
196                                &func_type,
197                                calling_convention,
198                                assembler,
199                            )
200                        })
201                        .collect::<Vec<_>>()
202                        .into_iter()
203                        .collect::<PrimaryMap<FunctionIndex, FunctionBody>>()
204                },
205            );
206
207        Ok(Compilation {
208            functions,
209            custom_sections: import_trampolines,
210            function_call_trampolines,
211            dynamic_function_trampolines,
212            debug: None,
213            trampolines: None,
214        })
215    }
216}
217
218trait ToCompileError {
219    fn to_compile_error(self) -> CompileError;
220}
221
222impl ToCompileError for CodegenError {
223    fn to_compile_error(self) -> CompileError {
224        CompileError::Codegen(self.message)
225    }
226}
227
228fn to_compile_error<T: ToCompileError>(x: T) -> CompileError {
229    x.to_compile_error()
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use near_vm_compiler::{CpuFeature, Features, Triple};
236    use near_vm_vm::{MemoryStyle, TableStyle};
237    use std::str::FromStr;
238    use target_lexicon::triple;
239
240    fn dummy_compilation_ingredients<'a>() -> (
241        CompileModuleInfo,
242        PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
243        finite_wasm::AnalysisOutcome,
244    ) {
245        let compile_info = CompileModuleInfo {
246            features: Features::new(),
247            module: Arc::new(ModuleInfo::new()),
248            memory_styles: PrimaryMap::<MemoryIndex, MemoryStyle>::new(),
249            table_styles: PrimaryMap::<TableIndex, TableStyle>::new(),
250        };
251        let function_body_inputs = PrimaryMap::<LocalFunctionIndex, FunctionBodyData<'_>>::new();
252        let analysis = finite_wasm::AnalysisOutcome {
253            function_frame_sizes: Vec::new(),
254            function_operand_stack_sizes: Vec::new(),
255            gas_offsets: Vec::new(),
256            gas_costs: Vec::new(),
257            gas_kinds: Vec::new(),
258        };
259        (compile_info, function_body_inputs, analysis)
260    }
261
262    #[test]
263    fn errors_for_unsupported_targets() {
264        let compiler = SinglepassCompiler::new(Singlepass::default());
265
266        // Compile for 32bit Linux
267        let linux32 = Target::new(triple!("i686-unknown-linux-gnu"), CpuFeature::for_host());
268        let (mut info, inputs, analysis) = dummy_compilation_ingredients();
269        let result = compiler.compile_module(
270            &linux32,
271            &mut info,
272            inputs,
273            &near_vm_vm::TestTunables,
274            &analysis,
275        );
276        match result.unwrap_err() {
277            CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"),
278            error => panic!("Unexpected error: {:?}", error),
279        };
280
281        // Compile for win32
282        let win32 = Target::new(triple!("i686-pc-windows-gnu"), CpuFeature::for_host());
283        let (mut info, inputs, analysis) = dummy_compilation_ingredients();
284        let result = compiler.compile_module(
285            &win32,
286            &mut info,
287            inputs,
288            &near_vm_vm::TestTunables,
289            &analysis,
290        );
291        match result.unwrap_err() {
292            CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), // Windows should be checked before architecture
293            error => panic!("Unexpected error: {:?}", error),
294        };
295    }
296}