瀏覽代碼

Build many contracts into a single solana BPF so file

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 年之前
父節點
當前提交
67781be3fb

+ 1 - 1
integration/solana/builtins.spec.ts

@@ -8,7 +8,7 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let hash_functions = await conn.loadProgram("builtins.so", "builtins.abi");
+        let hash_functions = await conn.loadProgram("bundle.so", "builtins.abi");
 
         // call the constructor
         await hash_functions.call_constructor(conn, 'builtins', []);

+ 3 - 3
integration/solana/calls.spec.ts

@@ -8,9 +8,9 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let caller = await conn.loadProgram("caller.so", "caller.abi");
-        let callee = await conn.loadProgram("callee.so", "callee.abi");
-        let callee2 = await conn.loadProgram("callee2.so", "callee2.abi");
+        let caller = await conn.loadProgram("bundle.so", "caller.abi");
+        let callee = await conn.loadProgram("bundle.so", "callee.abi");
+        let callee2 = await conn.loadProgram("bundle.so", "callee2.abi");
 
         // call the constructor
         await caller.call_constructor(conn, 'caller', []);

+ 1 - 1
integration/solana/index.ts

@@ -42,7 +42,7 @@ const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
 
 async function newAccountWithLamports(
     connection: Connection,
-    lamports: number = 1000000000,
+    lamports: number = 10000000000,
 ): Promise<Account> {
     const account = new Account();
 

+ 9 - 9
integration/solana/simple.spec.ts

@@ -8,7 +8,7 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let prog = await conn.loadProgram("flipper.so", "flipper.abi");
+        let prog = await conn.loadProgram("bundle.so", "flipper.abi");
 
         // call the constructor
         await prog.call_constructor(conn, 'flipper', ["true"]);
@@ -31,7 +31,7 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let prog = await conn.loadProgram("primitives.so", "primitives.abi");
+        let prog = await conn.loadProgram("bundle.so", "primitives.abi");
 
         // call the constructor
         await prog.call_constructor(conn, 'primitives', []);
@@ -154,7 +154,7 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let prog = await conn.loadProgram("store.so", "store.abi");
+        let prog = await conn.loadProgram("bundle.so", "store.abi");
 
         // call the constructor
         await prog.call_constructor(conn, 'store', []);
@@ -251,7 +251,7 @@ describe('Deploy solang contract and test', () => {
 
         let conn = await establishConnection();
 
-        let prog = await conn.loadProgram("store.so", "store.abi", 8192);
+        let prog = await conn.loadProgram("bundle.so", "store.abi", 8192);
 
         // call the constructor
         await prog.call_constructor(conn, 'store', []);
@@ -400,7 +400,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 byes
-        let prog = await conn.loadProgram("store.so", "store.abi", 100);
+        let prog = await conn.loadProgram("bundle.so", "store.abi", 100);
 
         await expect(prog.call_constructor(conn, 'store', []))
             .rejects
@@ -413,7 +413,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 byes
-        let prog = await conn.loadProgram("store.so", "store.abi", 512);
+        let prog = await conn.loadProgram("bundle.so", "store.abi", 512);
 
         await prog.call_constructor(conn, 'store', []);
 
@@ -431,7 +431,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 bytes on constructor, more for string data
-        let prog = await conn.loadProgram("store.so", "store.abi", 180);
+        let prog = await conn.loadProgram("bundle.so", "store.abi", 180);
 
         await prog.call_constructor(conn, 'store', []);
 
@@ -448,7 +448,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 bytes on constructor, more for string data
-        let prog = await conn.loadProgram("store.so", "store.abi", 210);
+        let prog = await conn.loadProgram("bundle.so", "store.abi", 210);
 
         await prog.call_constructor(conn, 'store', []);
 
@@ -471,7 +471,7 @@ describe('Deploy solang contract and test', () => {
         let conn = await establishConnection();
 
         // storage.sol needs 168 bytes on constructor, more for string data
-        let prog = await conn.loadProgram("arrays.so", "arrays.abi", 4096);
+        let prog = await conn.loadProgram("bundle.so", "arrays.abi", 4096);
 
         await prog.call_constructor(conn, 'arrays', []);
 

+ 234 - 148
src/bin/solang.rs

@@ -1,4 +1,5 @@
 use clap::{App, Arg, ArgMatches};
+use itertools::Itertools;
 use serde::Serialize;
 use std::collections::HashMap;
 use std::fs::File;
@@ -8,7 +9,7 @@ use std::path::{Path, PathBuf};
 use solang::abi;
 use solang::codegen::{codegen, Options};
 use solang::file_cache::FileCache;
-use solang::sema::diagnostics;
+use solang::sema::{ast::Namespace, diagnostics};
 
 mod doc;
 mod languageserver;
@@ -147,12 +148,13 @@ fn main() {
         languageserver::start_server(target);
     }
 
+    let verbose = matches.is_present("VERBOSE");
     let mut json = JsonResult {
         errors: Vec::new(),
         contracts: HashMap::new(),
     };
 
-    if matches.is_present("VERBOSE") {
+    if verbose {
         eprintln!("info: Solang version {}", env!("GIT_HASH"));
     }
 
@@ -215,15 +217,91 @@ fn main() {
             doc::generate_docs(matches.value_of("OUTPUT").unwrap_or("."), &files, verbose);
         }
     } else {
+        let llvm_opt = match matches.value_of("OPT").unwrap() {
+            "none" => inkwell::OptimizationLevel::None,
+            "less" => inkwell::OptimizationLevel::Less,
+            "default" => inkwell::OptimizationLevel::Default,
+            "aggressive" => inkwell::OptimizationLevel::Aggressive,
+            _ => unreachable!(),
+        };
+
+        let opt = Options {
+            dead_storage: !matches.is_present("DEADSTORAGE"),
+            strength_reduce: !matches.is_present("STRENGTHREDUCE"),
+            constant_folding: !matches.is_present("CONSTANTFOLDING"),
+            vector_to_slice: !matches.is_present("VECTORTOSLICE"),
+        };
+
+        let mut namespaces = Vec::new();
+
         for filename in matches.values_of("INPUT").unwrap() {
-            process_filename(
+            namespaces.push(process_filename(
                 filename,
                 &mut cache,
                 target,
                 &matches,
                 &mut json,
                 math_overflow_check,
+                &opt,
+                llvm_opt,
+            ));
+        }
+
+        if target == solang::Target::Solana {
+            let context = inkwell::context::Context::create();
+
+            let binary = solang::compile_many(
+                &context,
+                &namespaces,
+                "bundle.sol",
+                llvm_opt,
+                math_overflow_check,
             );
+
+            if !save_intermediates(&binary, &matches) {
+                let bin_filename = output_file(&matches, "bundle", target.file_extension());
+
+                if matches.is_present("VERBOSE") {
+                    eprintln!(
+                        "info: Saving binary {} for contracts: {}",
+                        bin_filename.display(),
+                        namespaces
+                            .iter()
+                            .flat_map(|ns| ns
+                                .contracts
+                                .iter()
+                                .map(|contract| contract.name.as_str()))
+                            .join(", "),
+                    );
+                }
+
+                let code = binary.code(true).expect("llvm code emit should work");
+
+                let mut file = File::create(bin_filename).unwrap();
+                file.write_all(&code).unwrap();
+
+                // Write all ABI files
+                for ns in &namespaces {
+                    for contract_no in 0..ns.contracts.len() {
+                        let contract = &ns.contracts[contract_no];
+
+                        let (abi_bytes, abi_ext) =
+                            abi::generate_abi(contract_no, &ns, &code, verbose);
+                        let abi_filename = output_file(&matches, &contract.name, abi_ext);
+
+                        if verbose {
+                            eprintln!(
+                                "info: Saving ABI {} for contract {}",
+                                abi_filename.display(),
+                                contract.name
+                            );
+                        }
+
+                        let mut file = File::create(abi_filename).unwrap();
+                        file.write_all(&abi_bytes.as_bytes()).unwrap();
+                    }
+                }
+            }
         }
 
         if matches.is_present("STD-JSON") {
@@ -232,6 +310,10 @@ fn main() {
     }
 }
 
+fn output_file(matches: &ArgMatches, stem: &str, ext: &str) -> PathBuf {
+    Path::new(matches.value_of("OUTPUT").unwrap_or(".")).join(format!("{}.{}", stem, ext))
+}
+
 fn process_filename(
     filename: &str,
     cache: &mut FileCache,
@@ -239,30 +321,16 @@ fn process_filename(
     matches: &ArgMatches,
     json: &mut JsonResult,
     math_overflow_check: bool,
-) {
-    let output_file = |stem: &str, ext: &str| -> PathBuf {
-        Path::new(matches.value_of("OUTPUT").unwrap_or(".")).join(format!("{}.{}", stem, ext))
-    };
+    opt: &Options,
+    llvm_opt: inkwell::OptimizationLevel,
+) -> Namespace {
     let verbose = matches.is_present("VERBOSE");
-    let llvm_opt = match matches.value_of("OPT").unwrap() {
-        "none" => inkwell::OptimizationLevel::None,
-        "less" => inkwell::OptimizationLevel::Less,
-        "default" => inkwell::OptimizationLevel::Default,
-        "aggressive" => inkwell::OptimizationLevel::Aggressive,
-        _ => unreachable!(),
-    };
+
     let mut json_contracts = HashMap::new();
 
     // resolve phase
     let mut ns = solang::parse_and_resolve(filename, cache, target);
 
-    let opt = Options {
-        dead_storage: !matches.is_present("DEADSTORAGE"),
-        strength_reduce: !matches.is_present("STRENGTHREDUCE"),
-        constant_folding: !matches.is_present("CONSTANTFOLDING"),
-        vector_to_slice: !matches.is_present("VECTORTOSLICE"),
-    };
-
     // codegen all the contracts; some additional errors/warnings will be detected here
     for contract_no in 0..ns.contracts.len() {
         codegen(contract_no, &mut ns, &opt);
@@ -282,7 +350,7 @@ fn process_filename(
 
     if let Some("ast") = matches.value_of("EMIT") {
         println!("{}", ns.print(filename));
-        return;
+        return ns;
     }
 
     // emit phase
@@ -298,6 +366,10 @@ fn process_filename(
             continue;
         }
 
+        if target == solang::Target::Solana {
+            return ns;
+        }
+
         if verbose {
             eprintln!(
                 "info: Generating LLVM IR for contract {} with target {}",
@@ -307,120 +379,14 @@ fn process_filename(
 
         let context = inkwell::context::Context::create();
 
-        let contract =
+        let binary =
             resolved_contract.emit(&ns, &context, &filename, llvm_opt, math_overflow_check);
 
-        if let Some("llvm-ir") = matches.value_of("EMIT") {
-            if let Some(runtime) = &contract.runtime {
-                // In Ethereum, an ewasm contract has two parts, deployer and runtime. The deployer code returns the runtime wasm
-                // as a byte string
-                let llvm_filename = output_file(&format!("{}_deploy", contract.name), "ll");
-
-                if verbose {
-                    eprintln!(
-                        "info: Saving deployer LLVM {} for contract {}",
-                        llvm_filename.display(),
-                        contract.name
-                    );
-                }
-
-                contract.dump_llvm(&llvm_filename).unwrap();
-
-                let llvm_filename = output_file(&format!("{}_runtime", contract.name), "ll");
-
-                if verbose {
-                    eprintln!(
-                        "info: Saving runtime LLVM {} for contract {}",
-                        llvm_filename.display(),
-                        contract.name
-                    );
-                }
-
-                runtime.dump_llvm(&llvm_filename).unwrap();
-            } else {
-                let llvm_filename = output_file(&contract.name, "ll");
-
-                if verbose {
-                    eprintln!(
-                        "info: Saving LLVM IR {} for contract {}",
-                        llvm_filename.display(),
-                        contract.name
-                    );
-                }
-
-                contract.dump_llvm(&llvm_filename).unwrap();
-            }
+        if save_intermediates(&binary, matches) {
             continue;
         }
 
-        if let Some("llvm-bc") = matches.value_of("EMIT") {
-            // In Ethereum, an ewasm contract has two parts, deployer and runtime. The deployer code returns the runtime wasm
-            // as a byte string
-            if let Some(runtime) = &contract.runtime {
-                let bc_filename = output_file(&format!("{}_deploy", contract.name), "bc");
-
-                if verbose {
-                    eprintln!(
-                        "info: Saving deploy LLVM BC {} for contract {}",
-                        bc_filename.display(),
-                        contract.name
-                    );
-                }
-
-                contract.bitcode(&bc_filename);
-
-                let bc_filename = output_file(&format!("{}_runtime", contract.name), "bc");
-
-                if verbose {
-                    eprintln!(
-                        "info: Saving runtime LLVM BC {} for contract {}",
-                        bc_filename.display(),
-                        contract.name
-                    );
-                }
-
-                runtime.bitcode(&bc_filename);
-            } else {
-                let bc_filename = output_file(&contract.name, "bc");
-
-                if verbose {
-                    eprintln!(
-                        "info: Saving LLVM BC {} for contract {}",
-                        bc_filename.display(),
-                        contract.name
-                    );
-                }
-
-                contract.bitcode(&bc_filename);
-            }
-            continue;
-        }
-
-        if let Some("object") = matches.value_of("EMIT") {
-            let obj = match contract.code(false) {
-                Ok(o) => o,
-                Err(s) => {
-                    println!("error: {}", s);
-                    std::process::exit(1);
-                }
-            };
-
-            let obj_filename = output_file(&contract.name, "o");
-
-            if verbose {
-                eprintln!(
-                    "info: Saving Object {} for contract {}",
-                    obj_filename.display(),
-                    contract.name
-                );
-            }
-
-            let mut file = File::create(obj_filename).unwrap();
-            file.write_all(&obj).unwrap();
-            continue;
-        }
-
-        let code = match contract.code(true) {
+        let code = match binary.code(true) {
             Ok(o) => o,
             Err(s) => {
                 println!("error: {}", s);
@@ -430,7 +396,7 @@ fn process_filename(
 
         if matches.is_present("STD-JSON") {
             json_contracts.insert(
-                contract.name.to_owned(),
+                binary.name.to_owned(),
                 JsonContract {
                     abi: abi::ethereum::gen_abi(contract_no, &ns),
                     ewasm: EwasmContract {
@@ -442,7 +408,7 @@ fn process_filename(
             if verbose && target == solang::Target::Solana {
                 eprintln!(
                     "info: contract {} uses at least {} bytes account data",
-                    contract.name, resolved_contract.fixed_layout_size,
+                    binary.name, resolved_contract.fixed_layout_size,
                 );
             }
 
@@ -450,48 +416,168 @@ fn process_filename(
             if target == solang::Target::Substrate {
                 let (contract_bs, contract_ext) =
                     abi::generate_abi(contract_no, &ns, &code, verbose);
-                let contract_filename = output_file(&contract.name, contract_ext);
+                let contract_filename = output_file(matches, &binary.name, contract_ext);
 
                 if verbose {
                     eprintln!(
                         "info: Saving {} for contract {}",
                         contract_filename.display(),
-                        contract.name
+                        binary.name
                     );
                 }
 
                 let mut file = File::create(contract_filename).unwrap();
                 file.write_all(&contract_bs.as_bytes()).unwrap();
             } else {
-                let bin_filename = output_file(&contract.name, target.file_extension());
+                let bin_filename = output_file(matches, &binary.name, target.file_extension());
 
                 if verbose {
                     eprintln!(
                         "info: Saving binary {} for contract {}",
                         bin_filename.display(),
-                        contract.name
+                        binary.name
                     );
                 }
 
                 let mut file = File::create(bin_filename).unwrap();
                 file.write_all(&code).unwrap();
 
-                let (abi_bytes, abi_ext) = abi::generate_abi(contract_no, &ns, &code, verbose);
-                let abi_filename = output_file(&contract.name, abi_ext);
+                if target != solang::Target::Solana {
+                    let (abi_bytes, abi_ext) = abi::generate_abi(contract_no, &ns, &code, verbose);
+                    let abi_filename = output_file(matches, &binary.name, abi_ext);
 
-                if verbose {
-                    eprintln!(
-                        "info: Saving ABI {} for contract {}",
-                        abi_filename.display(),
-                        contract.name
-                    );
-                }
+                    if verbose {
+                        eprintln!(
+                            "info: Saving ABI {} for contract {}",
+                            abi_filename.display(),
+                            binary.name
+                        );
+                    }
 
-                file = File::create(abi_filename).unwrap();
-                file.write_all(&abi_bytes.as_bytes()).unwrap();
+                    let mut file = File::create(abi_filename).unwrap();
+                    file.write_all(&abi_bytes.as_bytes()).unwrap();
+                }
             }
         }
     }
 
     json.contracts.insert(filename.to_owned(), json_contracts);
+
+    ns
+}
+
+fn save_intermediates(binary: &solang::emit::Binary, matches: &ArgMatches) -> bool {
+    let verbose = matches.is_present("VERBOSE");
+
+    if let Some("llvm-ir") = matches.value_of("EMIT") {
+        if let Some(runtime) = &binary.runtime {
+            // In Ethereum, an ewasm contract has two parts, deployer and runtime. The deployer code returns the runtime wasm
+            // as a byte string
+            let llvm_filename = output_file(matches, &format!("{}_deploy", binary.name), "ll");
+
+            if verbose {
+                eprintln!(
+                    "info: Saving deployer LLVM {} for contract {}",
+                    llvm_filename.display(),
+                    binary.name
+                );
+            }
+
+            binary.dump_llvm(&llvm_filename).unwrap();
+
+            let llvm_filename = output_file(matches, &format!("{}_runtime", binary.name), "ll");
+
+            if verbose {
+                eprintln!(
+                    "info: Saving runtime LLVM {} for contract {}",
+                    llvm_filename.display(),
+                    binary.name
+                );
+            }
+
+            runtime.dump_llvm(&llvm_filename).unwrap();
+        } else {
+            let llvm_filename = output_file(matches, &binary.name, "ll");
+
+            if verbose {
+                eprintln!(
+                    "info: Saving LLVM IR {} for contract {}",
+                    llvm_filename.display(),
+                    binary.name
+                );
+            }
+
+            binary.dump_llvm(&llvm_filename).unwrap();
+        }
+        return true;
+    }
+
+    if let Some("llvm-bc") = matches.value_of("EMIT") {
+        // In Ethereum, an ewasm contract has two parts, deployer and runtime. The deployer code returns the runtime wasm
+        // as a byte string
+        if let Some(runtime) = &binary.runtime {
+            let bc_filename = output_file(matches, &format!("{}_deploy", binary.name), "bc");
+
+            if verbose {
+                eprintln!(
+                    "info: Saving deploy LLVM BC {} for contract {}",
+                    bc_filename.display(),
+                    binary.name
+                );
+            }
+
+            binary.bitcode(&bc_filename);
+
+            let bc_filename = output_file(matches, &format!("{}_runtime", binary.name), "bc");
+
+            if verbose {
+                eprintln!(
+                    "info: Saving runtime LLVM BC {} for contract {}",
+                    bc_filename.display(),
+                    binary.name
+                );
+            }
+
+            runtime.bitcode(&bc_filename);
+        } else {
+            let bc_filename = output_file(matches, &binary.name, "bc");
+
+            if verbose {
+                eprintln!(
+                    "info: Saving LLVM BC {} for contract {}",
+                    bc_filename.display(),
+                    binary.name
+                );
+            }
+
+            binary.bitcode(&bc_filename);
+        }
+        return true;
+    }
+
+    if let Some("object") = matches.value_of("EMIT") {
+        let obj = match binary.code(false) {
+            Ok(o) => o,
+            Err(s) => {
+                println!("error: {}", s);
+                std::process::exit(1);
+            }
+        };
+
+        let obj_filename = output_file(matches, &binary.name, "o");
+
+        if verbose {
+            eprintln!(
+                "info: Saving Object {} for contract {}",
+                obj_filename.display(),
+                binary.name
+            );
+        }
+
+        let mut file = File::create(obj_filename).unwrap();
+        file.write_all(&obj).unwrap();
+        return true;
+    }
+
+    false
 }

+ 3 - 2
src/emit/ewasm.rs

@@ -37,7 +37,7 @@ impl EwasmTarget {
         };
         let mut runtime_code = Binary::new(
             context,
-            ns,
+            ns.target,
             &contract.name,
             filename,
             opt,
@@ -66,7 +66,7 @@ impl EwasmTarget {
         };
         let mut deploy_code = Binary::new(
             context,
-            ns,
+            ns.target,
             &contract.name,
             filename,
             opt,
@@ -690,6 +690,7 @@ impl EwasmTarget {
             argsdata,
             argslen,
             function,
+            &binary.functions,
             None,
             |func| !binary.function_abort_value_transfers && func.nonpayable,
         );

+ 2 - 1
src/emit/generic.rs

@@ -34,7 +34,7 @@ impl GenericTarget {
 
         let mut binary = Binary::new(
             context,
-            ns,
+            ns.target,
             &contract.name,
             filename,
             opt,
@@ -197,6 +197,7 @@ impl GenericTarget {
             argsdata,
             argslen,
             function,
+            &binary.functions,
             None,
             |_| false,
         );

+ 11 - 10
src/emit/mod.rs

@@ -4066,6 +4066,7 @@ pub trait TargetRuntime<'a> {
         argsdata: inkwell::values::PointerValue<'a>,
         argslen: inkwell::values::IntValue<'a>,
         function: inkwell::values::FunctionValue<'a>,
+        functions: &HashMap<usize, FunctionValue<'a>>,
         fallback: Option<inkwell::basic_block::BasicBlock>,
         nonpayable: F,
     ) where
@@ -4132,7 +4133,7 @@ pub trait TargetRuntime<'a> {
                 argsdata,
                 argslen,
                 function,
-                bin.functions[&cfg_no],
+                functions[&cfg_no],
                 &nonpayable,
             );
         }
@@ -5256,14 +5257,14 @@ impl<'a> Binary<'a> {
     /// Build the LLVM IR for a set of contracts in a single namespace
     pub fn build_bundle(
         context: &'a Context,
-        ns: &'a ast::Namespace,
-        filename: &'a str,
+        namespaces: &'a [ast::Namespace],
+        filename: &str,
         opt: OptimizationLevel,
         math_overflow_check: bool,
     ) -> Self {
-        assert!(ns.target == Target::Solana);
+        assert!(namespaces.iter().all(|ns| ns.target == Target::Solana));
 
-        solana::SolanaTarget::build_bundle(context, ns, filename, opt, math_overflow_check)
+        solana::SolanaTarget::build_bundle(context, namespaces, filename, opt, math_overflow_check)
     }
 
     /// Compile the bin and return the code as bytes. The result is
@@ -5373,23 +5374,23 @@ impl<'a> Binary<'a> {
 
     pub fn new(
         context: &'a Context,
-        ns: &'a ast::Namespace,
+        target: Target,
         name: &str,
-        filename: &'a str,
+        filename: &str,
         opt: OptimizationLevel,
         math_overflow_check: bool,
         runtime: Option<Box<Binary<'a>>>,
     ) -> Self {
         lazy_static::initialize(&LLVM_INIT);
 
-        let triple = ns.target.llvm_target_triple();
+        let triple = target.llvm_target_triple();
         let module = context.create_module(name);
 
         module.set_triple(&triple);
         module.set_source_file_name(filename);
 
         // stdlib
-        let intr = load_stdlib(&context, &ns.target);
+        let intr = load_stdlib(&context, &target);
         module.link_in_module(intr).unwrap();
 
         let selector =
@@ -5439,7 +5440,7 @@ impl<'a> Binary<'a> {
             math_overflow_check,
             builder: context.create_builder(),
             context,
-            target: ns.target,
+            target,
             functions: HashMap::new(),
             code: RefCell::new(Vec::new()),
             opt,

+ 2 - 1
src/emit/sabre.rs

@@ -33,7 +33,7 @@ impl SabreTarget {
         };
         let mut c = Binary::new(
             context,
-            ns,
+            ns.target,
             &contract.name,
             filename,
             opt,
@@ -225,6 +225,7 @@ impl SabreTarget {
             argsdata,
             argslen,
             function,
+            &binary.functions,
             None,
             |_| false,
         );

+ 67 - 63
src/emit/solana.rs

@@ -1,6 +1,7 @@
 use crate::codegen::cfg::HashTy;
 use crate::parser::pt;
 use crate::sema::ast;
+use crate::Target;
 use std::collections::HashMap;
 use std::convert::TryInto;
 use std::str;
@@ -25,8 +26,10 @@ pub struct SolanaTarget {
 pub struct Contract<'a> {
     magic: u32,
     contract: &'a ast::Contract,
+    ns: &'a ast::Namespace,
     storage_initializer: FunctionValue<'a>,
     constructor: Option<(FunctionValue<'a>, &'a Vec<ast::Parameter>)>,
+    functions: HashMap<usize, FunctionValue<'a>>,
 }
 
 // Implement the Solana target which uses BPF
@@ -53,7 +56,7 @@ impl SolanaTarget {
 
         let mut binary = Binary::new(
             context,
-            ns,
+            Target::Solana,
             &contract.name,
             filename,
             opt,
@@ -87,15 +90,20 @@ impl SolanaTarget {
             .find(|(_, cfg)| cfg.ty == pt::FunctionTy::Constructor)
             .map(|(cfg_no, cfg)| (binary.functions[&cfg_no], &cfg.params));
 
+        let mut functions = HashMap::new();
+
+        std::mem::swap(&mut functions, &mut binary.functions);
+
         target.emit_dispatch(
             &mut binary,
             &[Contract {
                 magic: target.magic,
                 contract,
+                ns,
                 storage_initializer,
                 constructor,
+                functions,
             }],
-            ns,
         );
 
         binary.internalize(&[
@@ -112,8 +120,8 @@ impl SolanaTarget {
     /// Build a bundle of contracts from the same namespace
     pub fn build_bundle<'a>(
         context: &'a Context,
-        ns: &'a ast::Namespace,
-        filename: &'a str,
+        namespaces: &'a [ast::Namespace],
+        filename: &str,
         opt: OptimizationLevel,
         math_overflow_check: bool,
     ) -> Binary<'a> {
@@ -124,7 +132,7 @@ impl SolanaTarget {
 
         let mut binary = Binary::new(
             context,
-            ns,
+            Target::Solana,
             "bundle",
             filename,
             opt,
@@ -149,41 +157,49 @@ impl SolanaTarget {
 
         let mut contracts: Vec<Contract> = Vec::new();
 
-        for contract in &ns.contracts {
-            if !contract.is_concrete() {
-                continue;
-            }
+        for ns in namespaces {
+            for contract in &ns.contracts {
+                if !contract.is_concrete() {
+                    continue;
+                }
 
-            // We need a magic number for our contract.
-            let mut hasher = Keccak::v256();
-            let mut hash = [0u8; 32];
-            hasher.update(contract.name.as_bytes());
-            hasher.finalize(&mut hash);
+                // We need a magic number for our contract.
+                let mut hasher = Keccak::v256();
+                let mut hash = [0u8; 32];
+                hasher.update(contract.name.as_bytes());
+                hasher.finalize(&mut hash);
 
-            target.magic = u32::from_le_bytes(hash[0..4].try_into().unwrap());
+                target.magic = u32::from_le_bytes(hash[0..4].try_into().unwrap());
 
-            target.emit_functions(&mut binary, contract, ns);
+                target.emit_functions(&mut binary, contract, ns);
 
-            let storage_initializer = target.emit_initializer(&mut binary, contract, ns);
+                let storage_initializer = target.emit_initializer(&mut binary, contract, ns);
 
-            let constructor = contract
-                .cfg
-                .iter()
-                .enumerate()
-                .find(|(_, cfg)| cfg.ty == pt::FunctionTy::Constructor)
-                .map(|(cfg_no, cfg)| (binary.functions[&cfg_no], &cfg.params));
+                let constructor = contract
+                    .cfg
+                    .iter()
+                    .enumerate()
+                    .find(|(_, cfg)| cfg.ty == pt::FunctionTy::Constructor)
+                    .map(|(cfg_no, cfg)| (binary.functions[&cfg_no], &cfg.params));
 
-            contracts.push(Contract {
-                magic: target.magic,
-                contract,
-                storage_initializer,
-                constructor,
-            });
+                let mut functions = HashMap::new();
+
+                std::mem::swap(&mut functions, &mut binary.functions);
 
-            binary.functions.drain();
+                contracts.push(Contract {
+                    magic: target.magic,
+                    ns,
+                    contract,
+                    storage_initializer,
+                    constructor,
+                    functions,
+                });
+
+                binary.functions.drain();
+            }
         }
 
-        target.emit_dispatch(&mut binary, &contracts, ns);
+        target.emit_dispatch(&mut binary, &contracts);
 
         binary.internalize(&[
             "entrypoint",
@@ -365,12 +381,7 @@ impl SolanaTarget {
             .into_int_value()
     }
 
-    fn emit_dispatch<'b>(
-        &mut self,
-        binary: &mut Binary<'b>,
-        contracts: &[Contract<'b>],
-        ns: &ast::Namespace,
-    ) {
+    fn emit_dispatch<'b>(&mut self, binary: &mut Binary<'b>, contracts: &[Contract<'b>]) {
         let function = binary.module.get_function("solang_dispatch").unwrap();
 
         let entry = binary.context.append_basic_block(function, "entry");
@@ -417,7 +428,6 @@ impl SolanaTarget {
             .build_load(magic_value_ptr, "magic")
             .into_int_value();
 
-        let function_block = binary.context.append_basic_block(function, "function_call");
         let constructor_block = binary
             .context
             .append_basic_block(function, "constructor_call");
@@ -426,31 +436,11 @@ impl SolanaTarget {
         // if the magic is zero it's a virgin binary
         // if the magic is our magic value, it's a function call
         // if the magic is another magic value, it is an error
-        binary.builder.build_switch(
-            magic_value,
-            badmagic_block,
-            &[
-                (binary.context.i32_type().const_zero(), constructor_block),
-                (
-                    binary
-                        .context
-                        .i32_type()
-                        .const_int(self.magic as u64, false),
-                    function_block,
-                ),
-            ],
-        );
-
-        binary.builder.position_at_end(badmagic_block);
-
-        binary.builder.build_return(Some(
-            &binary.context.i64_type().const_int(4u64 << 32, false),
-        ));
 
         // Generate function call dispatch
-        let mut cases = Vec::new();
+        let function_block = binary.builder.get_insert_block().unwrap();
 
-        binary.builder.position_at_end(function_block);
+        let mut cases = vec![(binary.context.i32_type().const_zero(), constructor_block)];
 
         let input = binary.builder.build_pointer_cast(
             input,
@@ -476,16 +466,23 @@ impl SolanaTarget {
             self.emit_function_dispatch(
                 binary,
                 &contract.contract,
-                ns,
+                contract.ns,
                 pt::FunctionTy::Function,
                 input,
                 input_len,
                 function,
+                &contract.functions,
                 None,
                 |_| false,
             );
         }
 
+        binary.builder.position_at_end(badmagic_block);
+
+        binary.builder.build_return(Some(
+            &binary.context.i64_type().const_int(4u64 << 32, false),
+        ));
+
         binary.builder.position_at_end(function_block);
 
         let input = binary.builder.build_pointer_cast(
@@ -591,8 +588,15 @@ impl SolanaTarget {
                 let mut args = Vec::new();
 
                 // insert abi decode
-                self.abi
-                    .decode(binary, function, &mut args, input, input_len, params, ns);
+                self.abi.decode(
+                    binary,
+                    function,
+                    &mut args,
+                    input,
+                    input_len,
+                    params,
+                    contract.ns,
+                );
 
                 let params_ty = constructor_function
                     .get_type()

+ 3 - 1
src/emit/substrate.rs

@@ -31,7 +31,7 @@ impl SubstrateTarget {
     ) -> Binary<'a> {
         let mut binary = Binary::new(
             context,
-            ns,
+            ns.target,
             &contract.name,
             filename,
             opt,
@@ -522,6 +522,7 @@ impl SubstrateTarget {
             deploy_args,
             deploy_args_length,
             function,
+            &binary.functions,
             Some(fallback_block),
             |_| false,
         );
@@ -563,6 +564,7 @@ impl SubstrateTarget {
             call_args,
             call_args_length,
             function,
+            &binary.functions,
             None,
             |func| !binary.function_abort_value_transfers && func.nonpayable,
         );

+ 12 - 1
src/lib.rs

@@ -1,6 +1,6 @@
 pub mod abi;
 pub mod codegen;
-mod emit;
+pub mod emit;
 pub mod file_cache;
 pub mod linker;
 pub mod parser;
@@ -95,6 +95,17 @@ pub fn compile(
     (results, ns)
 }
 
+/// Build a single binary out of multiple contracts. This is only possible on Solana
+pub fn compile_many<'a>(
+    context: &'a inkwell::context::Context,
+    namespaces: &'a [ast::Namespace],
+    filename: &str,
+    opt: OptimizationLevel,
+    math_overflow_check: bool,
+) -> emit::Binary<'a> {
+    emit::Binary::build_bundle(context, namespaces, filename, opt, math_overflow_check)
+}
+
 /// Parse and resolve the Solidity source code provided in src, for the target chain as specified in target.
 /// The result is a list of resolved contracts (if successful) and a list of compiler warnings, errors and
 /// informational messages like `found contact N`.