Skip to content

Commit c2f849d

Browse files
joskeclaude
andcommitted
feat: implement Tier 1 aptos-framework natives (event, create_signer, transaction_context, aggregator_v2)
Implement ~30 native functions across 4 aptos-framework modules using custom shim modules (full AptosFramework dep has too many compilation failures for now). - event: 2 no-op guest exports (fire-and-forget) - create_signer: translator interception (load+insertvalue+store to handle address→signer wrapping without RV64 ABI mismatch) - transaction_context: 9 natives with mixed translator interceptions (address-returning functions) + guest exports + 5 host bridges - aggregator_v2: 9 pure guest exports with u64/u128 type dispatch Also makes LLVM module/function verification non-fatal (skips modules that fail) and adds catch_unwind around module translation to handle panics from unsupported constructs gracefully. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d84a793 commit c2f849d

10 files changed

Lines changed: 848 additions & 20 deletions

File tree

crates/move-to-polka/src/lib.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,26 @@ pub fn compile(global_env: &GlobalEnv, options: &Options) -> anyhow::Result<()>
279279
debug!("Generating code for module {modname}");
280280
let llmod = global_cx.llvm_cx.create_module(&modname);
281281
let module_source_path = module.get_source_path().to_str().expect("utf-8");
282-
let mod_cx =
283-
&mut global_cx.create_module_context(mod_id, &llmod, options, module_source_path);
284-
mod_cx.translate(&mut exports);
282+
283+
// Wrap translation + codegen in catch_unwind so that modules with
284+
// unsupported constructs (complex enums, etc.) are skipped gracefully
285+
// instead of aborting the entire compilation.
286+
let translate_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
287+
let mod_cx =
288+
&mut global_cx.create_module_context(mod_id, &llmod, options, module_source_path);
289+
mod_cx.translate(&mut exports);
290+
}));
291+
if let Err(e) = translate_result {
292+
let msg = if let Some(s) = e.downcast_ref::<String>() {
293+
s.clone()
294+
} else if let Some(s) = e.downcast_ref::<&str>() {
295+
s.to_string()
296+
} else {
297+
"unknown panic".to_string()
298+
};
299+
eprintln!("WARNING: module {modname} translation failed: {msg}, skipping");
300+
continue;
301+
}
285302

286303
let mut out_path = out_path.join(&modname);
287304
out_path.set_extension(&options.output_file_extension);
@@ -301,7 +318,12 @@ pub fn compile(global_env: &GlobalEnv, options: &Options) -> anyhow::Result<()>
301318
if options.compile {
302319
output_file = options.output.clone();
303320
}
304-
write_object_file(llmod, &llmachine, &output_file)?;
321+
let verified = write_object_file(llmod, &llmachine, &output_file)?;
322+
if !verified {
323+
// Module failed LLVM verification — skip it and continue
324+
// with remaining modules.
325+
continue;
326+
}
305327
}
306328
if !(options.compile || options.llvm_ir) {
307329
objects.push(Path::new(&output_file).to_path_buf());

crates/move-to-polka/src/linker.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,10 +787,53 @@ fn define_host_functions(linker: &mut MoveProgramLinker) -> Result<(), anyhow::E
787787
},
788788
)?;
789789

790-
// --- type_info host functions ---
790+
// --- type_info / transaction_context host functions ---
791791

792792
linker.define_typed("chain_id_internal", || -> u32 { 4u32 })?;
793793

