near_vm_compiler_singlepass/
compiler.rs1#![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
23pub struct SinglepassCompiler {
26 config: Singlepass,
27}
28
29impl SinglepassCompiler {
30 pub fn new(config: Singlepass) -> Self {
32 Self { config }
33 }
34
35 fn config(&self) -> &Singlepass {
37 &self.config
38 }
39}
40
41impl Compiler for SinglepassCompiler {
42 #[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().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 _ => 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 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() .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 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 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"), error => panic!("Unexpected error: {:?}", error),
294 };
295 }
296}