From 15116df5e6f6d871d355393b4f41adee2ff4e3a0 Mon Sep 17 00:00:00 2001 From: Dmitri Makarov Date: Mon, 18 Sep 2023 08:57:17 -0400 Subject: [PATCH] Extend entrypoint generation to handle multi-module packages --- .../move-stdlib/tests/fixedpoint32_tests.move | 2 +- language/solana/move-to-solana/src/lib.rs | 17 +- .../src/stackless/entrypoint.rs | 529 +++++++++++++++++ .../move-to-solana/src/stackless/llvm.rs | 1 + .../move-to-solana/src/stackless/mod.rs | 2 + .../src/stackless/module_context.rs | 557 ++++-------------- .../move-to-solana/src/stackless/translate.rs | 35 +- .../tools/move-mv-llvm-compiler/src/main.rs | 55 +- .../move-mv-llvm-compiler/tests/rbpf-tests.rs | 8 + .../tests/rbpf-tests/entry-point08.json | 7 + .../tests/rbpf-tests/entry-point08.move | 26 + 11 files changed, 782 insertions(+), 457 deletions(-) create mode 100644 language/solana/move-to-solana/src/stackless/entrypoint.rs create mode 100644 language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.json create mode 100644 language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.move diff --git a/language/move-stdlib/tests/fixedpoint32_tests.move b/language/move-stdlib/tests/fixedpoint32_tests.move index 83513dbe0c..1e81af1fdf 100644 --- a/language/move-stdlib/tests/fixedpoint32_tests.move +++ b/language/move-stdlib/tests/fixedpoint32_tests.move @@ -1,5 +1,5 @@ #[test_only] -module std::fixed_point32_tests { +module std::fp32_tests { use std::fixed_point32; #[test] diff --git a/language/solana/move-to-solana/src/lib.rs b/language/solana/move-to-solana/src/lib.rs index a63813f0ca..18bfef4812 100644 --- a/language/solana/move-to-solana/src/lib.rs +++ b/language/solana/move-to-solana/src/lib.rs @@ -390,6 +390,9 @@ fn compile(global_env: &GlobalEnv, options: &Options) -> anyhow::Result<()> { .or_else(|err| anyhow::bail!("Error creating directory: {}", err))?; } let mut objects = vec![]; + let entry_llmod = global_cx.llvm_cx.create_module("solana_entrypoint"); + let entrypoint_generator = + EntrypointGenerator::new(&global_cx, &entry_llmod, &llmachine, options); for mod_id in global_env .get_modules() .collect::>() @@ -401,12 +404,15 @@ fn compile(global_env: &GlobalEnv, options: &Options) -> anyhow::Result<()> { let modname = module.llvm_module_name(); debug!("Generating code for module {}", modname); let llmod = global_cx.llvm_cx.create_module(&modname); - let mod_cx = global_cx.create_module_context(mod_id, &llmod, options); + let mod_cx = + global_cx.create_module_context(mod_id, &llmod, &entrypoint_generator, options); mod_cx.translate(); let mut out_path = out_path.join(&modname); out_path.set_extension(&options.output_file_extension); let mut output_file = out_path.to_str().unwrap().to_string(); + // llmod is moved and dropped in both branches of this + // if-then-else when the module is written to a file. if options.llvm_ir { output_file = options.output.clone(); let path = Path::new(&output_file); @@ -435,6 +441,10 @@ fn compile(global_env: &GlobalEnv, options: &Options) -> anyhow::Result<()> { } } if !(options.compile || options.llvm_ir) { + if entrypoint_generator.has_entries() { + let output_file = entrypoint_generator.write_object_file(&out_path).unwrap(); + objects.push(Path::new(&output_file).to_path_buf()); + } link_object_files( out_path, objects.as_slice(), @@ -442,8 +452,9 @@ fn compile(global_env: &GlobalEnv, options: &Options) -> anyhow::Result<()> { &options.move_native_archive, )?; } - // NB: context must outlive llvm module - // fixme this should be handled with lifetimes + // FIXME: this should be handled with lifetimes. + // Context (global_cx) must outlive llvm module (entry_llmod). + drop(entry_llmod); drop(global_cx); Ok(()) } diff --git a/language/solana/move-to-solana/src/stackless/entrypoint.rs b/language/solana/move-to-solana/src/stackless/entrypoint.rs new file mode 100644 index 0000000000..41d3d44db5 --- /dev/null +++ b/language/solana/move-to-solana/src/stackless/entrypoint.rs @@ -0,0 +1,529 @@ +// Copyright (c) The Diem Core Contributors +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + options::Options, + stackless::{ + extensions::*, llvm, module_context::ModuleContext, rttydesc::RttyContext, GlobalContext, + RtCall, + }, +}; +use log::{debug, log_enabled, Level}; +use move_core_types::u256::U256; +use move_model::{model as mm, ty as mty}; +use move_native::shared::MOVE_UNTYPED_VEC_DESC_SIZE; +use std::{ + cell::{Cell, RefCell}, + collections::BTreeMap, + path::Path, +}; + +pub struct EntrypointGenerator<'mm, 'up> { + pub env: &'mm mm::GlobalEnv, + pub llvm_cx: &'up llvm::Context, + pub llvm_module: &'up llvm::Module, + pub llvm_builder: llvm::Builder, + pub options: &'up Options, + pub rtty_cx: RttyContext<'mm, 'up>, + + fn_decls: RefCell>, + + ll_fn_solana_entrypoint: llvm::Function, + entry_slice_ptr: llvm::AnyValue, + entry_slice_len: llvm::AnyValue, + insn_data_ptr: llvm::AnyValue, + offset: llvm::Alloca, + retval: llvm::AnyValue, + exit_bb: llvm::BasicBlock, + + entries: Cell, + target_machine: &'up llvm::TargetMachine, +} + +/** + * Generate solana entrypoint functon code. This function + * recieves serialized input paramteres from the VM. It calls + * native function `deserialize` to decode the parameters into + * corresponding data structures. The function `deserialize` + * returns a triple consiting of + * - instruction_data -- a byte array, + * - program_id -- SolanaPubkey, and + * - accounts -- a vector of SolanaAccountInfo items. + * + * To select one from possibly several entry functions defined in + * the module, the entrypoint function expects the name of the + * requested entry function to be passed in instruction_data byte + * array. The logic in solana entrypoint iteratively compares the + * string slice passed in instruction_data to every entry function + * symbol of the module. Once a matching entry function is found, + * it is called, and its return value is used as the exit code for + * the program. + */ +impl<'mm, 'up> EntrypointGenerator<'mm, 'up> { + pub fn new( + global_context: &'up GlobalContext<'mm>, + llvm_module: &'up llvm::Module, + target_machine: &'up llvm::TargetMachine, + options: &'mm Options, + ) -> EntrypointGenerator<'mm, 'up> { + let env = global_context.env; + let llvm_cx = &global_context.llvm_cx; + let llvm_builder = llvm_cx.create_builder(); + let rtty_cx = RttyContext::new(env, llvm_cx, llvm_module); + + let ll_fnty = { + let ll_rty = llvm_cx.int_type(64_usize); + let ll_param_tys = vec![llvm_cx.ptr_type()]; + llvm::FunctionType::new(ll_rty, &ll_param_tys) + }; + let ll_fn_solana_entrypoint = llvm_module.add_function("main", ll_fnty); + let entry_block = ll_fn_solana_entrypoint.append_basic_block("entry"); + llvm_builder.position_at_end(entry_block); + let retval = llvm_builder + .build_alloca(llvm_cx.int_type(64), "retval") + .as_any_value(); + let offset = llvm_builder.build_alloca(llvm_cx.int_type(64), "offset"); + llvm_builder.store_const( + llvm::Constant::int(llvm_cx.int_type(64), U256::zero()), + offset, + ); + + // Get inputs from the VM into proper data structures. + let (insn_data, _program_id, _accounts) = { + let input = ll_fn_solana_entrypoint.get_param(0).as_any_value(); + let ll_sret = llvm_cx.get_anonymous_struct_type(&[ + rtty_cx.get_llvm_type_for_slice(), + llvm_cx.ptr_type(), + rtty_cx.get_llvm_type_for_move_native_vector(), + ]); + let params = llvm_builder.build_alloca(ll_sret, "params").as_any_value(); + let args = vec![params, input]; + let ll_fn_deserialize = ModuleContext::get_runtime_function( + llvm_cx, + llvm_module, + &rtty_cx, + &RtCall::Deserialize(params, input), + ); + llvm_builder.call(ll_fn_deserialize, &args); + + let insn_data = llvm_builder.getelementptr( + params, + &ll_sret.as_struct_type(), + 0, + "instruction_data", + ); + let program_id = + llvm_builder.getelementptr(params, &ll_sret.as_struct_type(), 1, "program_id"); + let accounts = + llvm_builder.getelementptr(params, &ll_sret.as_struct_type(), 2, "accounts"); + (insn_data, program_id, accounts) + }; + // Make a str slice from instruction_data byte array returned + // from a call to deserialize + let str_slice_type = rtty_cx.get_llvm_type_for_slice(); + let insn_data_ptr = llvm_builder.getelementptr( + insn_data, + &str_slice_type.as_struct_type(), + 0, + "insn_data_ptr", + ); + let insn_data_ptr = + llvm_builder.load(insn_data_ptr, llvm_cx.ptr_type(), "insn_data_ptr_loaded"); + let offset_value = Self::advance_offset_by_increment( + llvm_cx, + &llvm_builder, + offset.as_any_value(), + llvm::Constant::int(llvm_cx.int_type(64), U256::from(8u64)).as_any_value(), + ); + let entry_slice_ptr = llvm_builder.build_address_with_indices( + llvm_cx.int_type(8), + insn_data_ptr, + &[offset_value], + "entry_slice_ptr", + ); + let entry_slice_len = + llvm_builder.load(insn_data_ptr, llvm_cx.int_type(64), "entry_slice_len"); + let _offset_value = Self::advance_offset_by_increment( + llvm_cx, + &llvm_builder, + offset.as_any_value(), + entry_slice_len, + ); + + let curr_bb = llvm_builder.get_insert_block(); + let exit_bb = ll_fn_solana_entrypoint.insert_basic_block_after(curr_bb, "exit_bb"); + + EntrypointGenerator { + env, + llvm_cx, + llvm_module, + llvm_builder, + options, + rtty_cx, + fn_decls: RefCell::new(BTreeMap::new()), + ll_fn_solana_entrypoint, + entry_slice_ptr, + entry_slice_len, + insn_data_ptr, + offset, + retval, + exit_bb, + entries: Cell::new(0), + target_machine, + } + } + + pub fn add_entry_declaration( + &self, + ll_sym_name: &str, + llfn_type: llvm::FunctionType, + attrs: &[(llvm_sys::LLVMAttributeIndex, &str, Option)], + ) { + debug_assert!( + !self.fn_decls.borrow().contains_key(ll_sym_name), + "Duplicate entry function name found." + ); + let tfn = self.llvm_module.add_function(ll_sym_name, llfn_type); + self.llvm_module.add_attributes(tfn, attrs); + tfn.as_gv() + .set_linkage(llvm::LLVMLinkage::LLVMExternalLinkage); + self.fn_decls + .borrow_mut() + .insert(ll_sym_name.to_owned(), tfn); + } + + pub fn add_entries(&self, mod_cx: &ModuleContext) { + debug!("unit test function {:?}", self.options.unit_test_function); + let unit_test_function = self.options.unit_test_function.clone().unwrap_or_default(); + let entry_functions: Vec<_> = mod_cx + .env + .get_functions() + .filter(|fn_env| { + fn_env.is_entry() + || fn_env.get_full_name_str().replace("::", "__") == unit_test_function + }) + .collect(); + + // Do not generate solana entrypoint if module doesn't contain any entry functions. + if entry_functions.is_empty() { + return; + } + self.entries + .set(self.entries.get() + entry_functions.len() as u64); + let str_slice_type = self.rtty_cx.get_llvm_type_for_slice(); + + // For every entry function defined in the module compare its + // name to the name passed in the instruction_data, and call + // the matching entry function. + for fun in entry_functions { + let entry = self.generate_global_str_slice(fun.llvm_symbol_name(&[]).as_str()); + + let func_name_ptr = self.llvm_builder.getelementptr( + entry.as_any_value(), + &str_slice_type.as_struct_type(), + 0, + "entry_func_ptr", + ); + let func_name_ptr = self.llvm_builder.load( + func_name_ptr, + self.llvm_cx.ptr_type(), + "entry_func_ptr_loaded", + ); + let func_name_len = self.llvm_builder.getelementptr( + entry.as_any_value(), + &str_slice_type.as_struct_type(), + 1, + "entry_func_len", + ); + let func_name_len = self.llvm_builder.load( + func_name_len, + self.llvm_cx.int_type(64), + "entry_func_len_loaded", + ); + + let rtcall = RtCall::StrCmpEq( + self.entry_slice_ptr, + self.entry_slice_len, + func_name_ptr, + func_name_len, + ); + let llfn = ModuleContext::get_runtime_function( + self.llvm_cx, + self.llvm_module, + &self.rtty_cx, + &rtcall, + ); + let params = vec![ + self.entry_slice_ptr, + self.entry_slice_len, + func_name_ptr, + func_name_len, + ]; + let condition = self.llvm_builder.call(llfn, ¶ms); + let curr_bb = self.llvm_builder.get_insert_block(); + let then_bb = self + .ll_fn_solana_entrypoint + .insert_basic_block_after(curr_bb, "then_bb"); + let else_bb = self + .ll_fn_solana_entrypoint + .insert_basic_block_after(then_bb, "else_bb"); + self.llvm_builder.build_cond_br(condition, then_bb, else_bb); + self.llvm_builder.position_at_end(then_bb); + let fn_name = fun.llvm_symbol_name(&[]); + let fn_decls = self.fn_decls.borrow(); + let ll_fun = fn_decls.get(&fn_name).unwrap(); + let params = self.emit_entry_arguments( + mod_cx, + &fun, + &self.insn_data_ptr, + &self.offset.as_any_value(), + ); + let ret = self.llvm_builder.call(*ll_fun, ¶ms); + if fun.get_return_count() > 0 { + self.llvm_builder.store(ret, self.retval); + } else { + self.llvm_builder.store( + llvm::Constant::int(self.llvm_cx.int_type(64), U256::zero()).as_any_value(), + self.retval, + ); + } + self.llvm_builder.build_br(self.exit_bb); + self.llvm_builder.position_at_end(else_bb); + } + } + + pub fn has_entries(&self) -> bool { + self.entries.get() > 0 + } + + pub fn write_object_file(&self, out_path: &Path) -> anyhow::Result { + self.emit_exit(); + let output_file = out_path.join("solana_entrypoint.o"); + let output_file = output_file.to_str().unwrap(); + self.target_machine + .emit_to_obj_file(self.llvm_module, output_file)?; + Ok(output_file.to_string()) + } + + fn emit_exit(&self) { + // Abort if no entry function matched the requested name. + ModuleContext::emit_rtcall_abort_raw( + self.llvm_cx, + &self.llvm_builder, + self.llvm_module, + &self.rtty_cx, + move_core_types::vm_status::StatusCode::EXECUTE_ENTRY_FUNCTION_CALLED_ON_NON_ENTRY_FUNCTION as u64, + ); + self.llvm_builder.position_at_end(self.exit_bb); + let ret = self + .llvm_builder + .load(self.retval, self.llvm_cx.int_type(64), "exit_code"); + self.llvm_builder.build_return(ret); + self.llvm_module.verify(); + + if log_enabled!(target: "entry_point", Level::Debug) { + self.llvm_module.dump(); + } + } + + // This function extracts an entry function actual arguments from + // instruction_data byte array, containing values of actual + // arguments in sequential order without gaps. + fn emit_entry_arguments( + &self, + mod_cx: &ModuleContext, + fn_env: &mm::FunctionEnv, + instruction_data: &llvm::AnyValue, + index: &llvm::AnyValue, + ) -> Vec { + if fn_env.get_parameter_count() == 0 { + return vec![]; + } + let llcx = self.llvm_cx; + let i64_ty = llcx.int_type(64); + let byte_ty = llcx.int_type(8); + let mut index_value = self.llvm_builder.load(*index, i64_ty, "index_value"); + let mut args = vec![]; + for ty in fn_env.get_parameter_types() { + let mut arg = self.llvm_builder.build_address_with_indices( + byte_ty, + *instruction_data, + &[index_value], + "arg_ptr", + ); + match ty { + mty::Type::Primitive(mty::PrimitiveType::Bool) => { + arg = self.llvm_builder.load(arg, llcx.int_type(1), "arg"); + index_value = Self::advance_offset_by_increment( + self.llvm_cx, + &self.llvm_builder, + *index, + llvm::Constant::int(i64_ty, U256::one()).as_any_value(), + ); + } + mty::Type::Reference(_, ty) => { + index_value = Self::advance_offset_by_increment( + self.llvm_cx, + &self.llvm_builder, + *index, + llvm::Constant::int(i64_ty, U256::from(ty.get_bitwidth() / 8)) + .as_any_value(), + ); + } + // This code is generated when control flow of the + // function already has branches. LLVM seems to allow + // stack allocations only in the entry basic + // block. Therefore the layout of serialized vector + // must have a valid vector structure, which can be + // referenced for loading arguments from. This code + // assumes that the vector is represented as + // {data_pointer, length, capacity} triple and data + // follows this triple immediately. We patch the + // data_pointer location in memory to contain the + // address of the actual vector's data. The length and + // the capaciity are assumed to have the same value. + mty::Type::Vector(ty) => { + let vec_ty = self.rtty_cx.get_llvm_type_for_move_native_vector(); + let vec_ptr = self.llvm_builder.getelementptr( + arg, + &vec_ty.as_struct_type(), + 0, + "vec_ptr", + ); + let vec_len = self.llvm_builder.getelementptr( + arg, + &vec_ty.as_struct_type(), + 1, + "vec_len", + ); + index_value = Self::advance_offset_by_increment( + self.llvm_cx, + &self.llvm_builder, + *index, + llvm::Constant::int(i64_ty, U256::from(MOVE_UNTYPED_VEC_DESC_SIZE)) + .as_any_value(), + ); + let vec_data = self.llvm_builder.build_address_with_indices( + byte_ty, + *instruction_data, + &[index_value], + "vec_data", + ); + self.llvm_builder.store(vec_data, vec_ptr); + arg = self.llvm_builder.load(arg, vec_ty, "vec_arg"); + let vec_element_size = + llvm::Constant::int(i64_ty, U256::from(ty.get_bitwidth() / 8)) + .as_any_value(); + let vec_len = self.llvm_builder.load(vec_len, i64_ty, "vec_len_loaded"); + let vec_data_len = self.llvm_builder.build_binop( + llvm_sys::LLVMOpcode::LLVMMul, + vec_len, + vec_element_size, + "vec_data_len", + ); + index_value = Self::advance_offset_by_increment( + self.llvm_cx, + &self.llvm_builder, + *index, + vec_data_len, + ); + } + mty::Type::Struct(mid, sid, _) => { + arg = self.llvm_builder.load( + arg, + mod_cx.to_llvm_type(&ty, &[]).unwrap(), + "str_arg", + ); + let g_env = &self.env; + let s_env = g_env.get_module(mid).into_struct(sid); + // FIXME! This computes incorrect width when + // fields of a struct are structs themselves. + // get_bitwidth() on Type::Struct currently + // returns 0, because model creates Type::Struct + // values with an empty vector for field + // types. Only instantiations of structs include + // actual types of fields in their corresponding + // Type::Struct values. + let width = s_env + .get_fields() + .map(|f_env| f_env.get_type()) + .fold(0, |acc, ty| acc + ty.get_bitwidth()); + let size = llvm::Constant::int(i64_ty, U256::from(width / 8)).as_any_value(); + index_value = Self::advance_offset_by_increment( + self.llvm_cx, + &self.llvm_builder, + *index, + size, + ); + } + _ => { + arg = + self.llvm_builder + .load(arg, mod_cx.to_llvm_type(&ty, &[]).unwrap(), "arg"); + let size = llvm::Constant::int(i64_ty, U256::from(ty.get_bitwidth() / 8)) + .as_any_value(); + index_value = Self::advance_offset_by_increment( + self.llvm_cx, + &self.llvm_builder, + *index, + size, + ); + } + } + args.push(arg); + } + args + } + + fn generate_global_str_slice(&self, s: &str) -> llvm::Global { + let llcx = &self.llvm_cx; + + // Create an LLVM global for the string. + let str_literal_init = llcx.const_int_array::(s.as_bytes()).as_const(); + let str_literal = self + .llvm_module + .add_global2(str_literal_init.llvm_type(), "str_literal"); + str_literal.set_constant(); + str_literal.set_alignment(1); + str_literal.set_unnamed_addr(); + str_literal.set_linkage(llvm::LLVMLinkage::LLVMPrivateLinkage); + str_literal.set_initializer(str_literal_init); + + // Create an LLVM global for the slice, which is a struct with two fields: + // - pointer to the string literal + // - integer length of the string literal + let slice_len = s.len(); + let slice_init = llcx.const_struct(&[ + str_literal.ptr(), + llvm::Constant::int(llcx.int_type(64), U256::from(slice_len as u128)), + ]); + let slice = self + .llvm_module + .add_global2(slice_init.llvm_type(), "str_slice"); + slice.set_constant(); + slice.set_alignment(8); + slice.set_unnamed_addr(); + slice.set_linkage(llvm::LLVMLinkage::LLVMPrivateLinkage); + slice.set_initializer(slice_init); + + slice + } + + fn advance_offset_by_increment( + llvm_cx: &'up llvm::Context, + llvm_builder: &llvm::Builder, + offset: llvm::AnyValue, + increment: llvm::AnyValue, + ) -> llvm::AnyValue { + let offset_loaded = llvm_builder.load(offset, llvm_cx.int_type(64), "offset_loaded"); + let offset_loaded = llvm_builder.build_binop( + llvm_sys::LLVMOpcode::LLVMAdd, + offset_loaded, + increment, + "offset_loaded", + ); + llvm_builder.store(offset_loaded, offset); + offset_loaded + } +} diff --git a/language/solana/move-to-solana/src/stackless/llvm.rs b/language/solana/move-to-solana/src/stackless/llvm.rs index eab6b81a2d..4419c3b08f 100644 --- a/language/solana/move-to-solana/src/stackless/llvm.rs +++ b/language/solana/move-to-solana/src/stackless/llvm.rs @@ -216,6 +216,7 @@ impl Context { #[derive(Copy, Clone)] pub struct TargetData(LLVMTargetDataRef); +#[derive(Debug)] pub struct Module(LLVMModuleRef); impl Drop for Module { diff --git a/language/solana/move-to-solana/src/stackless/mod.rs b/language/solana/move-to-solana/src/stackless/mod.rs index 9c831192e3..3842001e30 100644 --- a/language/solana/move-to-solana/src/stackless/mod.rs +++ b/language/solana/move-to-solana/src/stackless/mod.rs @@ -2,12 +2,14 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 +mod entrypoint; pub mod extensions; mod llvm; mod module_context; mod rttydesc; mod translate; +pub use entrypoint::EntrypointGenerator; pub use llvm::*; pub use module_context::*; pub use translate::*; diff --git a/language/solana/move-to-solana/src/stackless/module_context.rs b/language/solana/move-to-solana/src/stackless/module_context.rs index c72795da3b..d64294df63 100644 --- a/language/solana/move-to-solana/src/stackless/module_context.rs +++ b/language/solana/move-to-solana/src/stackless/module_context.rs @@ -5,11 +5,11 @@ use crate::{ options::Options, stackless::{ - extensions::*, llvm, llvm::TargetMachine, rttydesc::RttyContext, FunctionContext, RtCall, - TargetPlatform, + entrypoint::EntrypointGenerator, extensions::*, llvm, llvm::TargetMachine, + rttydesc::RttyContext, FunctionContext, RtCall, TargetPlatform, }, }; -use log::{debug, log_enabled, Level}; +use log::debug; use move_binary_format::file_format::SignatureToken; use move_core_types::u256::U256; use move_model::{model as mm, ty as mty}; @@ -23,8 +23,9 @@ use std::{ iter, }; -pub struct ModuleContext<'mm, 'up> { +pub struct ModuleContext<'mm: 'up, 'up> { pub env: mm::ModuleEnv<'mm>, + pub entrypoint_generator: &'up EntrypointGenerator<'mm, 'up>, pub llvm_cx: &'up llvm::Context, pub llvm_module: &'up llvm::Module, pub llvm_builder: llvm::Builder, @@ -40,7 +41,7 @@ pub struct ModuleContext<'mm, 'up> { pub rtty_cx: RttyContext<'mm, 'up>, } -impl<'mm, 'up> ModuleContext<'mm, 'up> { +impl<'mm: 'up, 'up> ModuleContext<'mm, 'up> { pub fn translate(mut self) { let filename = self.env.get_source_path().to_str().expect("utf-8"); self.llvm_module.set_source_file_name(filename); @@ -63,7 +64,7 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { } if !self.env.is_script_module() { - self.emit_solana_entrypoint(); + self.entrypoint_generator.add_entries(&self); } self.llvm_module.verify(); @@ -463,6 +464,7 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { fn_data: &FunctionData, linkage: llvm::LLVMLinkage, ) { + let mut linkage = linkage; let ll_sym_name = fn_env.llvm_symbol_name(tyvec); debug!("Declare Move function {ll_sym_name}"); let ll_fn = { @@ -519,6 +521,12 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { attrs.push((parm_num, "noalias", None)); } } + let unit_test = self.options.unit_test_function.clone().unwrap_or_default(); + if fn_env.is_entry() || fn_env.get_full_name_str().replace("::", "__") == unit_test { + self.entrypoint_generator + .add_entry_declaration(&ll_sym_name, ll_fnty, &attrs); + linkage = llvm::LLVMLinkage::LLVMExternalLinkage; + } let tfn = self.llvm_module.add_function(&ll_sym_name, ll_fnty); self.llvm_module.add_attributes(tfn, &attrs); tfn @@ -726,7 +734,12 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { match &rtcall { RtCall::VecCopy(ll_dst_value, ll_src_value, elt_mty) => { // Note, no retval from vec_copy. - let llfn = self.get_runtime_function(&rtcall); + let llfn = Self::get_runtime_function( + self.llvm_cx, + self.llvm_module, + &self.rtty_cx, + &rtcall, + ); let mut typarams: Vec<_> = self .get_rttydesc_ptrs(&[elt_mty.clone()]) .iter() @@ -737,7 +750,12 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { self.llvm_builder.call(llfn, &typarams) } RtCall::VecCmpEq(ll_dst_value, ll_src_value, elt_mty) => { - let llfn = self.get_runtime_function(&rtcall); + let llfn = Self::get_runtime_function( + self.llvm_cx, + self.llvm_module, + &self.rtty_cx, + &rtcall, + ); let mut typarams: Vec<_> = self .get_rttydesc_ptrs(&[elt_mty.clone()]) .iter() @@ -748,7 +766,12 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { self.llvm_builder.call(llfn, &typarams) } RtCall::VecEmpty(elt_mty) => { - let llfn = self.get_runtime_function(&rtcall); + let llfn = Self::get_runtime_function( + self.llvm_cx, + self.llvm_module, + &self.rtty_cx, + &rtcall, + ); let typarams: Vec<_> = self .get_rttydesc_ptrs(&[elt_mty.clone()]) .iter() @@ -757,12 +780,22 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { self.llvm_builder.call(llfn, &typarams) } RtCall::StrCmpEq(str1_ptr, str1_len, str2_ptr, str2_len) => { - let llfn = self.get_runtime_function(&rtcall); + let llfn = Self::get_runtime_function( + self.llvm_cx, + self.llvm_module, + &self.rtty_cx, + &rtcall, + ); let params = vec![*str1_ptr, *str1_len, *str2_ptr, *str2_len]; self.llvm_builder.call(llfn, ¶ms) } RtCall::StructCmpEq(ll_src1_value, ll_src2_value, s_mty) => { - let llfn = self.get_runtime_function(&rtcall); + let llfn = Self::get_runtime_function( + self.llvm_cx, + self.llvm_module, + &self.rtty_cx, + &rtcall, + ); let mut typarams: Vec<_> = self .get_rttydesc_ptrs(&[s_mty.clone()]) .iter() @@ -776,15 +809,32 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { } } - pub fn emit_rtcall_abort_raw(&self, val: u64) { - let thefn = self.get_runtime_function_by_name("abort"); - let param_ty = self.llvm_cx.int_type(64); + // TODO: consider better refactoring for this and other + // class-level methods, which used to be instance methods. + // These methods were converted to class-level because their code + // is resued by EntrypointGeenrator, which operates outside any + // ModuleContext, yet needs to add declarations of functions + // defined in other modules. + pub fn emit_rtcall_abort_raw( + llvm_cx: &'up llvm::Context, + llvm_builder: &llvm::Builder, + llvm_module: &'up llvm::Module, + rtty_cx: &RttyContext, + val: u64, + ) { + let thefn = Self::get_runtime_function_by_name(llvm_cx, llvm_module, rtty_cx, "abort"); + let param_ty = llvm_cx.int_type(64); let const_llval = llvm::Constant::int(param_ty, U256::from(val)); - self.llvm_builder.build_call_imm(thefn, &[const_llval]); - self.llvm_builder.build_unreachable(); + llvm_builder.build_call_imm(thefn, &[const_llval]); + llvm_builder.build_unreachable(); } - pub fn get_runtime_function(&self, rtcall: &RtCall) -> llvm::Function { + pub fn get_runtime_function( + llvm_cx: &'up llvm::Context, + llvm_module: &'up llvm::Module, + rtty_cx: &RttyContext, + rtcall: &RtCall, + ) -> llvm::Function { let name = match rtcall { RtCall::Abort(..) => "abort", RtCall::Deserialize(..) => "deserialize", @@ -795,21 +845,24 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { RtCall::StrCmpEq(..) => "str_cmp_eq", RtCall::StructCmpEq(..) => "struct_cmp_eq", }; - self.get_runtime_function_by_name(name) + Self::get_runtime_function_by_name(llvm_cx, llvm_module, rtty_cx, name) } - fn get_runtime_function_by_name(&self, rtcall_name: &str) -> llvm::Function { + fn get_runtime_function_by_name( + llvm_cx: &'up llvm::Context, + llvm_module: &'up llvm::Module, + rtty_cx: &RttyContext, + rtcall_name: &str, + ) -> llvm::Function { let fn_name = format!("move_rt_{rtcall_name}"); - let llmod = &self.llvm_module; - let llcx = &self.llvm_cx; - let llfn = llmod.get_named_function(&fn_name); + let llfn = llvm_module.get_named_function(&fn_name); if let Some(llfn) = llfn { llfn } else { let (llty, attrs) = match rtcall_name { "abort" => { - let ret_ty = llcx.void_type(); - let param_tys = &[llcx.int_type(64)]; + let ret_ty = llvm_cx.void_type(); + let param_tys = &[llvm_cx.int_type(64)]; let llty = llvm::FunctionType::new(ret_ty, param_tys); let attrs = vec![ (llvm::LLVMAttributeFunctionIndex, "noreturn", None), @@ -818,76 +871,81 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { (llty, attrs) } "deserialize" => { - let ret_ty = llcx.void_type(); - let ptr_ty = llcx.ptr_type(); - let int_ty = llcx.int_type(64); + let ret_ty = llvm_cx.void_type(); + let ptr_ty = llvm_cx.ptr_type(); + let int_ty = llvm_cx.int_type(64); let param_tys = &[ptr_ty, ptr_ty]; - let ll_sret = llcx.get_anonymous_struct_type(&[ - llcx.get_anonymous_struct_type(&[ptr_ty, int_ty]), + let ll_sret = llvm_cx.get_anonymous_struct_type(&[ + llvm_cx.get_anonymous_struct_type(&[ptr_ty, int_ty]), ptr_ty, - llcx.get_anonymous_struct_type(&[ptr_ty, int_ty, int_ty]), + llvm_cx.get_anonymous_struct_type(&[ptr_ty, int_ty, int_ty]), ]); let llty = llvm::FunctionType::new(ret_ty, param_tys); - let ll_fn = llmod.add_function(&fn_name, llty); - self.llvm_module - .add_type_attribute(ll_fn, 1, "sret", ll_sret); + let ll_fn = llvm_module.add_function(&fn_name, llty); + llvm_module.add_type_attribute(ll_fn, 1, "sret", ll_sret); return ll_fn; } "vec_destroy" => { // vec_destroy(type_ve: &MoveType, v: MoveUntypedVector) - let ret_ty = llcx.void_type(); - let tydesc_ty = llcx.ptr_type(); + let ret_ty = llvm_cx.void_type(); + let tydesc_ty = llvm_cx.ptr_type(); // The vector is passed by value, but the C ABI here passes structs by reference, // so it's another pointer. - let vector_ty = llcx.ptr_type(); + let vector_ty = llvm_cx.ptr_type(); let param_tys = &[tydesc_ty, vector_ty]; let llty = llvm::FunctionType::new(ret_ty, param_tys); - let attrs = self.mk_pattrs_for_move_type(1); + let attrs = Self::mk_pattrs_for_move_type(1); (llty, attrs) } "vec_copy" => { // vec_copy(type_ve: &MoveType, dstv: &mut MoveUntypedVector, srcv: &MoveUntypedVector) - let ret_ty = llcx.void_type(); - let tydesc_ty = llcx.ptr_type(); + let ret_ty = llvm_cx.void_type(); + let tydesc_ty = llvm_cx.ptr_type(); // The vectors are passed by value, but the C ABI here passes structs by reference, // so it's another pointer. - let vector_ty = llcx.ptr_type(); + let vector_ty = llvm_cx.ptr_type(); let param_tys = &[tydesc_ty, vector_ty, vector_ty]; let llty = llvm::FunctionType::new(ret_ty, param_tys); - let mut attrs = self.mk_pattrs_for_move_type(1); - attrs.extend(self.mk_pattrs_for_move_untyped_vec(2, true /* mut */)); - attrs.extend(self.mk_pattrs_for_move_untyped_vec(3, false /* !mut */)); + let mut attrs = Self::mk_pattrs_for_move_type(1); + attrs.extend(Self::mk_pattrs_for_move_untyped_vec(2, true /* mut */)); + attrs.extend(Self::mk_pattrs_for_move_untyped_vec( + 3, false, /* !mut */ + )); (llty, attrs) } "vec_cmp_eq" => { // vec_cmp_eq(type_ve: &MoveType, v1: &MoveUntypedVector, v2: &MoveUntypedVector) -> bool - let ret_ty = llcx.int_type(1); - let tydesc_ty = llcx.ptr_type(); + let ret_ty = llvm_cx.int_type(1); + let tydesc_ty = llvm_cx.ptr_type(); // The vectors are passed by value, but the C ABI here passes structs by reference, // so it's another pointer. - let vector_ty = llcx.ptr_type(); + let vector_ty = llvm_cx.ptr_type(); let param_tys = &[tydesc_ty, vector_ty, vector_ty]; let llty = llvm::FunctionType::new(ret_ty, param_tys); - let mut attrs = self.mk_pattrs_for_move_type(1); - attrs.extend(self.mk_pattrs_for_move_untyped_vec(2, false /* !mut */)); - attrs.extend(self.mk_pattrs_for_move_untyped_vec(3, false /* !mut */)); + let mut attrs = Self::mk_pattrs_for_move_type(1); + attrs.extend(Self::mk_pattrs_for_move_untyped_vec( + 2, false, /* !mut */ + )); + attrs.extend(Self::mk_pattrs_for_move_untyped_vec( + 3, false, /* !mut */ + )); (llty, attrs) } "vec_empty" => { // vec_empty(type_ve: &MoveType) -> MoveUntypedVector - let ret_ty = self.rtty_cx.get_llvm_type_for_move_native_vector(); - let tydesc_ty = llcx.ptr_type(); + let ret_ty = rtty_cx.get_llvm_type_for_move_native_vector(); + let tydesc_ty = llvm_cx.ptr_type(); let param_tys = &[tydesc_ty]; let llty = llvm::FunctionType::new(ret_ty, param_tys); - let attrs = self.mk_pattrs_for_move_type(1); + let attrs = Self::mk_pattrs_for_move_type(1); (llty, attrs) } "str_cmp_eq" => { // str_cmp_eq(str1_ptr: &AnyValue, str1_len: &AnyValue, // str2_ptr: &AnyValue, str1_len: &AnyValue) -> bool - let ret_ty = llcx.int_type(1); - let ptr_ty = llcx.ptr_type(); - let len_ty = llcx.int_type(64); + let ret_ty = llvm_cx.int_type(1); + let ptr_ty = llvm_cx.ptr_type(); + let len_ty = llvm_cx.int_type(64); let param_tys = &[ptr_ty, len_ty, ptr_ty, len_ty]; let llty = llvm::FunctionType::new(ret_ty, param_tys); let attrs = vec![ @@ -900,12 +958,12 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { } "struct_cmp_eq" => { // struct_cmp_eq(type_ve: &MoveType, s1: &AnyValue, s2: &AnyValue) -> bool; - let ret_ty = llcx.int_type(1); - let tydesc_ty = llcx.ptr_type(); - let anyval_ty = llcx.ptr_type(); + let ret_ty = llvm_cx.int_type(1); + let tydesc_ty = llvm_cx.ptr_type(); + let anyval_ty = llvm_cx.ptr_type(); let param_tys = &[tydesc_ty, anyval_ty, anyval_ty]; let llty = llvm::FunctionType::new(ret_ty, param_tys); - let mut attrs = self.mk_pattrs_for_move_type(1); + let mut attrs = Self::mk_pattrs_for_move_type(1); attrs.push((2, "readonly", None)); attrs.push((2, "nonnull", None)); attrs.push((3, "readonly", None)); @@ -915,14 +973,13 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { n => panic!("unknown runtime function {n}"), }; - let ll_fn = llmod.add_function(&fn_name, llty); - llmod.add_attributes(ll_fn, &attrs); + let ll_fn = llvm_module.add_function(&fn_name, llty); + llvm_module.add_attributes(ll_fn, &attrs); ll_fn } } fn mk_pattrs_for_move_type( - &self, attr_idx: llvm::LLVMAttributeIndex, ) -> Vec<(llvm::LLVMAttributeIndex, &'static str, Option)> { assert!( @@ -937,7 +994,6 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { } fn mk_pattrs_for_move_untyped_vec( - &self, attr_idx: llvm::LLVMAttributeIndex, mutable: bool, ) -> Vec<(llvm::LLVMAttributeIndex, &'static str, Option)> { @@ -958,379 +1014,4 @@ impl<'mm, 'up> ModuleContext<'mm, 'up> { } attrs } - - // This function extracts an entry function actual arguments from - // instruction_data byte array, containing values of actual - // arguments in sequential order without gaps. - fn emit_entry_arguments( - &self, - fn_env: &mm::FunctionEnv, - instruction_data: &llvm::AnyValue, - index: &llvm::AnyValue, - ) -> Vec { - if fn_env.get_parameter_count() == 0 { - return vec![]; - } - let llcx = self.llvm_cx; - let i64_ty = llcx.int_type(64); - let byte_ty = llcx.int_type(8); - let mut index_value = self.llvm_builder.load(*index, i64_ty, "index_value"); - let mut args = vec![]; - for ty in fn_env.get_parameter_types() { - let mut arg = self.llvm_builder.build_address_with_indices( - byte_ty, - *instruction_data, - &[index_value], - "arg_ptr", - ); - match ty { - mty::Type::Primitive(mty::PrimitiveType::Bool) => { - arg = self.llvm_builder.load(arg, llcx.int_type(1), "arg"); - index_value = self.advance_offset_by_increment( - *index, - llvm::Constant::int(i64_ty, U256::one()).as_any_value(), - ); - } - mty::Type::Reference(_, ty) => { - index_value = self.advance_offset_by_increment( - *index, - llvm::Constant::int(i64_ty, U256::from(ty.get_bitwidth() / 8)) - .as_any_value(), - ); - } - // This code is generated when control flow of the - // function already has branches. LLVM seems to allow - // stack allocations only in the entry basic - // block. Therefore the layout of serialized vector - // must have a valid vector structure, which can be - // referenced for loading arguments from. This code - // assumes that the vector is represented as - // {data_pointer, length, capacity} triple and data - // follows this triple immediately. We patch the - // data_pointer location in memory to contain the - // address of the actual vector's data. The length and - // the capaciity are assumed to have the same value. - mty::Type::Vector(ty) => { - let vec_ty = self.rtty_cx.get_llvm_type_for_move_native_vector(); - let vec_ptr = self.llvm_builder.getelementptr( - arg, - &vec_ty.as_struct_type(), - 0, - "vec_ptr", - ); - let vec_len = self.llvm_builder.getelementptr( - arg, - &vec_ty.as_struct_type(), - 1, - "vec_len", - ); - index_value = self.advance_offset_by_increment( - *index, - llvm::Constant::int(i64_ty, U256::from(MOVE_UNTYPED_VEC_DESC_SIZE)) - .as_any_value(), - ); - let vec_data = self.llvm_builder.build_address_with_indices( - byte_ty, - *instruction_data, - &[index_value], - "vec_data", - ); - self.llvm_builder.store(vec_data, vec_ptr); - arg = self.llvm_builder.load(arg, vec_ty, "vec_arg"); - let vec_element_size = - llvm::Constant::int(i64_ty, U256::from(ty.get_bitwidth() / 8)) - .as_any_value(); - let vec_len = self.llvm_builder.load(vec_len, i64_ty, "vec_len_loaded"); - let vec_data_len = self.llvm_builder.build_binop( - llvm_sys::LLVMOpcode::LLVMMul, - vec_len, - vec_element_size, - "vec_data_len", - ); - index_value = self.advance_offset_by_increment(*index, vec_data_len); - } - mty::Type::Struct(mid, sid, _) => { - arg = self.llvm_builder.load( - arg, - self.to_llvm_type(&ty, &[]).unwrap(), - "str_arg", - ); - let m_env = &self.env; - let g_env = &m_env.env; - let s_env = g_env.get_module(mid).into_struct(sid); - // FIXME! This computes incorrect width when - // fields of a struct are structs themselves. - // get_bitwidth() on Type::Struct currently - // returns 0, because model creates Type::Struct - // values with an empty vector for field - // types. Only instantiations of structs include - // actual types of fields in their corresponding - // Type::Struct values. - let width = s_env - .get_fields() - .map(|f_env| f_env.get_type()) - .fold(0, |acc, ty| acc + ty.get_bitwidth()); - let size = llvm::Constant::int(i64_ty, U256::from(width / 8)).as_any_value(); - index_value = self.advance_offset_by_increment(*index, size); - } - _ => { - arg = self - .llvm_builder - .load(arg, self.to_llvm_type(&ty, &[]).unwrap(), "arg"); - let size = llvm::Constant::int(i64_ty, U256::from(ty.get_bitwidth() / 8)) - .as_any_value(); - index_value = self.advance_offset_by_increment(*index, size); - } - } - args.push(arg); - } - args - } - - fn generate_global_str_slice(&self, s: &str) -> llvm::Global { - let llcx = &self.llvm_cx; - - // Create an LLVM global for the string. - let str_literal_init = llcx.const_int_array::(s.as_bytes()).as_const(); - let str_literal = self - .llvm_module - .add_global2(str_literal_init.llvm_type(), "str_literal"); - str_literal.set_constant(); - str_literal.set_alignment(1); - str_literal.set_unnamed_addr(); - str_literal.set_linkage(llvm::LLVMLinkage::LLVMPrivateLinkage); - str_literal.set_initializer(str_literal_init); - - // Create an LLVM global for the slice, which is a struct with two fields: - // - pointer to the string literal - // - integer length of the string literal - let slice_len = s.len(); - let slice_init = llcx.const_struct(&[ - str_literal.ptr(), - llvm::Constant::int(llcx.int_type(64), U256::from(slice_len as u128)), - ]); - let slice = self - .llvm_module - .add_global2(slice_init.llvm_type(), "str_slice"); - slice.set_constant(); - slice.set_alignment(8); - slice.set_unnamed_addr(); - slice.set_linkage(llvm::LLVMLinkage::LLVMPrivateLinkage); - slice.set_initializer(slice_init); - - slice - } - - fn emit_rtcall_deserialize( - &self, - input: llvm::AnyValue, - ) -> (llvm::AnyValue, llvm::AnyValue, llvm::AnyValue) { - let llcx = self.llvm_cx; - let ll_sret = llcx.get_anonymous_struct_type(&[ - self.rtty_cx.get_llvm_type_for_slice(), - llcx.ptr_type(), - self.rtty_cx.get_llvm_type_for_move_native_vector(), - ]); - let params = self - .llvm_builder - .build_alloca(ll_sret, "params") - .as_any_value(); - let args = vec![params, input]; - let ll_fn_deserialize = self.get_runtime_function(&RtCall::Deserialize(params, input)); - self.llvm_builder.call(ll_fn_deserialize, &args); - - let insn_data = self.llvm_builder.getelementptr( - params, - &ll_sret.as_struct_type(), - 0, - "instruction_data", - ); - let program_id = - self.llvm_builder - .getelementptr(params, &ll_sret.as_struct_type(), 1, "program_id"); - let accounts = - self.llvm_builder - .getelementptr(params, &ll_sret.as_struct_type(), 2, "accounts"); - (insn_data, program_id, accounts) - } - - fn advance_offset_by_increment( - &self, - offset: llvm::AnyValue, - increment: llvm::AnyValue, - ) -> llvm::AnyValue { - let offset_loaded = - self.llvm_builder - .load(offset, self.llvm_cx.int_type(64), "offset_loaded"); - let offset_loaded = self.llvm_builder.build_binop( - llvm_sys::LLVMOpcode::LLVMAdd, - offset_loaded, - increment, - "offset_loaded", - ); - self.llvm_builder.store(offset_loaded, offset); - offset_loaded - } - - /** - * Generate solana entrypoint functon code. This function - * recieves serialized input paramteres from the VM. It calls - * native function `deserialize` to decode the parameters into - * corresponding data structures. The function `deserialize` - * returns a triple consiting of - * - instruction_data -- a byte array, - * - program_id -- SolanaPubkey, and - * - accounts -- a vector of SolanaAccountInfo items. - * - * To select one from possibly several entry functions defined in - * the module, the entrypoint function expects the name of the - * requested entry function to be passed in instruction_data byte - * array. The logic in solana entrypoint iteratively compares the - * string slice passed in instruction_data to every entry function - * symbol of the module. Once a matching entry function is found, - * it is called, and its return value is used as the exit code for - * the program. - */ - fn emit_solana_entrypoint(&mut self) { - debug!("unit test function {:?}", self.options.unit_test_function); - let unit_test_function = self.options.unit_test_function.clone().unwrap_or_default(); - let entry_functions: Vec<_> = self - .env - .get_functions() - .filter(|fn_env| { - fn_env.is_entry() - || fn_env.get_full_name_str().replace("::", "__") == unit_test_function - }) - .collect(); - - // Do not generate solana entrypoint if module doesn't contain any entry functions. - if entry_functions.is_empty() { - return; - } - - let ll_fn_solana_entrypoint = { - let ll_fnty = { - let ll_rty = self.llvm_cx.int_type(64_usize); - let ll_param_tys = vec![self.llvm_cx.ptr_type()]; - llvm::FunctionType::new(ll_rty, &ll_param_tys) - }; - self.llvm_module.add_function("main", ll_fnty) - }; - let entry_block = ll_fn_solana_entrypoint.append_basic_block("entry"); - self.llvm_builder.position_at_end(entry_block); - let retval = self - .llvm_builder - .build_alloca(self.llvm_cx.int_type(64), "retval") - .as_any_value(); - let offset = self - .llvm_builder - .build_alloca(self.llvm_cx.int_type(64), "offset"); - self.llvm_builder.store_const( - llvm::Constant::int(self.llvm_cx.int_type(64), U256::zero()), - offset, - ); - - // Get inputs from the VM into proper data structures. - let (insn_data, _program_id, _accounts) = - self.emit_rtcall_deserialize(ll_fn_solana_entrypoint.get_param(0).as_any_value()); - // Make a str slice from instruction_data byte array returned - // from a call to deserialize - let str_slice_type = self.rtty_cx.get_llvm_type_for_slice(); - let insn_data_ptr = self.llvm_builder.getelementptr( - insn_data, - &str_slice_type.as_struct_type(), - 0, - "insn_data_ptr", - ); - let insn_data_ptr = self.llvm_builder.load( - insn_data_ptr, - self.llvm_cx.ptr_type(), - "insn_data_ptr_loaded", - ); - let offset_value = self.advance_offset_by_increment( - offset.as_any_value(), - llvm::Constant::int(self.llvm_cx.int_type(64), U256::from(8u64)).as_any_value(), - ); - let entry_slice_ptr = self.llvm_builder.build_address_with_indices( - self.llvm_cx.int_type(8), - insn_data_ptr, - &[offset_value], - "entry_slice_ptr", - ); - let entry_slice_len = - self.llvm_builder - .load(insn_data_ptr, self.llvm_cx.int_type(64), "entry_slice_len"); - let _offset_value = - self.advance_offset_by_increment(offset.as_any_value(), entry_slice_len); - - let curr_bb = self.llvm_builder.get_insert_block(); - let exit_bb = ll_fn_solana_entrypoint.insert_basic_block_after(curr_bb, "exit_bb"); - // For every entry function defined in the module compare its - // name to the name passed in the instruction_data, and call - // the matching entry function. - for fun in entry_functions { - let entry = self.generate_global_str_slice(fun.llvm_symbol_name(&[]).as_str()); - - let func_name_ptr = self.llvm_builder.getelementptr( - entry.as_any_value(), - &str_slice_type.as_struct_type(), - 0, - "entry_func_ptr", - ); - let func_name_ptr = self.llvm_builder.load( - func_name_ptr, - self.llvm_cx.ptr_type(), - "entry_func_ptr_loaded", - ); - let func_name_len = self.llvm_builder.getelementptr( - entry.as_any_value(), - &str_slice_type.as_struct_type(), - 1, - "entry_func_len", - ); - let func_name_len = self.llvm_builder.load( - func_name_len, - self.llvm_cx.int_type(64), - "entry_func_len_loaded", - ); - let condition = self.emit_rtcall_with_retval(RtCall::StrCmpEq( - entry_slice_ptr, - entry_slice_len, - func_name_ptr, - func_name_len, - )); - - let curr_bb = self.llvm_builder.get_insert_block(); - let then_bb = ll_fn_solana_entrypoint.insert_basic_block_after(curr_bb, "then_bb"); - let else_bb = ll_fn_solana_entrypoint.insert_basic_block_after(then_bb, "else_bb"); - self.llvm_builder.build_cond_br(condition, then_bb, else_bb); - self.llvm_builder.position_at_end(then_bb); - let fn_name = fun.llvm_symbol_name(&[]); - let ll_fun = self.fn_decls.get(&fn_name).unwrap(); - let params = self.emit_entry_arguments(&fun, &insn_data_ptr, &offset.as_any_value()); - let ret = self.llvm_builder.call(*ll_fun, ¶ms); - if fun.get_return_count() > 0 { - self.llvm_builder.store(ret, retval); - } else { - self.llvm_builder.store( - llvm::Constant::int(self.llvm_cx.int_type(64), U256::zero()).as_any_value(), - retval, - ); - } - self.llvm_builder.build_br(exit_bb); - self.llvm_builder.position_at_end(else_bb); - } - // Abort if no entry function matched the requested name. - self.emit_rtcall_abort_raw(move_core_types::vm_status::StatusCode::EXECUTE_ENTRY_FUNCTION_CALLED_ON_NON_ENTRY_FUNCTION as u64); - self.llvm_builder.position_at_end(exit_bb); - let ret = self - .llvm_builder - .load(retval, self.llvm_cx.int_type(64), "exit_code"); - self.llvm_builder.build_return(ret); - ll_fn_solana_entrypoint.verify(); - - if log_enabled!(target: "entry_point", Level::Debug) { - self.llvm_module.dump(); - } - } } diff --git a/language/solana/move-to-solana/src/stackless/translate.rs b/language/solana/move-to-solana/src/stackless/translate.rs index a8bb7dcf81..02f62a7971 100644 --- a/language/solana/move-to-solana/src/stackless/translate.rs +++ b/language/solana/move-to-solana/src/stackless/translate.rs @@ -32,7 +32,10 @@ use crate::{ options::Options, - stackless::{extensions::*, llvm, module_context::ModuleContext, rttydesc::RttyContext}, + stackless::{ + entrypoint::EntrypointGenerator, extensions::*, llvm, module_context::ModuleContext, + rttydesc::RttyContext, + }, }; use log::debug; use move_core_types::{account_address, u256::U256, vm_status::StatusCode::ARITHMETIC_ERROR}; @@ -79,7 +82,7 @@ impl TargetPlatform { } pub struct GlobalContext<'up> { - env: &'up mm::GlobalEnv, + pub env: &'up mm::GlobalEnv, pub llvm_cx: llvm::Context, target: TargetPlatform, target_machine: &'up llvm::TargetMachine, @@ -125,15 +128,17 @@ impl<'up> GlobalContext<'up> { } } - pub fn create_module_context<'this>( + pub fn create_module_context<'this: 'up>( &'this self, id: mm::ModuleId, llmod: &'this llvm::Module, + entrypoint_generator: &'this EntrypointGenerator<'this, 'up>, options: &'this Options, ) -> ModuleContext<'up, 'this> { let rtty_cx = RttyContext::new(self.env, &self.llvm_cx, llmod); ModuleContext { env: self.env.get_module(id), + entrypoint_generator, llvm_cx: &self.llvm_cx, llvm_module: llmod, llvm_builder: self.llvm_cx.create_builder(), @@ -566,8 +571,14 @@ impl<'mm, 'up> FunctionContext<'mm, 'up> { // Generate the conditional branch and call to abort. builder.build_cond_br(cond_reg, then_bb, join_bb); builder.position_at_end(then_bb); - self.module_cx - .emit_rtcall_abort_raw(ARITHMETIC_ERROR as u64); + + ModuleContext::emit_rtcall_abort_raw( + self.module_cx.llvm_cx, + &self.module_cx.llvm_builder, + self.module_cx.llvm_module, + &self.module_cx.rtty_cx, + ARITHMETIC_ERROR as u64, + ); builder.position_at_end(join_bb); } @@ -1732,7 +1743,12 @@ impl<'mm, 'up> FunctionContext<'mm, 'up> { fn emit_rtcall(&self, rtcall: RtCall) { match &rtcall { RtCall::Abort(local_idx) => { - let llfn = self.module_cx.get_runtime_function(&rtcall); + let llfn = ModuleContext::get_runtime_function( + self.module_cx.llvm_cx, + self.module_cx.llvm_module, + &self.module_cx.rtty_cx, + &rtcall, + ); let local_llval = self.locals[*local_idx].llval; let local_llty = self.locals[*local_idx].llty; self.module_cx.llvm_builder.load_call_store( @@ -1743,7 +1759,12 @@ impl<'mm, 'up> FunctionContext<'mm, 'up> { self.module_cx.llvm_builder.build_unreachable(); } RtCall::VecDestroy(local_idx, elt_mty) => { - let llfn = self.module_cx.get_runtime_function(&rtcall); + let llfn = ModuleContext::get_runtime_function( + self.module_cx.llvm_cx, + self.module_cx.llvm_module, + &self.module_cx.rtty_cx, + &rtcall, + ); let typarams = self.module_cx.get_rttydesc_ptrs(&[elt_mty.clone()]); let typarams = typarams.into_iter().map(|llval| llval.as_any_value()); // The C ABI passes the by-val-vector as a pointer. diff --git a/language/tools/move-mv-llvm-compiler/src/main.rs b/language/tools/move-mv-llvm-compiler/src/main.rs index f5e13742b4..8bdbe7efe4 100644 --- a/language/tools/move-mv-llvm-compiler/src/main.rs +++ b/language/tools/move-mv-llvm-compiler/src/main.rs @@ -12,6 +12,7 @@ use clap::Parser; use cli::{absolute_existing_file, absolute_new_file, Args}; use codespan_reporting::{diagnostic::Severity, term::termcolor::Buffer}; use llvm_sys::prelude::LLVMModuleRef; +use log::Level; use move_binary_format::{ binary_views::BinaryIndexedView, file_format::{CompiledModule, CompiledScript}, @@ -28,9 +29,10 @@ use move_model::{ }; use move_symbol_pool::Symbol as SymbolPool; use package::build_dependency; -use std::{fs, path::Path}; +use std::{fs, io::Write, path::Path}; fn main() -> anyhow::Result<()> { + initialize_logger(); let args = Args::parse(); if args.llvm_ir && args.obj { @@ -232,21 +234,25 @@ fn main() -> anyhow::Result<()> { println!("{}", modname); } } + let options = MoveToSolanaOptions { + gen_dot_cfg: args.gen_dot_cfg.clone(), + dot_file_path: args.dot_file_path.clone(), + test_signers: args.test_signers.clone(), + ..MoveToSolanaOptions::default() + }; + let entry_llmod = global_cx.llvm_cx.create_module("solana_entrypoint"); + let entrypoint_generator = + EntrypointGenerator::new(&global_cx, &entry_llmod, &llmachine, &options); for mod_id in modules { let module = global_env.get_module(mod_id); let modname = module.llvm_module_name(); let mut llmod = global_cx.llvm_cx.create_module(&modname); - let options = MoveToSolanaOptions { - gen_dot_cfg: args.gen_dot_cfg.clone(), - dot_file_path: args.dot_file_path.clone(), - test_signers: args.test_signers.clone(), - ..MoveToSolanaOptions::default() - }; if args.diagnostics { let disasm = module.disassemble(); println!("Module {} bytecode {}", modname, disasm); } - let mod_cx = global_cx.create_module_context(mod_id, &llmod, &options); + let mod_cx = + global_cx.create_module_context(mod_id, &llmod, &entrypoint_generator, &options); mod_cx.translate(); if args.diagnostics { println!("Module {} Solana llvm ir", modname); @@ -281,14 +287,47 @@ fn main() -> anyhow::Result<()> { break; } } + if entrypoint_generator.has_entries() { + let path = Path::new(&output_file_path); + entrypoint_generator.write_object_file(path.to_path_buf().parent().unwrap())?; + } // NB: context must outlive llvm module // fixme this should be handled with lifetimes + drop(entry_llmod); drop(global_cx); }; Ok(()) } +fn initialize_logger() { + static LOGGER_INIT: std::sync::Once = std::sync::Once::new(); + LOGGER_INIT.call_once(|| { + use env_logger::fmt::Color; + env_logger::Builder::from_default_env() + .format(|formatter, record| { + let level = record.level(); + let mut style = formatter.style(); + match record.level() { + Level::Error => style.set_color(Color::Red), + Level::Warn => style.set_color(Color::Yellow), + Level::Info => style.set_color(Color::Green), + Level::Debug => style.set_color(Color::Blue), + Level::Trace => style.set_color(Color::Cyan), + }; + writeln!( + formatter, + "[{} {}:{}] {}", + style.value(level), + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + }) + .init(); + }); +} + fn llvm_write_to_file( module: LLVMModuleRef, llvm_ir: bool, diff --git a/language/tools/move-mv-llvm-compiler/tests/rbpf-tests.rs b/language/tools/move-mv-llvm-compiler/tests/rbpf-tests.rs index b051d38e49..ac3663d0fa 100644 --- a/language/tools/move-mv-llvm-compiler/tests/rbpf-tests.rs +++ b/language/tools/move-mv-llvm-compiler/tests/rbpf-tests.rs @@ -284,6 +284,14 @@ fn link_object_files( cmd.arg(&cu.object_file()); } + let solana_entrypoint = test_plan + .build_dir + .join("modules") + .join("solana_entrypoint.o"); + if solana_entrypoint.exists() { + cmd.arg(solana_entrypoint); + } + cmd.arg(&runtime.archive_file); let output = cmd.output()?; diff --git a/language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.json b/language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.json new file mode 100644 index 0000000000..25cd684790 --- /dev/null +++ b/language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.json @@ -0,0 +1,7 @@ +{ + "program_id": "DozgQiYtGbdyniV2T74xMdmjZJvYDzoRFFqw7UR5MwPK", + "accounts": [], + "instruction_data": [ + 6, 0, 0, 0, 0, 0, 0, 0, + 66, 95, 95, 102, 111, 111] +} diff --git a/language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.move b/language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.move new file mode 100644 index 0000000000..d84b71b4f8 --- /dev/null +++ b/language/tools/move-mv-llvm-compiler/tests/rbpf-tests/entry-point08.move @@ -0,0 +1,26 @@ +// input entry-point08.json +// log 17 + +module 0x10::debug { + native public fun print(x: &T); +} + +module 0xa000::A { + use 0x10::debug; + + public entry fun bar(): u64 { + let rv = 123; + debug::print(&rv); + rv + } +} + +module 0xb000::B { + use 0x10::debug; + + public entry fun foo(): u64 { + let rv = 17; + debug::print(&rv); + rv + } +}