794+
linker.define_typed("txn_hash", |caller: Caller<Runtime>| {
795+
let runtime = caller.user_data;
796+
let instance = caller.instance;
797+
// Return a deterministic 32-byte hash for testing
798+
let hash = vec![
799+
0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45,
800+
0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01,
801+
0x23, 0x45, 0x67, 0x89,
802+
];
803+
let addr = to_move_byte_vector(instance, &mut runtime.allocator, hash)?;
804+
Result::<u32, ProgramError>::Ok(addr)
805+
})?;
806+
807+
linker.define_typed("max_gas_amount", || -> u64 { 1_000_000u64 })?;
808+
809+
linker.define_typed("gas_unit_price", || -> u64 { 100u64 })?;
810+
811+
// Test sender address: 32 bytes (Move address size)
812+
const TEST_SENDER: &[u8] = &[
813+
0xa0, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
814+
0, 0, 0, 0,
815+
];
816+
817+
linker.define_typed("sender_address", |caller: Caller<Runtime>, out_ptr: u32| {
818+
let instance = caller.instance;
819+
instance.write_memory(out_ptr, TEST_SENDER)?;
820+
Result::<(), ProgramError>::Ok(())
821+
})?;
822+
823+
let unique_addr_counter = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1));
824+
linker.define_typed(
825+
"generate_unique_addr",
826+
move |caller: Caller<Runtime>, out_ptr: u32| {
827+
let instance = caller.instance;
828+
let counter = unique_addr_counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
829+
// Generate unique address: counter bytes in first 8 bytes, rest zeroed
830+
let mut addr = [0u8; 32];
831+
addr[..8].copy_from_slice(&counter.to_le_bytes());
832+
instance.write_memory(out_ptr, &addr)?;
833+
Result::<(), ProgramError>::Ok(())
834+
},
835+
)?;
836+
794837
// --- ristretto255 host functions ---
795838

796839
let point_store = crypto::new_point_store();

