From 1ce6f2462c3b8b887d0115a5ba8afba101393213 Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Fri, 29 May 2026 00:28:12 +0800 Subject: [PATCH] feat(mlir): plumb cross-contract dependencies through the MLIR pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With per-contract MLIR emission, the linker assembles bytecodes across contracts but only sees the dependencies declared on each `EVMContractObject`. The MLIR branch in `solx-core/src/project/contract/mod.rs` was constructing those with only the contract's own runtime identifier, so `new B()` from contract A had no way to resolve B's deploy bytecode at link time. Mirroring how the Yul pipeline walks `datasize`/`dataoffset` calls to populate `Dependencies`, this change adds a `Vec` of cross-contract references to `MlirOutput` and threads it through `solx_mlir::Context` (interior mut, since emitters hold `&Context`), through `solx-core`'s `ContractMLIR`, and into the `Dependencies` struct fed to the assembler. Dormant until an emitter calls `Context::add_dependency` — no test delta today. The follow-up wiring `new B()` to `sol.new` will populate the list and make cross-contract creation linkable. --- solx-core/src/project/contract/ir/mlir.rs | 5 +++++ solx-core/src/project/contract/mod.rs | 19 ++++++++----------- solx-core/src/project/mod.rs | 2 ++ solx-mlir/src/context/mod.rs | 18 ++++++++++++++++++ solx-mlir/src/output.rs | 8 ++++++++ 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/solx-core/src/project/contract/ir/mlir.rs b/solx-core/src/project/contract/ir/mlir.rs index 97020a2eb..9998bffdf 100644 --- a/solx-core/src/project/contract/ir/mlir.rs +++ b/solx-core/src/project/contract/ir/mlir.rs @@ -9,6 +9,11 @@ pub struct MLIR { /// LLVM dialect text of this code segment. pub source: String, + /// Bare object names of contracts this segment references via cross- + /// contract ops (`sol.new` and friends). Populated during Slang→MLIR + /// emission and used by the assembler to pull in dependency bytecodes. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dependencies: Vec, /// Runtime code object that is only set in deploy code. #[serde(default, skip_serializing_if = "Option::is_none")] pub runtime_code: Option>, diff --git a/solx-core/src/project/contract/mod.rs b/solx-core/src/project/contract/mod.rs index 17b0d1fc2..12d9e2152 100644 --- a/solx-core/src/project/contract/mod.rs +++ b/solx-core/src/project/contract/mod.rs @@ -529,17 +529,14 @@ impl Contract { solx_utils::CodeSegment::Deploy => bare_name.to_owned(), solx_utils::CodeSegment::Runtime => runtime_identifier.clone(), }; - let dependencies = match code_segment { - solx_utils::CodeSegment::Deploy => { - let mut dependencies = - solx_codegen_evm::Dependencies::new(code_identifier.as_str()); - dependencies.push(runtime_identifier, true); - dependencies - } - solx_utils::CodeSegment::Runtime => { - solx_codegen_evm::Dependencies::new(code_identifier.as_str()) - } - }; + let mut dependencies = + solx_codegen_evm::Dependencies::new(code_identifier.as_str()); + if matches!(code_segment, solx_utils::CodeSegment::Deploy) { + dependencies.push(runtime_identifier, true); + } + for cross_contract in mlir.dependencies.iter() { + dependencies.push(cross_contract.clone(), false); + } let melior_context = solx_mlir::Context::create_mlir_context(); let raw_llvm = diff --git a/solx-core/src/project/mod.rs b/solx-core/src/project/mod.rs index e5c056ec8..b8a034c0d 100644 --- a/solx-core/src/project/mod.rs +++ b/solx-core/src/project/mod.rs @@ -167,10 +167,12 @@ impl Project { let result = contract.mlir.as_ref().map(|output| { let runtime_code = ContractMLIR { source: output.runtime_source.clone(), + dependencies: output.dependencies.clone(), runtime_code: None, }; let deploy_code = ContractMLIR { source: output.deploy_source.clone(), + dependencies: output.dependencies.clone(), runtime_code: Some(Box::new(runtime_code)), }; Ok::<_, anyhow::Error>(Some(ContractIR::from(deploy_code))) diff --git a/solx-mlir/src/context/mod.rs b/solx-mlir/src/context/mod.rs index 2bc61b1ed..c9f2beb2f 100644 --- a/solx-mlir/src/context/mod.rs +++ b/solx-mlir/src/context/mod.rs @@ -10,6 +10,7 @@ pub mod builder; pub mod environment; pub mod function; +use std::cell::RefCell; use std::collections::HashMap; use std::sync::Once; @@ -48,6 +49,11 @@ pub struct Context<'context> { /// The MLIR type of the contract currently being emitted, used to type /// `this` expressions. Frontends set this before emitting function bodies. pub current_contract_type: Option>, + /// Cross-contract references collected during emission, in encounter + /// order. Populated by [`Self::add_dependency`] from emitters that + /// reach into other contracts (e.g. `sol.new`), drained into the + /// returned [`crate::output::MlirOutput`] by [`Self::finalize_module`]. + dependencies: RefCell>, } impl<'context> Context<'context> { @@ -159,6 +165,17 @@ impl<'context> Context<'context> { function_signatures: HashMap::new(), builder: Builder::new(context), current_contract_type: None, + dependencies: RefCell::new(Vec::new()), + } + } + + /// Records a cross-contract reference (e.g. the object name passed to + /// `sol.new`). Duplicates are ignored. The accumulated list is drained + /// into [`crate::output::MlirOutput::dependencies`] at finalize time. + pub fn add_dependency(&self, name: String) { + let mut dependencies = self.dependencies.borrow_mut(); + if !dependencies.iter().any(|existing| existing == &name) { + dependencies.push(name); } } @@ -316,6 +333,7 @@ impl<'context> Context<'context> { sol_source, deploy_source: deploy_llvm, runtime_source: runtime_llvm, + dependencies: self.dependencies.into_inner(), }) } diff --git a/solx-mlir/src/output.rs b/solx-mlir/src/output.rs index 67b130300..3d9cd8361 100644 --- a/solx-mlir/src/output.rs +++ b/solx-mlir/src/output.rs @@ -16,4 +16,12 @@ pub struct MlirOutput { /// LLVM dialect text of the runtime module. #[serde(default, skip_serializing_if = "String::is_empty")] pub runtime_source: String, + /// Cross-contract references collected during Slang→MLIR emission. + /// + /// Each entry is the bare object name (e.g. `"B"`) of a contract this + /// module references via `sol.new` or other cross-contract ops. The + /// linker resolves these to the dependency's deploy bytecode when + /// assembling. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dependencies: Vec, }