crates/move-to-polka/src/stackless/dwarf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ impl<'up> DIBuilder<'up> {
600600
let module_di_info = print_module_to_str(&module_di);
601601
debug!(target: "dwarf", "DIBuilder bof DI starting at next line and until line starting with !!!\n{module_di_info}\n!!!\n");
602602

603-
module.verify();
603+
assert!(module.verify(), "DWARF DI module verification failed");
604604

605605
DIBuilder(Some(builder_core))
606606
} else {

crates/move-to-polka/src/stackless/llvm.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
//! - Hides weirdly mutable array pointers.
1414
//! - Provides high-level instruction builders compatible with the stackless bytecode model.
1515
16-
use libc::abort;
1716
use llvm_sys::{
1817
core::*, prelude::*, target::*, target_machine::*, LLVMIntPredicate::LLVMIntEQ, LLVMOpcode,
1918
LLVMUnnamedAddr,
@@ -464,7 +463,7 @@ impl Module {
464463
}
465464
}
466465

467-
pub fn verify(&self) {
466+
pub fn verify(&self) -> bool {
468467
use llvm_sys::analysis::*;
469468
unsafe {
470469
let name = &self.get_module_id();
@@ -476,12 +475,16 @@ impl Module {
476475
ptr::null_mut(),
477476
) == 1
478477
{
479-
println!("\n{} module verification failed\n", &self.get_module_id());
478+
eprintln!(
479+
"WARNING: {} module verification failed, skipping",
480+
&self.get_module_id()
481+
);
480482
let module_info = &self.print_to_str();
481483
debug!(target: "module", "Module content:\n{module_info}\n");
482-
abort();
484+
return false;
483485
}
484486
}
487+
true
485488
}
486489

487490
pub fn set_data_layout(&self, machine: &TargetMachine) {
@@ -1433,7 +1436,7 @@ impl Function {
14331436
unsafe { Type(LLVMGetReturnType(LLVMGlobalGetValueType(self.0))) }
14341437
}
14351438

1436-
pub fn verify(&self, module_cx: &ModuleContext<'_, '_>) {
1439+
pub fn verify(&self, module_cx: &ModuleContext<'_, '_>) -> bool {
14371440
use llvm_sys::analysis::*;
14381441
let module_info = module_cx.llvm_module.print_to_str();
14391442
debug!(target: "verify function", "Module content:");
@@ -1442,10 +1445,14 @@ impl Function {
14421445
debug!(target: "verify function", "------------------------------");
14431446
unsafe {
14441447
if LLVMVerifyFunction(self.0, LLVMVerifierFailureAction::LLVMPrintMessageAction) == 1 {
1445-
println!("{} function verifiction failed", &self.get_name());
1446-
abort();
1448+
eprintln!(
1449+
"WARNING: {} function verification failed, will skip module",
1450+
&self.get_name()
1451+
);
1452+
return false;
14471453
}
14481454
}
1455+
true
14491456
}
14501457
}
14511458

crates/move-to-polka/src/stackless/module_context.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<'mm: 'up, 'up> ModuleContext<'mm, 'up> {
108108
.print_log_unresoled_types(UnresolvedPrintLogLevel::Warning);
109109
self.llvm_di_builder.finalize();
110110
self.llvm_module.finalize(); // this generates the inline ASM for the polkavm sections
111-
self.llvm_module.verify();
111+
// Note: verification is done in write_object_file, which can skip modules that fail.
112112
}
113113

114114
/// Generate LLVM IR struct declarations for all Move structures.
@@ -717,6 +717,15 @@ impl<'mm: 'up, 'up> ModuleContext<'mm, 'up> {
717717
debug!("Skipping function {name} as it is not an entry function");
718718
continue;
719719
}
720+
// The call selector only passes a signer pointer.
721+
// Skip entry functions that take extra parameters beyond the signer.
722+
if func.count_params() != 1 {
723+
debug!(
724+
"Skipping entry function {name} from call selector: expects {} params, call selector only provides signer",
725+
func.count_params()
726+
);
727+
continue;
728+
}
720729
debug!("Adding call selector function {name} to exports");
721730
let mut keccak = Keccak::v256();
722731
keccak.update(name.as_bytes());
@@ -795,6 +804,19 @@ impl<'mm: 'up, 'up> ModuleContext<'mm, 'up> {
795804
debug!("Declare native function {}", fn_env.get_full_name_str());
796805
assert!(fn_env.is_native());
797806

807+
// Skip declaring functions that are intercepted in translate_native_fun_call.
808+
// These have ABI mismatches (32-byte address/signer by value) and are either
809+
// handled inline (create_signer) or declared with correct ABI at the call site.
810+
let full_name = fn_env.get_full_name_str();
811+
if full_name.ends_with("::create_signer")
812+
|| full_name.ends_with("::sender_internal")
813+
|| full_name.ends_with("::gas_payer_internal")
814+
|| full_name.ends_with("::generate_unique_address")
815+
{
816+
debug!("Skipping declaration for intercepted native: {full_name}");
817+
return;
818+
}
819+
798820
let llcx = &self.llvm_cx;
799821
let ll_native_sym_name = fn_env.llvm_native_fn_symbol_name();
800822
let ll_fn = {

crates/move-to-polka/src/stackless/translate.rs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,9 @@ impl<'mm, 'up> FunctionContext<'mm, 'up> {
415415
self.module_cx
416416
.llvm_di_builder
417417
.finalize_function(&self, di_func);
418-
ll_fn.verify(self.module_cx);
418+
// Non-fatal: function verification errors will be caught at module level
419+
// and the module will be skipped.
420+
let _ = ll_fn.verify(self.module_cx);
419421
}
420422

421423
fn translate_instruction(&mut self, instr: &sbc::Bytecode) {
@@ -1891,11 +1893,15 @@ impl<'mm, 'up> FunctionContext<'mm, 'up> {
18911893
src: &[mast::TempIndex],
18921894
_instr: &sbc::Bytecode,
18931895
) {
1894-
// Special-case: object::exists_at<T>(addr) → reuse Exists rtcall
1896+
// Special-case interceptions for native functions that can't go through
1897+
// the normal native call path (ABI mismatches, pure in-memory ops, etc.)
18951898
{
18961899
let global_env = &self.env.module_env.env;
18971900
let fn_env = global_env.get_function(fun_id.qualified(mod_id));
1898-
if fn_env.get_full_name_str().ends_with("::exists_at") {
1901+
let full_name = fn_env.get_full_name_str();
1902+
1903+
// object::exists_at<T>(addr) → reuse Exists rtcall
1904+
if full_name.ends_with("::exists_at") {
18991905
let types = mty::Type::instantiate_vec(types.to_vec(), self.type_params);
19001906
assert_eq!(types.len(), 1);
19011907
assert_eq!(src.len(), 1);
@@ -1904,6 +1910,54 @@ impl<'mm, 'up> FunctionContext<'mm, 'up> {
19041910
self.emit_rtcall(RtCall::Exists(src0_reg, types[0].clone()), dst, _instr);
19051911
return;
19061912
}
1913+
1914+
// create_signer(addr: address) -> signer
1915+
// Both are 32 bytes but different LLVM types: address=[32 x i8], signer={[32 x i8]}.
1916+
// RV64 ABI mismatch prevents passing/returning 32-byte aggregates by value.
1917+
// Since signer is a transparent wrapper around address, just load+wrap+store.
1918+
if full_name.ends_with("::create_signer") {
1919+
assert_eq!(src.len(), 1);
1920+
assert_eq!(dst.len(), 1);
1921+
let builder = &self.module_cx.llvm_builder;
1922+
let addr_val =
1923+
builder.load_alloca(self.locals[src[0]].llval, self.locals[src[0]].llty);
1924+
let signer_ty = self.locals[dst[0]].llty;
1925+
let agg = llvm::Constant::get_const_null(signer_ty).as_any_value();
1926+
let signer_val = builder.build_insert_value(agg, addr_val, 0, "wrap_signer");
1927+
builder.build_store(signer_val, self.locals[dst[0]].llval);
1928+
return;
1929+
}
1930+
1931+
// transaction_context functions returning address (32 bytes).
1932+
// RV64 ABI mismatch — intercept and call guest export with output pointer.
1933+
if full_name.ends_with("::sender_internal")
1934+
|| full_name.ends_with("::gas_payer_internal")
1935+
|| full_name.ends_with("::generate_unique_address")
1936+
{
1937+
assert_eq!(dst.len(), 1);
1938+
assert_eq!(src.len(), 0);
1939+
let builder = &self.module_cx.llvm_builder;
1940+
let llcx = &self.module_cx.llvm_cx;
1941+
// Determine the guest export symbol name
1942+
let sym_name = fn_env.llvm_native_fn_symbol_name();
1943+
// Declare or get the function with correct ABI: void(ptr)
1944+
let ll_fn = match self.module_cx.llvm_module.get_named_function(&sym_name) {
1945+
Some(f) => f,
1946+
None => {
1947+
let fn_ty = llvm::FunctionType::new(llcx.void_type(), &[llcx.ptr_type()]);
1948+
self.module_cx.llvm_module.add_function(
1949+
&mut vec![],
1950+
"native",
1951+
&sym_name,
1952+
fn_ty,
1953+
false,
1954+
)
1955+
}
1956+
};
1957+
let dst_ptr = self.locals[dst[0]].llval.as_any_value();
1958+
builder.call(ll_fn, &[dst_ptr]);
1959+
return;
1960+
}
19071961
}
19081962

19091963
let types = mty::Type::instantiate_vec(types.to_vec(), self.type_params);
@@ -2561,8 +2615,10 @@ pub fn write_object_file(
25612615
llmod: llvm::Module,
25622616
llmachine: &llvm::TargetMachine,
25632617
outpath: &str,
2564-
) -> anyhow::Result<()> {
2565-
llmod.verify();
2618+
) -> anyhow::Result<bool> {
2619+
if !llmod.verify() {
2620+
return Ok(false);
2621+
}
25662622
// Dump LLVM IR for debugging
25672623
unsafe {
25682624
let ir_path = format!("{outpath}.ll");
@@ -2571,5 +2627,5 @@ pub fn write_object_file(
25712627
llvm_sys::core::LLVMPrintModuleToFile(llmod.0, ir_path_c.as_ptr(), &mut err);
25722628
}
25732629
llmachine.emit_to_obj_file(&llmod, outpath)?;
2574-
Ok(())
2630+
Ok(true)
25752631
}

0 commit comments

Comments
 (0)