瀏覽代碼

Add solana bpf target

Not fully functional yet.

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

+ 2 - 2
.github/workflows/release.yml

@@ -16,7 +16,7 @@ jobs:
     - name: Rust stable
       run: rustup default stable
     - name: Compile stdlib
-      run: clang-10 --target=wasm32 -c -emit-llvm -O3 -ffreestanding -fno-builtin -Wall stdlib.c sha3.c substrate.c ripemd160.c
+      run: make
       working-directory: ./stdlib
     - name: Build
       run: cargo build --verbose --release
@@ -43,7 +43,7 @@ jobs:
     - name: Add LLVM to Path
       run: echo "c:\llvm10.0\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8
     - name: Compile stdlib
-      run: clang --target=wasm32 -c -emit-llvm -O3 -ffreestanding -fno-builtin -Wall stdlib.c sha3.c substrate.c ripemd160.c
+      run: make
       working-directory: ./stdlib
     - name: Build
       run: cargo build --release --verbose

+ 2 - 2
.github/workflows/test.yml

@@ -25,7 +25,7 @@ jobs:
     - name: Rust stable
       run: rustup default stable
     - name: Compile stdlib
-      run: clang-10 --target=wasm32 -c -emit-llvm -O3 -ffreestanding -fno-builtin -Wall stdlib.c sha3.c substrate.c ripemd160.c
+      run: make
       working-directory:  ./stdlib
     - name: Build
       run: cargo build --verbose
@@ -45,7 +45,7 @@ jobs:
     - name: Add LLVM to Path
       run: echo "c:\llvm10.0\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8
     - name: Compile stdlib
-      run: clang --target=wasm32 -c -emit-llvm -O3 -ffreestanding -fno-builtin -Wall stdlib.c sha3.c substrate.c ripemd160.c
+      run: make
       working-directory: ./stdlib
     - name: Build
       run: cargo build --verbose

+ 4 - 0
Cargo.toml

@@ -33,6 +33,7 @@ unicode-xid = "0.2.0"
 handlebars = "3.4"
 contract-metadata = "0.1.0"
 semver = { version = "0.10.0", features = ["serde"] }
+tempfile = "3.1"
 
 [dev-dependencies]
 parity-scale-codec-derive = "1.2"
@@ -42,6 +43,9 @@ ethereum-types = "0.9"
 wasmi = "0.6"
 rand = "0.7"
 sha2 = "0.9"
+solana_rbpf = "=0.1.32"
+byteorder = "1.3"
+libc = "0.2"
 
 [profile.release]
 lto = true

+ 7 - 3
Dockerfile

@@ -10,11 +10,15 @@ FROM hyperledgerlabs/solang:ci as builder
 
 COPY . src
 WORKDIR /src/stdlib/
-RUN clang-10 --target=wasm32 -c -emit-llvm -O3 -ffreestanding -fno-builtin -Wall stdlib.c sha3.c substrate.c ripemd160.c
+RUN make
 
-RUN cargo install --path /src
+WORKDIR /src
+RUN cargo build --release
 
 FROM ubuntu:18.04
-COPY --from=builder /root/.cargo/bin/solang /usr/bin/solang
+COPY --from=builder /src/target/release/solang /usr/bin/solang
+COPY --from=builder /llvm10.0 /llvm10.0/
+
+ENV PATH="/llvm10.0/bin:${PATH}"
 
 ENTRYPOINT ["/usr/bin/solang"]

+ 3 - 4
scripts/build-llvm-linux.dockerfile

@@ -9,10 +9,9 @@ RUN git clone --branch bpf --single-branch \
 
 WORKDIR /llvm-project
 
-RUN git checkout -b release_10.x origin/release/10.x
-
-RUN cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On -DLLVM_ENABLE_PROJECTS=clang  \
-    -DLLVM_ENABLE_TERMINFO=Off -DLLVM_TARGETS_TO_BUILD=WebAssembly \
+RUN cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On -DLLVM_ENABLE_TERMINFO=Off \
+    -DLLVM_ENABLE_PROJECTS=clang\;lld  \
+    -DLLVM_TARGETS_TO_BUILD=WebAssembly\;BPF \
     -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_INSTALL_PREFIX=/llvm10.0 llvm
 
 RUN cmake --build . --target install

+ 6 - 5
src/bin/solang.rs

@@ -65,7 +65,7 @@ fn main() {
                 .help("Target to build for")
                 .long("target")
                 .takes_value(true)
-                .possible_values(&["substrate", "ewasm", "sabre", "generic"])
+                .possible_values(&["substrate", "ewasm", "sabre", "generic", "solana"])
                 .default_value("substrate"),
         )
         .arg(
@@ -111,6 +111,7 @@ fn main() {
         Some("ewasm") => solang::Target::Ewasm,
         Some("sabre") => solang::Target::Sabre,
         Some("generic") => solang::Target::Generic,
+        Some("solana") => solang::Target::Solana,
         _ => unreachable!(),
     };
 
@@ -383,17 +384,17 @@ fn process_filename(
                 },
             );
         } else {
-            let wasm_filename = output_file(&contract.name, "wasm");
+            let bin_filename = output_file(&contract.name, target.file_extension());
 
             if verbose {
                 eprintln!(
-                    "info: Saving WebAssembly {} for contract {}",
-                    wasm_filename.display(),
+                    "info: Saving binary {} for contract {}",
+                    bin_filename.display(),
                     contract.name
                 );
             }
 
-            let mut file = File::create(wasm_filename).unwrap();
+            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);

+ 3 - 3
src/emit/ewasm.rs

@@ -608,18 +608,18 @@ impl EwasmTarget {
         contract.builder.build_call(initializer, &[], "");
 
         // ewasm only allows one constructor, hence find()
-        if let Some((cfg_no, con)) = contract
+        if let Some((cfg_no, cfg)) = contract
             .contract
             .cfg
             .iter()
             .enumerate()
-            .find(|(_, f)| f.ty == pt::FunctionTy::Constructor && f.public)
+            .find(|(_, cfg)| cfg.ty == pt::FunctionTy::Constructor)
         {
             let mut args = Vec::new();
 
             // insert abi decode
             self.abi
-                .decode(contract, function, &mut args, argsdata, length, &con.params);
+                .decode(contract, function, &mut args, argsdata, length, &cfg.params);
 
             contract
                 .builder

+ 65 - 36
src/emit/mod.rs

@@ -18,7 +18,7 @@ use inkwell::context::Context;
 use inkwell::memory_buffer::MemoryBuffer;
 use inkwell::module::{Linkage, Module};
 use inkwell::passes::PassManager;
-use inkwell::targets::{CodeModel, FileType, RelocMode, Target, TargetTriple};
+use inkwell::targets::{CodeModel, FileType, RelocMode, TargetTriple};
 use inkwell::types::BasicTypeEnum;
 use inkwell::types::{BasicType, IntType, StringRadix};
 use inkwell::values::{
@@ -27,19 +27,21 @@ use inkwell::values::{
 use inkwell::AddressSpace;
 use inkwell::IntPredicate;
 use inkwell::OptimizationLevel;
+use Target;
 
 mod ethabiencoder;
 mod ewasm;
 mod generic;
 mod sabre;
+mod solana;
 mod substrate;
 
-use link::link;
+use linker::link;
 
 lazy_static::lazy_static! {
     static ref LLVM_INIT: () = {
-        Target::initialize_webassembly(&Default::default());
-        Target::initialize_bpf(&Default::default());
+        inkwell::targets::Target::initialize_webassembly(&Default::default());
+        inkwell::targets::Target::initialize_bpf(&Default::default());
     };
 }
 
@@ -1964,7 +1966,7 @@ pub trait TargetRuntime<'a> {
                         contract.context.i8_type().ptr_type(AddressSpace::Generic),
                         "invalid",
                     ),
-                    Some(s) => contract.emit_global_string("const_string", s, false),
+                    Some(s) => contract.emit_global_string("const_string", s, true),
                 };
 
                 let v = contract
@@ -2169,7 +2171,7 @@ pub trait TargetRuntime<'a> {
                 self.balance(contract, addr).into()
             }
             Expression::Builtin(_, _, Builtin::Calldata, _)
-                if contract.ns.target != crate::Target::Substrate =>
+                if contract.ns.target != Target::Substrate =>
             {
                 contract
                     .builder
@@ -2410,7 +2412,7 @@ pub trait TargetRuntime<'a> {
     ) -> (PointerValue<'a>, IntValue<'a>) {
         match location {
             StringLocation::CompileTime(literal) => (
-                contract.emit_global_string("const_string", literal, false),
+                contract.emit_global_string("const_string", literal, true),
                 contract
                     .context
                     .i32_type()
@@ -3243,7 +3245,7 @@ pub trait TargetRuntime<'a> {
 
                                 self.abi_encode(
                                     contract,
-                                    Some(if contract.ns.target == crate::Target::Ewasm {
+                                    Some(if contract.ns.target == Target::Ewasm {
                                         selector.to_be()
                                     } else {
                                         selector
@@ -3460,7 +3462,7 @@ pub trait TargetRuntime<'a> {
                                 .into_int_value();
 
                             // ewasm stores the selector little endian
-                            let selector = if contract.ns.target == crate::Target::Ewasm {
+                            let selector = if contract.ns.target == Target::Ewasm {
                                 (*selector).to_be()
                             } else {
                                 *selector
@@ -3649,9 +3651,12 @@ pub trait TargetRuntime<'a> {
             .build_load(argsdata, "function_selector")
             .into_int_value();
 
-        contract
-            .builder
-            .build_store(contract.selector.as_pointer_value(), fid);
+        if contract.ns.target != Target::Solana {
+            // TODO: solana does not support bss, so different solution is needed
+            contract
+                .builder
+                .build_store(contract.selector.as_pointer_value(), fid);
+        }
 
         // step over the function selector
         let argsdata = unsafe {
@@ -4435,32 +4440,26 @@ impl<'a> Contract<'a> {
         opt: OptimizationLevel,
     ) -> Self {
         match ns.target {
-            super::Target::Substrate => {
+            Target::Substrate => {
                 substrate::SubstrateTarget::build(context, contract, ns, filename, opt)
             }
-            super::Target::Ewasm => ewasm::EwasmTarget::build(context, contract, ns, filename, opt),
-            super::Target::Sabre => sabre::SabreTarget::build(context, contract, ns, filename, opt),
-            super::Target::Generic => {
-                generic::GenericTarget::build(context, contract, ns, filename, opt)
-            }
+            Target::Ewasm => ewasm::EwasmTarget::build(context, contract, ns, filename, opt),
+            Target::Sabre => sabre::SabreTarget::build(context, contract, ns, filename, opt),
+            Target::Generic => generic::GenericTarget::build(context, contract, ns, filename, opt),
+            Target::Solana => solana::SolanaTarget::build(context, contract, ns, filename, opt),
         }
     }
 
     /// Compile the contract and return the code as bytes. The result is
     /// cached, since this function can be called multiple times (e.g. one for
     /// each time a contract of this type is created).
+    /// Pass our module to llvm for optimization and compilation
     pub fn code(&self, linking: bool) -> Result<Vec<u8>, String> {
-        let wasm = self.code.borrow();
-
-        if wasm.is_empty() {
-            self.compile(linking)
-        } else {
-            Ok(wasm.clone())
+        // return cached result if available
+        if !self.code.borrow().is_empty() {
+            return Ok(self.code.borrow().clone());
         }
-    }
 
-    /// Pass our module to llvm for optimization and compilation
-    fn compile(&self, linking: bool) -> Result<Vec<u8>, String> {
         match self.opt {
             OptimizationLevel::Default | OptimizationLevel::Aggressive => {
                 let pass_manager = PassManager::create(());
@@ -4475,7 +4474,8 @@ impl<'a> Contract<'a> {
             _ => {}
         }
 
-        let target = Target::from_name(self.ns.target.llvm_target_name()).unwrap();
+        let target =
+            inkwell::targets::Target::from_name(self.ns.target.llvm_target_name()).unwrap();
 
         let target_machine = target
             .create_target_machine(
@@ -4500,7 +4500,7 @@ impl<'a> Contract<'a> {
                     let slice = out.as_slice();
 
                     if linking {
-                        let bs = link(slice, self.ns.target);
+                        let bs = link(slice, &self.contract.name, self.ns.target);
 
                         if !self.patch_code_size(bs.len() as u64) {
                             self.code.replace(bs.to_vec());
@@ -5224,15 +5224,24 @@ static STDLIB_IR: &[u8] = include_bytes!("../../stdlib/stdlib.bc");
 static SHA3_IR: &[u8] = include_bytes!("../../stdlib/sha3.bc");
 static RIPEMD160_IR: &[u8] = include_bytes!("../../stdlib/ripemd160.bc");
 static SUBSTRATE_IR: &[u8] = include_bytes!("../../stdlib/substrate.bc");
+static SOLANA_IR: &[u8] = include_bytes!("../../stdlib/solana.bc");
 
 /// Return the stdlib as parsed llvm module. The solidity standard library is hardcoded into
 /// the solang library
-fn load_stdlib<'a>(context: &'a Context, target: &crate::Target) -> Module<'a> {
+fn load_stdlib<'a>(context: &'a Context, target: &Target) -> Module<'a> {
+    if *target == Target::Solana {
+        let memory = MemoryBuffer::create_from_memory_range(SOLANA_IR, "solana");
+
+        let module = Module::parse_bitcode_from_buffer(&memory, context).unwrap();
+
+        return module;
+    }
+
     let memory = MemoryBuffer::create_from_memory_range(STDLIB_IR, "stdlib");
 
     let module = Module::parse_bitcode_from_buffer(&memory, context).unwrap();
 
-    if let super::Target::Substrate = target {
+    if Target::Substrate == *target {
         let memory = MemoryBuffer::create_from_memory_range(SUBSTRATE_IR, "substrate");
 
         module
@@ -5257,14 +5266,34 @@ fn load_stdlib<'a>(context: &'a Context, target: &crate::Target) -> Module<'a> {
     module
 }
 
-impl crate::Target {
-    // LLVM Targett name
+impl Target {
+    /// LLVM Target name
     fn llvm_target_name(&self) -> &'static str {
-        "wasm32"
+        if *self == Target::Solana {
+            "bpfel"
+        } else {
+            "wasm32"
+        }
     }
 
-    // LLVM Target triple
+    /// LLVM Target triple
     fn llvm_target_triple(&self) -> &'static str {
-        "wasm32-unknown-unknown-wasm"
+        if *self == Target::Solana {
+            "bpfel-unknown-unknown"
+        } else {
+            "wasm32-unknown-unknown-wasm"
+        }
+    }
+
+    /// File extension
+    pub fn file_extension(&self) -> &'static str {
+        match self {
+            // Solana uses ELF dynamic shared object (BPF)
+            Target::Solana => "so",
+            // Generic target produces object file for linking
+            Target::Generic => "o",
+            // Everything else generates webassembly
+            _ => "wasm",
+        }
     }
 }

+ 535 - 0
src/emit/solana.rs

@@ -0,0 +1,535 @@
+use codegen::cfg::HashTy;
+use parser::pt;
+use sema::ast;
+use std::collections::HashMap;
+use std::str;
+
+use inkwell::context::Context;
+use inkwell::types::IntType;
+use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, UnnamedAddress};
+use inkwell::AddressSpace;
+use inkwell::OptimizationLevel;
+
+use super::ethabiencoder;
+use super::{Contract, TargetRuntime, Variable};
+
+pub struct SolanaTarget {
+    abi: ethabiencoder::EthAbiEncoder,
+}
+
+impl SolanaTarget {
+    pub fn build<'a>(
+        context: &'a Context,
+        contract: &'a ast::Contract,
+        ns: &'a ast::Namespace,
+        filename: &'a str,
+        opt: OptimizationLevel,
+    ) -> Contract<'a> {
+        let mut b = SolanaTarget {
+            abi: ethabiencoder::EthAbiEncoder {},
+        };
+
+        let mut c = Contract::new(context, contract, ns, filename, opt, None);
+
+        // externals
+        b.declare_externals(&mut c);
+
+        b.emit_functions(&mut c);
+
+        b.emit_constructor(&mut c);
+        b.emit_function(&mut c);
+
+        c.internalize(&["entrypoint", "sol_log_", "sol_alloc_free_"]);
+
+        c
+    }
+
+    fn declare_externals(&self, contract: &mut Contract) {
+        let void_ty = contract.context.void_type();
+        let u8_ptr = contract.context.i8_type().ptr_type(AddressSpace::Generic);
+        let u64_ty = contract.context.i64_type();
+
+        let function = contract.module.add_function(
+            "sol_alloc_free_",
+            u8_ptr.fn_type(&[u8_ptr.into(), u64_ty.into()], false),
+            None,
+        );
+        function
+            .as_global_value()
+            .set_unnamed_address(UnnamedAddress::Local);
+        let function = contract.module.add_function(
+            "sol_log_",
+            void_ty.fn_type(&[u8_ptr.into(), u64_ty.into()], false),
+            None,
+        );
+        function
+            .as_global_value()
+            .set_unnamed_address(UnnamedAddress::Local);
+    }
+
+    fn emit_constructor(&mut self, contract: &mut Contract) {
+        let initializer = self.emit_initializer(contract);
+
+        let function = contract.module.get_function("solang_constructor").unwrap();
+
+        let entry = contract.context.append_basic_block(function, "entry");
+
+        contract.builder.position_at_end(entry);
+
+        let argsdata = function.get_nth_param(0).unwrap().into_pointer_value();
+        let argslen = function.get_nth_param(1).unwrap().into_int_value();
+
+        // init our storage vars
+        contract.builder.build_call(initializer, &[], "");
+
+        // There is only one possible constructor
+        let ret = if let Some((cfg_no, cfg)) = contract
+            .contract
+            .cfg
+            .iter()
+            .enumerate()
+            .find(|(_, cfg)| cfg.ty == pt::FunctionTy::Constructor)
+        {
+            let mut args = Vec::new();
+
+            // insert abi decode
+            self.abi.decode(
+                contract,
+                function,
+                &mut args,
+                argsdata,
+                argslen,
+                &cfg.params,
+            );
+
+            contract
+                .builder
+                .build_call(contract.functions[&cfg_no], &args, "")
+                .try_as_basic_value()
+                .left()
+                .unwrap()
+        } else {
+            // return 0 for success
+            contract.context.i32_type().const_int(0, false).into()
+        };
+
+        contract.builder.build_return(Some(&ret));
+    }
+
+    // emit function dispatch
+    fn emit_function<'s>(&'s mut self, contract: &'s mut Contract) {
+        let function = contract.module.get_function("solang_function").unwrap();
+
+        let entry = contract.context.append_basic_block(function, "entry");
+
+        contract.builder.position_at_end(entry);
+
+        let argsdata = function.get_nth_param(0).unwrap().into_pointer_value();
+        let argslen = function.get_nth_param(1).unwrap().into_int_value();
+
+        let argsdata = contract.builder.build_pointer_cast(
+            argsdata,
+            contract.context.i32_type().ptr_type(AddressSpace::Generic),
+            "argsdata32",
+        );
+
+        self.emit_function_dispatch(
+            contract,
+            pt::FunctionTy::Function,
+            argsdata,
+            argslen,
+            function,
+            None,
+            |_| false,
+        );
+    }
+}
+
+impl<'a> TargetRuntime<'a> for SolanaTarget {
+    fn clear_storage(&self, _contract: &Contract, _function: FunctionValue, _slot: PointerValue) {
+        unimplemented!();
+    }
+
+    fn set_storage(
+        &self,
+        _contract: &Contract,
+        _function: FunctionValue,
+        _slot: PointerValue,
+        _dest: PointerValue,
+    ) {
+        unimplemented!();
+    }
+
+    fn set_storage_string(
+        &self,
+        _contract: &Contract,
+        _function: FunctionValue,
+        _slot: PointerValue,
+        _dest: PointerValue,
+    ) {
+        unimplemented!();
+    }
+
+    fn get_storage_string(
+        &self,
+        _contract: &Contract<'a>,
+        _function: FunctionValue,
+        _slot: PointerValue<'a>,
+    ) -> PointerValue<'a> {
+        unimplemented!();
+    }
+    fn get_storage_bytes_subscript(
+        &self,
+        _contract: &Contract<'a>,
+        _function: FunctionValue,
+        _slot: PointerValue<'a>,
+        _index: IntValue<'a>,
+    ) -> IntValue<'a> {
+        unimplemented!();
+    }
+    fn set_storage_bytes_subscript(
+        &self,
+        _contract: &Contract,
+        _function: FunctionValue,
+        _slot: PointerValue,
+        _index: IntValue,
+        _val: IntValue,
+    ) {
+        unimplemented!();
+    }
+    fn storage_bytes_push(
+        &self,
+        _contract: &Contract,
+        _function: FunctionValue,
+        _slot: PointerValue,
+        _val: IntValue,
+    ) {
+        unimplemented!();
+    }
+    fn storage_bytes_pop(
+        &self,
+        _contract: &Contract<'a>,
+        _function: FunctionValue,
+        _slot: PointerValue<'a>,
+    ) -> IntValue<'a> {
+        unimplemented!();
+    }
+    fn storage_string_length(
+        &self,
+        _contract: &Contract<'a>,
+        _function: FunctionValue,
+        _slot: PointerValue<'a>,
+    ) -> IntValue<'a> {
+        unimplemented!();
+    }
+
+    fn get_storage_int(
+        &self,
+        _contract: &Contract<'a>,
+        _function: FunctionValue,
+        _slot: PointerValue,
+        _ty: IntType<'a>,
+    ) -> IntValue<'a> {
+        unimplemented!();
+    }
+
+    /// sabre has no keccak256 host function, so call our implementation
+    fn keccak256_hash(
+        &self,
+        contract: &Contract,
+        src: PointerValue,
+        length: IntValue,
+        dest: PointerValue,
+    ) {
+        contract.builder.build_call(
+            contract.module.get_function("sha3").unwrap(),
+            &[
+                contract
+                    .builder
+                    .build_pointer_cast(
+                        src,
+                        contract.context.i8_type().ptr_type(AddressSpace::Generic),
+                        "src",
+                    )
+                    .into(),
+                length.into(),
+                contract
+                    .builder
+                    .build_pointer_cast(
+                        dest,
+                        contract.context.i8_type().ptr_type(AddressSpace::Generic),
+                        "dest",
+                    )
+                    .into(),
+                contract.context.i32_type().const_int(32, false).into(),
+            ],
+            "",
+        );
+    }
+
+    fn return_empty_abi(&self, contract: &Contract) {
+        // return 0 for success
+        contract
+            .builder
+            .build_return(Some(&contract.context.i32_type().const_int(0, false)));
+    }
+
+    fn return_abi<'b>(&self, _contract: &'b Contract, _data: PointerValue<'b>, _length: IntValue) {
+        unimplemented!();
+    }
+
+    fn assert_failure<'b>(&self, _contract: &'b Contract, _data: PointerValue, _length: IntValue) {
+        unimplemented!();
+    }
+
+    /// ABI encode into a vector for abi.encode* style builtin functions
+    fn abi_encode_to_vector<'b>(
+        &self,
+        _contract: &Contract<'b>,
+        _selector: Option<IntValue<'b>>,
+        _function: FunctionValue,
+        _packed: bool,
+        _args: &[BasicValueEnum<'b>],
+        _spec: &[ast::Type],
+    ) -> PointerValue<'b> {
+        unimplemented!();
+    }
+
+    fn abi_encode<'b>(
+        &self,
+        contract: &Contract<'b>,
+        selector: Option<u32>,
+        load: bool,
+        function: FunctionValue,
+        args: &[BasicValueEnum<'b>],
+        spec: &[ast::Parameter],
+    ) -> (PointerValue<'b>, IntValue<'b>) {
+        let mut offset = contract.context.i32_type().const_int(
+            spec.iter()
+                .map(|arg| self.abi.encoded_fixed_length(&arg.ty, contract.ns))
+                .sum(),
+            false,
+        );
+
+        let mut length = offset;
+
+        // now add the dynamic lengths
+        for (i, s) in spec.iter().enumerate() {
+            length = contract.builder.build_int_add(
+                length,
+                self.abi
+                    .encoded_dynamic_length(args[i], load, &s.ty, function, contract),
+                "",
+            );
+        }
+
+        if selector.is_some() {
+            length = contract.builder.build_int_add(
+                length,
+                contract
+                    .context
+                    .i32_type()
+                    .const_int(std::mem::size_of::<u32>() as u64, false),
+                "",
+            );
+        }
+
+        let encoded_data = contract
+            .builder
+            .build_call(
+                contract.module.get_function("solang_malloc").unwrap(),
+                &[length.into()],
+                "",
+            )
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_pointer_value();
+
+        // malloc returns u8*
+        let mut data = encoded_data;
+
+        if let Some(selector) = selector {
+            contract.builder.build_store(
+                contract.builder.build_pointer_cast(
+                    data,
+                    contract.context.i32_type().ptr_type(AddressSpace::Generic),
+                    "",
+                ),
+                contract
+                    .context
+                    .i32_type()
+                    .const_int(selector.to_be() as u64, false),
+            );
+
+            data = unsafe {
+                contract.builder.build_gep(
+                    data,
+                    &[contract
+                        .context
+                        .i32_type()
+                        .const_int(std::mem::size_of_val(&selector) as u64, false)],
+                    "",
+                )
+            };
+        }
+
+        // We use a little trick here. The length might or might not include the selector.
+        // The length will be a multiple of 32 plus the selector (4). So by dividing by 8,
+        // we lose the selector.
+        // contract.builder.build_call(
+        //     contract.module.get_function("__bzero8").unwrap(),
+        //     &[
+        //         data.into(),
+        //         contract
+        //             .builder
+        //             .build_int_unsigned_div(
+        //                 length,
+        //                 contract.context.i32_type().const_int(8, false),
+        //                 "",
+        //             )
+        //             .into(),
+        //     ],
+        //     "",
+        // );
+
+        let mut dynamic = unsafe { contract.builder.build_gep(data, &[offset], "") };
+
+        for (i, arg) in spec.iter().enumerate() {
+            self.abi.encode_ty(
+                contract,
+                load,
+                function,
+                &arg.ty,
+                args[i],
+                &mut data,
+                &mut offset,
+                &mut dynamic,
+            );
+        }
+
+        (encoded_data, length)
+    }
+
+    fn abi_decode<'b>(
+        &self,
+        contract: &Contract<'b>,
+        function: FunctionValue,
+        args: &mut Vec<BasicValueEnum<'b>>,
+        data: PointerValue<'b>,
+        length: IntValue<'b>,
+        spec: &[ast::Parameter],
+    ) {
+        self.abi
+            .decode(contract, function, args, data, length, spec);
+    }
+
+    fn print(&self, contract: &Contract, string_ptr: PointerValue, string_len: IntValue) {
+        let string_len64 =
+            contract
+                .builder
+                .build_int_z_extend(string_len, contract.context.i64_type(), "");
+
+        contract.builder.build_call(
+            contract.module.get_function("sol_log_").unwrap(),
+            &[string_ptr.into(), string_len64.into()],
+            "",
+        );
+    }
+
+    /// Create new contract
+    fn create_contract<'b>(
+        &mut self,
+        _contract: &Contract<'b>,
+        _function: FunctionValue,
+        _success: Option<&mut BasicValueEnum<'b>>,
+        _contract_no: usize,
+        _constructor_no: Option<usize>,
+        _address: PointerValue<'b>,
+        _args: &[BasicValueEnum],
+        _gas: IntValue<'b>,
+        _value: Option<IntValue<'b>>,
+        _salt: Option<IntValue<'b>>,
+    ) {
+        unimplemented!();
+    }
+
+    /// Call external contract
+    fn external_call<'b>(
+        &self,
+        _contract: &Contract<'b>,
+        _function: FunctionValue,
+        _success: Option<&mut BasicValueEnum<'b>>,
+        _payload: PointerValue<'b>,
+        _payload_len: IntValue<'b>,
+        _address: PointerValue<'b>,
+        _gas: IntValue<'b>,
+        _value: IntValue<'b>,
+        _ty: ast::CallTy,
+    ) {
+        unimplemented!();
+    }
+
+    /// Get return buffer for external call
+    fn return_data<'b>(&self, _contract: &Contract<'b>) -> PointerValue<'b> {
+        unimplemented!();
+    }
+
+    fn return_u32<'b>(&self, contract: &'b Contract, ret: IntValue<'b>) {
+        contract.builder.build_return(Some(&ret));
+    }
+
+    /// Value received
+    fn value_transferred<'b>(&self, contract: &Contract<'b>) -> IntValue<'b> {
+        contract.value_type().const_zero()
+    }
+
+    /// Return the current address
+    fn get_address<'b>(&self, contract: &Contract<'b>) -> IntValue<'b> {
+        contract.address_type().const_zero()
+    }
+
+    /// Return the balance for address
+    fn balance<'b>(&self, _contract: &Contract<'b>, _addr: IntValue<'b>) -> IntValue<'b> {
+        unimplemented!();
+    }
+
+    /// Terminate execution, destroy contract and send remaining funds to addr
+    fn selfdestruct<'b>(&self, _contract: &Contract<'b>, _addr: IntValue<'b>) {
+        unimplemented!();
+    }
+
+    /// Send event
+    fn send_event<'b>(
+        &self,
+        _contract: &Contract<'b>,
+        _event_no: usize,
+        _data: PointerValue<'b>,
+        _data_len: IntValue<'b>,
+        _topics: Vec<(PointerValue<'b>, IntValue<'b>)>,
+    ) {
+        unimplemented!();
+    }
+
+    /// builtin expressions
+    fn builtin<'b>(
+        &self,
+        _contract: &Contract<'b>,
+        _expr: &ast::Expression,
+        _vartab: &HashMap<usize, Variable<'b>>,
+        _function: FunctionValue<'b>,
+    ) -> BasicValueEnum<'b> {
+        unimplemented!();
+    }
+
+    /// Crypto Hash
+    fn hash<'b>(
+        &self,
+        _contract: &Contract<'b>,
+        _hash: HashTy,
+        _input: PointerValue<'b>,
+        _input_len: IntValue<'b>,
+    ) -> IntValue<'b> {
+        unimplemented!()
+    }
+}

+ 11 - 2
src/lib.rs

@@ -14,6 +14,7 @@ extern crate semver;
 extern crate serde;
 extern crate serde_derive;
 extern crate serde_json;
+extern crate tempfile;
 extern crate tiny_keccak;
 extern crate unicode_xid;
 
@@ -21,7 +22,7 @@ pub mod abi;
 pub mod codegen;
 mod emit;
 pub mod file_cache;
-pub mod link;
+pub mod linker;
 pub mod parser;
 pub mod sema;
 
@@ -42,6 +43,8 @@ pub enum Target {
     Sabre,
     /// Generate a generic object file for linking
     Generic,
+    /// Solana, see https://solana.com/
+    Solana,
 }
 
 impl fmt::Display for Target {
@@ -51,6 +54,7 @@ impl fmt::Display for Target {
             Target::Ewasm => write!(f, "ewasm"),
             Target::Sabre => write!(f, "Sawtooth Sabre"),
             Target::Generic => write!(f, "generic"),
+            Target::Solana => write!(f, "solana"),
         }
     }
 }
@@ -111,8 +115,13 @@ pub fn parse_and_resolve(filename: &str, cache: &mut FileCache, target: Target)
             Target::Substrate => 32,
             Target::Sabre => 0,    // Sabre has no address type
             Target::Generic => 20, // Same as ethereum
+            Target::Solana => 32,
+        },
+        if target == Target::Solana {
+            8 // lamports is u64
+        } else {
+            16 // value is 128 bits
         },
-        16,
     );
 
     if let Err(message) = cache.populate_cache(filename) {

+ 88 - 0
src/linker/bpf.rs

@@ -0,0 +1,88 @@
+// Use the llvm lld linker to create our solana shared object
+// This requires an lld with the solana bpf patches.
+
+// Note it is possible to convert the single ELF reloc file to a dynamic file ourselves;
+// this would require creating a dynamic program header in the elf file
+
+// Using the llvm linker does give some possibilities around linking non-Solidity files
+// and doing link time optimizations
+
+use std::fs::File;
+use std::io::Read;
+use std::io::Write;
+use std::process::Command;
+use tempfile::NamedTempFile;
+
+pub fn link(input: &[u8], name: &str) -> Vec<u8> {
+    let mut objectfile = NamedTempFile::new().expect("failed to create object temp file");
+
+    objectfile
+        .write_all(input)
+        .expect("failed to write object file to temp file");
+
+    let mut linker_script = NamedTempFile::new().expect("failed to create linker script temp file");
+
+    linker_script
+        .write_all(
+            br##"PHDRS
+{
+    text PT_LOAD  ;
+    rodata PT_LOAD ;
+    dynamic PT_DYNAMIC ;
+}
+
+SECTIONS
+{
+    . = SIZEOF_HEADERS;
+    .text : { *(.text) } :text
+    .rodata : { *(.rodata) } :rodata
+    .dynamic : { *(.dynamic) } :dynamic
+    .dynsym : { *(.dynsym) } :dynamic
+    .dynstr : { *(.dynstr) } :dynamic
+    .gnu.hash : { *(.gnu.hash) } :dynamic
+    .rel.dyn : { *(.rel.dyn) } :dynamic
+    .hash : { *(.hash) } :dynamic
+}"##,
+        )
+        .expect("failed to write linker script to temp file");
+
+    let command_line = format!(
+        "ld.lld  -z notext -shared --Bdynamic {} --entry entrypoint {} -o {}.so",
+        linker_script
+            .path()
+            .to_str()
+            .expect("temp path should be unicode"),
+        objectfile
+            .path()
+            .to_str()
+            .expect("temp path should be unicode"),
+        name,
+    );
+
+    let status = if cfg!(target_os = "windows") {
+        Command::new("cmd")
+            .args(&["/C", &command_line])
+            .status()
+            .expect("linker failed")
+    } else {
+        Command::new("sh")
+            .arg("-c")
+            .arg(command_line)
+            .status()
+            .expect("linker failed")
+    };
+
+    if !status.success() {
+        panic!("linker failed");
+    }
+
+    let mut output = Vec::new();
+    // read the whole file
+    let mut outputfile = File::open(format!("{}.so", name)).expect("output file should exist");
+
+    outputfile
+        .read_to_end(&mut output)
+        .expect("failed to read output file");
+
+    output
+}

+ 13 - 0
src/linker/mod.rs

@@ -0,0 +1,13 @@
+mod bpf;
+mod wasm;
+
+use crate::Target;
+
+/// Take an object file and turn it into a final linked binary ready for deployment
+pub fn link(input: &[u8], name: &str, target: Target) -> Vec<u8> {
+    if target == Target::Solana {
+        bpf::link(input, name)
+    } else {
+        wasm::link(input, target)
+    }
+}

+ 1 - 1
src/link.rs → src/linker/wasm.rs

@@ -36,7 +36,7 @@ pub fn link(input: &[u8], target: Target) -> Vec<u8> {
         Target::Ewasm => name == "main",
         Target::Substrate => name == "deploy" || name == "call",
         Target::Sabre => name == "entrypoint",
-        Target::Generic => unreachable!(),
+        _ => unreachable!(),
     };
 
     for c in module.custom_sections() {

+ 13 - 0
stdlib/Makefile

@@ -0,0 +1,13 @@
+CC=clang
+CFLAGS=--target=$(TARGET) -emit-llvm -O3 -ffreestanding -fno-builtin -Wall
+
+%.bc: %.c
+	$(CC) -c $(CFLAGS) $< -o $@
+
+SOLANA=solana.bc
+WASM=ripemd160.bc sha3.bc stdlib.bc substrate.bc
+
+all: $(SOLANA) $(WASM)
+
+$(SOLANA): TARGET=bpf
+$(WASM): TARGET=wasm32

二進制
stdlib/solana.bc


+ 121 - 0
stdlib/solana.c

@@ -0,0 +1,121 @@
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include "solana_sdk.h"
+
+extern int solang_constructor(const uint8_t *input, uint32_t input_len);
+extern int solang_function(const uint8_t *input, uint32_t input_len);
+
+uint64_t
+entrypoint(const uint8_t *input)
+{
+    SolAccountInfo ka[1];
+    SolParameters params = (SolParameters){.ka = ka};
+    if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka)))
+    {
+        return ERROR_INVALID_ARGUMENT;
+    }
+
+    if ((params.data_len % 32) == 0)
+    {
+        return solang_constructor(params.data, params.data_len);
+    }
+    else
+    {
+        return solang_function(params.data, params.data_len);
+    }
+
+    return SUCCESS;
+}
+
+/*
+ * Vector is used for dynamic array
+ */
+struct vector
+{
+    uint32_t len;
+    uint32_t size;
+    uint8_t data[];
+};
+
+void *__malloc(uint32_t size)
+{
+    return sol_alloc_free_(size, NULL);
+}
+
+// Create a new vector. If initial is -1 then clear the data. This is done since a null pointer valid in wasm
+struct vector *vector_new(uint32_t members, uint32_t size, uint8_t *initial)
+{
+    struct vector *v;
+    uint32_t size_array = members * size;
+
+    v = __malloc(sizeof(*v) + size_array);
+    v->len = members;
+    v->size = members;
+
+    uint8_t *data = v->data;
+
+    if ((int)initial != -1)
+    {
+        while (size_array--)
+        {
+            *data++ = *initial++;
+        }
+    }
+    else
+    {
+        while (size_array--)
+        {
+            *data++ = 0;
+        }
+    }
+
+    return v;
+}
+
+struct vector *concat(uint8_t *left, uint32_t left_len, uint8_t *right, uint32_t right_len)
+{
+    uint32_t size_array = left_len + right_len;
+    struct vector *v = __malloc(sizeof(*v) + size_array);
+    v->len = size_array;
+    v->size = size_array;
+
+    uint8_t *data = v->data;
+
+    while (left_len--)
+    {
+        *data++ = *left++;
+    }
+
+    while (right_len--)
+    {
+        *data++ = *right++;
+    }
+
+    return v;
+}
+
+// This function is used for abi decoding integers.
+// ABI encoding is big endian, and can have integers of 8 to 256 bits
+// (1 to 32 bytes). This function copies length bytes and reverses the
+// order since wasm is little endian.
+void __be32toleN(uint8_t *from, uint8_t *to, uint32_t length)
+{
+    from += 31;
+
+    do
+    {
+        *to++ = *from--;
+    } while (--length);
+}
+
+void __beNtoleN(uint8_t *from, uint8_t *to, uint32_t length)
+{
+    from += length;
+
+    do
+    {
+        *to++ = *--from;
+    } while (--length);
+}

+ 579 - 0
stdlib/solana_sdk.h

@@ -0,0 +1,579 @@
+#pragma once
+
+/**
+ * Numeric types
+ */
+#ifndef __LP64__
+#error LP64 data model required
+#endif
+
+/** Indicates the instruction was processed successfully */
+#define SUCCESS 0
+
+/**
+ * Builtin program status values occupy the upper 32 bits of the program return
+ * value.  Programs may define their own error values but they must be confined
+ * to the lower 32 bits.
+ */
+#define TO_BUILTIN(error) ((uint64_t)(error) << 32)
+
+/** Note: Not applicable to program written in C */
+#define ERROR_CUSTOM_ZERO TO_BUILTIN(1)
+/** The arguments provided to a program instruction where invalid */
+#define ERROR_INVALID_ARGUMENT TO_BUILTIN(2)
+/** An instruction's data contents was invalid */
+#define ERROR_INVALID_INSTRUCTION_DATA TO_BUILTIN(3)
+/** An account's data contents was invalid */
+#define ERROR_INVALID_ACCOUNT_DATA TO_BUILTIN(4)
+/** An account's data was too small */
+#define ERROR_ACCOUNT_DATA_TOO_SMALL TO_BUILTIN(5)
+/** An account's balance was too small to complete the instruction */
+#define ERROR_INSUFFICIENT_FUNDS TO_BUILTIN(6)
+/** The account did not have the expected program id */
+#define ERROR_INCORRECT_PROGRAM_ID TO_BUILTIN(7)
+/** A signature was required but not found */
+#define ERROR_MISSING_REQUIRED_SIGNATURES TO_BUILTIN(8)
+/** An initialize instruction was sent to an account that has already been initialized */
+#define ERROR_ACCOUNT_ALREADY_INITIALIZED TO_BUILTIN(9)
+/** An attempt to operate on an account that hasn't been initialized */
+#define ERROR_UNINITIALIZED_ACCOUNT TO_BUILTIN(10)
+/** The instruction expected additional account keys */
+#define ERROR_NOT_ENOUGH_ACCOUNT_KEYS TO_BUILTIN(11)
+/** Note: Not applicable to program written in C */
+#define ERROR_ACCOUNT_BORROW_FAILED TO_BUILTIN(12)
+/** The length of the seed is too long for address generation */
+#define MAX_SEED_LENGTH_EXCEEDED TO_BUILTIN(13)
+/** Provided seeds do not result in a valid address */
+#define INVALID_SEEDS TO_BUILTIN(14)
+
+/**
+ * Boolean type
+ */
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+
+/**
+ * Prints a string to stdout
+ */
+void sol_log_(const char *, uint64_t);
+#define sol_log(message) sol_log_(message, sol_strlen(message))
+
+/**
+ * Prints a 64 bit values represented in hexadecimal to stdout
+ */
+void sol_log_64_(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);
+#define sol_log_64 sol_log_64_
+
+/**
+ * Size of Public key in bytes
+ */
+#define SIZE_PUBKEY 32
+
+/**
+ * Public key
+ */
+typedef struct
+{
+  uint8_t x[SIZE_PUBKEY];
+} SolPubkey;
+
+/**
+ * Compares two public keys
+ *
+ * @param one First public key
+ * @param two Second public key
+ * @return true if the same
+ */
+static bool SolPubkey_same(const SolPubkey *one, const SolPubkey *two)
+{
+  for (int i = 0; i < sizeof(*one); i++)
+  {
+    if (one->x[i] != two->x[i])
+    {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Keyed Account
+ */
+typedef struct
+{
+  SolPubkey *key;      /** Public key of the account */
+  uint64_t *lamports;  /** Number of lamports owned by this account */
+  uint64_t data_len;   /** Length of data in bytes */
+  uint8_t *data;       /** On-chain data within this account */
+  SolPubkey *owner;    /** Program that owns this account */
+  uint64_t rent_epoch; /** The epoch at which this account will next owe rent */
+  bool is_signer;      /** Transaction was signed by this account's key? */
+  bool is_writable;    /** Is the account writable? */
+  bool executable;     /** This account's data contains a loaded program (and is now read-only) */
+} SolAccountInfo;
+
+/**
+ * Copies memory
+ */
+static void sol_memcpy(void *dst, const void *src, int len)
+{
+  for (int i = 0; i < len; i++)
+  {
+    *((uint8_t *)dst + i) = *((const uint8_t *)src + i);
+  }
+}
+
+/**
+ * Compares memory
+ */
+static int sol_memcmp(const void *s1, const void *s2, int n)
+{
+  for (int i = 0; i < n; i++)
+  {
+    uint8_t diff = *((uint8_t *)s1 + i) - *((const uint8_t *)s2 + i);
+    if (diff)
+    {
+      return diff;
+    }
+  }
+  return 0;
+}
+
+/**
+ * Fill a byte string with a byte value
+ */
+static void sol_memset(void *b, int c, size_t len)
+{
+  uint8_t *a = (uint8_t *)b;
+  while (len > 0)
+  {
+    *a = c;
+    a++;
+    len--;
+  }
+}
+
+/**
+ * Find length of string
+ */
+static size_t sol_strlen(const char *s)
+{
+  size_t len = 0;
+  while (*s)
+  {
+    len++;
+    s++;
+  }
+  return len;
+}
+
+/**
+ * Computes the number of elements in an array
+ */
+#define SOL_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+/**
+ * Internal memory alloc/free function
+ */
+void *sol_alloc_free_(uint64_t size, void *ptr);
+
+/**
+ * Alloc zero-initialized memory
+ */
+static void *sol_calloc(size_t nitems, size_t size)
+{
+  return sol_alloc_free_(nitems * size, 0);
+}
+
+/**
+ * Deallocates the memory previously allocated by sol_calloc
+ */
+static void sol_free(void *ptr)
+{
+  (void)sol_alloc_free_(0, ptr);
+}
+
+/**
+ * Panics
+ *
+ * Prints the line number where the panic occurred and then causes
+ * the BPF VM to immediately halt execution. No accounts' data are updated
+ */
+void sol_panic_(const char *, uint64_t, uint64_t, uint64_t);
+#define sol_panic() sol_panic_(__FILE__, sizeof(__FILE__), __LINE__, 0)
+
+/**
+ * Asserts
+ */
+#define sol_assert(expr) \
+  if (!(expr))           \
+  {                      \
+    sol_panic();         \
+  }
+
+/**
+ * Structure that the program's entrypoint input data is deserialized into.
+ */
+typedef struct
+{
+  SolAccountInfo *ka;          /** Pointer to an array of SolAccountInfo, must already
+                          point to an array of SolAccountInfos */
+  uint64_t ka_num;             /** Number of SolAccountInfo entries in `ka` */
+  const uint8_t *data;         /** pointer to the instruction data */
+  uint64_t data_len;           /** Length in bytes of the instruction data */
+  const SolPubkey *program_id; /** program_id of the currently executing program */
+} SolParameters;
+
+/**
+ * Maximum number of bytes a program may add to an account during a single realloc
+ */
+#define MAX_PERMITTED_DATA_INCREASE (1024 * 10)
+
+/**
+ * De-serializes the input parameters into usable types
+ *
+ * Use this function to deserialize the buffer passed to the program entrypoint
+ * into usable types.  This function does not perform copy deserialization,
+ * instead it populates the pointers and lengths in SolAccountInfo and data so
+ * that any modification to lamports or account data take place on the original
+ * buffer.  Doing so also eliminates the need to serialize back into the buffer
+ * at the end of the program.
+ *
+ * @param input Source buffer containing serialized input parameters
+ * @param params Pointer to a SolParameters structure
+ * @return Boolean true if successful.
+ */
+static bool sol_deserialize(
+    const uint8_t *input,
+    SolParameters *params,
+    uint64_t ka_num)
+{
+  if (NULL == input || NULL == params)
+  {
+    return false;
+  }
+  params->ka_num = *(uint64_t *)input;
+  input += sizeof(uint64_t);
+
+  for (int i = 0; i < params->ka_num; i++)
+  {
+    uint8_t dup_info = input[0];
+    input += sizeof(uint8_t);
+
+    if (i >= ka_num)
+    {
+      if (dup_info == UINT8_MAX)
+      {
+        input += sizeof(uint8_t);
+        input += sizeof(uint8_t);
+        input += sizeof(uint8_t);
+        input += 4; // padding
+        input += sizeof(SolPubkey);
+        input += sizeof(SolPubkey);
+        input += sizeof(uint64_t);
+        uint64_t data_len = *(uint64_t *)input;
+        input += sizeof(uint64_t);
+        input += data_len;
+        input += MAX_PERMITTED_DATA_INCREASE;
+        input = (uint8_t *)(((uint64_t)input + 8 - 1) & ~(8 - 1)); // padding
+        input += sizeof(uint64_t);
+      }
+      continue;
+    }
+    if (dup_info == UINT8_MAX)
+    {
+      // is signer?
+      params->ka[i].is_signer = *(uint8_t *)input != 0;
+      input += sizeof(uint8_t);
+
+      // is writable?
+      params->ka[i].is_writable = *(uint8_t *)input != 0;
+      input += sizeof(uint8_t);
+
+      // executable?
+      params->ka[i].executable = *(uint8_t *)input;
+      input += sizeof(uint8_t);
+
+      input += 4; // padding
+
+      // key
+      params->ka[i].key = (SolPubkey *)input;
+      input += sizeof(SolPubkey);
+
+      // owner
+      params->ka[i].owner = (SolPubkey *)input;
+      input += sizeof(SolPubkey);
+
+      // lamports
+      params->ka[i].lamports = (uint64_t *)input;
+      input += sizeof(uint64_t);
+
+      // account data
+      params->ka[i].data_len = *(uint64_t *)input;
+      input += sizeof(uint64_t);
+      params->ka[i].data = (uint8_t *)input;
+      input += params->ka[i].data_len;
+      input += MAX_PERMITTED_DATA_INCREASE;
+      input = (uint8_t *)(((uint64_t)input + 8 - 1) & ~(8 - 1)); // padding
+
+      // rent epoch
+      params->ka[i].rent_epoch = *(uint64_t *)input;
+      input += sizeof(uint64_t);
+    }
+    else
+    {
+      params->ka[i].is_signer = params->ka[dup_info].is_signer;
+      params->ka[i].is_writable = params->ka[dup_info].is_writable;
+      params->ka[i].executable = params->ka[dup_info].executable;
+      params->ka[i].key = params->ka[dup_info].key;
+      params->ka[i].owner = params->ka[dup_info].owner;
+      params->ka[i].lamports = params->ka[dup_info].lamports;
+      params->ka[i].data_len = params->ka[dup_info].data_len;
+      params->ka[i].data = params->ka[dup_info].data;
+      params->ka[i].rent_epoch = params->ka[dup_info].rent_epoch;
+      input += 7; // padding
+    }
+  }
+
+  params->data_len = *(uint64_t *)input;
+  input += sizeof(uint64_t);
+  params->data = input;
+  input += params->data_len;
+
+  params->program_id = (SolPubkey *)input;
+  input += sizeof(SolPubkey);
+
+  return true;
+}
+
+/**
+ * Byte array pointer and string
+ */
+typedef struct
+{
+  const uint8_t *addr; /** bytes */
+  uint64_t len;        /** number of bytes*/
+} SolBytes;
+
+/**
+ * Length of a sha256 hash result
+ */
+#define SHA256_RESULT_LENGTH 32
+
+/**
+ * Sha256
+ *
+ * @param bytes Array of byte arrays
+ * @param bytes_len Number of byte arrays
+ * @param result 32 byte array to hold the result
+ */
+static uint64_t sol_sha256(
+    const SolBytes *bytes,
+    int bytes_len,
+    const uint8_t *result);
+
+/**
+ * Account Meta
+ */
+typedef struct
+{
+  SolPubkey *pubkey; /** An account's public key */
+  bool is_writable;  /** True if the `pubkey` can be loaded as a read-write account */
+  bool is_signer;    /** True if an Instruction requires a Transaction signature matching `pubkey` */
+} SolAccountMeta;
+
+/**
+ * Instruction
+ */
+typedef struct
+{
+  SolPubkey *program_id;    /** Pubkey of the instruction processor that executes this instruction */
+  SolAccountMeta *accounts; /** Metadata for what accounts should be passed to the instruction processor */
+  uint64_t account_len;     /** Number of SolAccountMetas */
+  uint8_t *data;            /** Opaque data passed to the instruction processor */
+  uint64_t data_len;        /** Length of the data in bytes */
+} SolInstruction;
+
+/**
+ * Seed used to create a program address or passed to sol_invoke_signed
+ */
+typedef struct
+{
+  const uint8_t *addr; /** Seed bytes */
+  uint64_t len;        /** Length of the seed bytes */
+} SolSignerSeed;
+
+/**
+ * Seeds used by a signer to create a program address or passed to
+ * sol_invoke_signed
+ */
+typedef struct
+{
+  const SolSignerSeed *addr; /** An arry of a signer's seeds */
+  uint64_t len;              /** Number of seeds */
+} SolSignerSeeds;
+
+/**
+ * Create a program address
+ *
+ * @param seeds Seed bytes used to sign program accounts
+ * @param seeds_len Length of the seeds array
+ * @param Progam id of the signer
+ * @param Program address created, filled on return
+ */
+static uint64_t sol_create_program_address(
+    const SolSignerSeed *seeds,
+    int seeds_len,
+    const SolPubkey *program_id,
+    const SolPubkey *address);
+
+/**
+ * Cross-program invocation
+ *  * @{
+ */
+
+/**
+ * Invoke another program and sign for some of the keys
+ *
+ * @param instruction Instruction to process
+ * @param account_infos Accounts used by instruction
+ * @param account_infos_len Length of account_infos array
+ * @param seeds Seed bytes used to sign program accounts
+ * @param seeds_len Length of the seeds array
+ */
+static uint64_t sol_invoke_signed(
+    const SolInstruction *instruction,
+    const SolAccountInfo *account_infos,
+    int account_infos_len,
+    const SolSignerSeeds *signers_seeds,
+    int signers_seeds_len)
+{
+  uint64_t sol_invoke_signed_c(
+      const SolInstruction *instruction,
+      const SolAccountInfo *account_infos,
+      int account_infos_len,
+      const SolSignerSeeds *signers_seeds,
+      int signers_seeds_len);
+
+  return sol_invoke_signed_c(
+      instruction,
+      account_infos,
+      account_infos_len,
+      signers_seeds,
+      signers_seeds_len);
+}
+/**
+ * Invoke another program
+ *
+ * @param instruction Instruction to process
+ * @param account_infos Accounts used by instruction
+ * @param account_infos_len Length of account_infos array
+*/
+static uint64_t sol_invoke(
+    const SolInstruction *instruction,
+    const SolAccountInfo *account_infos,
+    int account_infos_len)
+{
+  const SolSignerSeeds signers_seeds[] = {{}};
+  return sol_invoke_signed(
+      instruction,
+      account_infos,
+      account_infos_len,
+      signers_seeds,
+      0);
+}
+
+/**@}*/
+
+/**
+ * Debugging utilities
+ * @{
+ */
+
+/**
+ * Prints the hexadecimal representation of a public key
+ *
+ * @param key The public key to print
+ */
+void sol_log_pubkey(
+    const SolPubkey *pubkey);
+
+/**
+ * Prints the hexadecimal representation of an array
+ *
+ * @param array The array to print
+ */
+static void sol_log_array(const uint8_t *array, int len)
+{
+  for (int j = 0; j < len; j++)
+  {
+    sol_log_64(0, 0, 0, j, array[j]);
+  }
+}
+
+/**
+ * Prints the program's input parameters
+ *
+ * @param params Pointer to a SolParameters structure
+ */
+static void sol_log_params(const SolParameters *params)
+{
+  sol_log("- Program identifier:");
+  sol_log_pubkey(params->program_id);
+
+  sol_log("- Number of KeyedAccounts");
+  sol_log_64(0, 0, 0, 0, params->ka_num);
+  for (int i = 0; i < params->ka_num; i++)
+  {
+    sol_log("  - Is signer");
+    sol_log_64(0, 0, 0, 0, params->ka[i].is_signer);
+    sol_log("  - Is writable");
+    sol_log_64(0, 0, 0, 0, params->ka[i].is_writable);
+    sol_log("  - Key");
+    sol_log_pubkey(params->ka[i].key);
+    sol_log("  - Lamports");
+    sol_log_64(0, 0, 0, 0, *params->ka[i].lamports);
+    sol_log("  - data");
+    sol_log_array(params->ka[i].data, params->ka[i].data_len);
+    sol_log("  - Owner");
+    sol_log_pubkey(params->ka[i].owner);
+    sol_log("  - Executable");
+    sol_log_64(0, 0, 0, 0, params->ka[i].executable);
+    sol_log("  - Rent Epoch");
+    sol_log_64(0, 0, 0, 0, params->ka[i].rent_epoch);
+  }
+  sol_log("- Instruction data\0");
+  sol_log_array(params->data, params->data_len);
+}
+
+/**@}*/
+
+/**
+ * Program instruction entrypoint
+ *
+ * @param input Buffer of serialized input parameters.  Use sol_deserialize() to decode
+ * @return 0 if the instruction executed successfully
+ */
+uint64_t entrypoint(const uint8_t *input);
+
+#ifdef SOL_TEST
+/**
+ * Stub log functions when building tests
+ */
+#include <stdio.h>
+void sol_log_(const char *s, uint64_t len)
+{
+  printf("sol_log: %s\n", s);
+}
+void sol_log_64(uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5)
+{
+  printf("sol_log_64: %llu, %llu, %llu, %llu, %llu\n", arg1, arg2, arg3, arg4, arg5);
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/

+ 304 - 0
tests/solana.rs

@@ -0,0 +1,304 @@
+//
+extern crate byteorder;
+extern crate ethabi;
+extern crate ethereum_types;
+extern crate libc;
+extern crate solana_rbpf;
+extern crate solang;
+
+mod solana_helpers;
+
+use byteorder::{LittleEndian, WriteBytesExt};
+use ethabi::Token;
+use libc::c_char;
+use solana_helpers::allocator_bump::BPFAllocator;
+use solana_rbpf::{
+    error::EbpfError,
+    memory_region::{translate_addr, MemoryRegion},
+    user_error::UserError,
+    vm::{Config, EbpfVm, SyscallObject},
+};
+use solang::{compile, file_cache::FileCache, sema::diagnostics, Target};
+use std::alloc::Layout;
+use std::io::Write;
+use std::mem::align_of;
+
+fn build_solidity(src: &'static str) -> VM {
+    let mut cache = FileCache::new();
+
+    cache.set_file_contents("test.sol".to_string(), src.to_string());
+
+    let (res, ns) = compile(
+        "test.sol",
+        &mut cache,
+        inkwell::OptimizationLevel::Default,
+        Target::Solana,
+    );
+
+    diagnostics::print_messages(&mut cache, &ns, false);
+
+    for v in &res {
+        println!("contract size:{}", v.0.len());
+    }
+
+    assert_eq!(res.is_empty(), false);
+
+    // resolve
+    let (code, abi) = res.last().unwrap().clone();
+
+    VM {
+        code,
+        abi: ethabi::Contract::load(abi.as_bytes()).unwrap(),
+        printbuf: String::new(),
+    }
+}
+
+const MAX_PERMITTED_DATA_INCREASE: usize = 10 * 1024;
+
+fn serialize_parameters(input: &[u8]) -> Vec<u8> {
+    let mut v: Vec<u8> = Vec::new();
+
+    // ka_num
+    v.write_u64::<LittleEndian>(1).unwrap();
+    // dup_info
+    v.write_u8(0xff).unwrap();
+    // signer
+    v.write_u8(1).unwrap();
+    // is_writable
+    v.write_u8(1).unwrap();
+    // executable
+    v.write_u8(1).unwrap();
+    // padding
+    v.write_all(&[0u8; 4]).unwrap();
+    // key
+    v.write_all(&[0u8; 32]).unwrap();
+    // owner
+    v.write_all(&[0u8; 32]).unwrap();
+    // lamports
+    v.write_u64::<LittleEndian>(0).unwrap();
+
+    // account data
+    // data len
+    v.write_u64::<LittleEndian>(0).unwrap();
+    v.write_all(&[0u8; MAX_PERMITTED_DATA_INCREASE]).unwrap();
+
+    let padding = v.len() % 8;
+    if padding != 0 {
+        let mut p = Vec::new();
+        p.resize(8 - padding, 0);
+        v.extend_from_slice(&p);
+    }
+    // rent epoch
+    v.write_u64::<LittleEndian>(0).unwrap();
+
+    // calldata
+    v.write_u64::<LittleEndian>(input.len() as u64).unwrap();
+    v.write_all(input).unwrap();
+
+    // program id
+    v.write_all(&[0u8; 32]).unwrap();
+
+    v
+}
+
+struct VM {
+    code: Vec<u8>,
+    abi: ethabi::Contract,
+    printbuf: String,
+}
+
+struct Printer<'a> {
+    buf: &'a mut String,
+}
+
+impl<'a> SyscallObject<UserError> for Printer<'a> {
+    fn call(
+        &mut self,
+        vm_addr: u64,
+        len: u64,
+        _arg3: u64,
+        _arg4: u64,
+        _arg5: u64,
+        ro_regions: &[MemoryRegion],
+        _rw_regions: &[MemoryRegion],
+    ) -> Result<u64, EbpfError<UserError>> {
+        let host_addr = translate_addr(vm_addr, len as usize, "Load", 0, ro_regions)?;
+        let c_buf: *const c_char = host_addr as *const c_char;
+        unsafe {
+            for i in 0..len {
+                let c = std::ptr::read(c_buf.offset(i as isize));
+                if c == 0 {
+                    break;
+                }
+            }
+            let message = std::str::from_utf8(std::slice::from_raw_parts(
+                host_addr as *const u8,
+                len as usize,
+            ))
+            .unwrap();
+            println!("log: {}", message);
+            self.buf.push_str(message);
+            Ok(0)
+        }
+    }
+}
+
+// Shamelessly stolen from solana source
+
+/// Dynamic memory allocation syscall called when the BPF program calls
+/// `sol_alloc_free_()`.  The allocator is expected to allocate/free
+/// from/to a given chunk of memory and enforce size restrictions.  The
+/// memory chunk is given to the allocator during allocator creation and
+/// information about that memory (start address and size) is passed
+/// to the VM to use for enforcement.
+pub struct SyscallAllocFree {
+    allocator: BPFAllocator,
+}
+
+const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
+pub const MM_HEAP_START: u64 = 0x300000000;
+/// Start of the input buffers in the memory map
+
+impl SyscallObject<UserError> for SyscallAllocFree {
+    fn call(
+        &mut self,
+        size: u64,
+        free_addr: u64,
+        _arg3: u64,
+        _arg4: u64,
+        _arg5: u64,
+        _ro_regions: &[MemoryRegion],
+        _rw_regions: &[MemoryRegion],
+    ) -> Result<u64, EbpfError<UserError>> {
+        let align = align_of::<u128>();
+        let layout = match Layout::from_size_align(size as usize, align) {
+            Ok(layout) => layout,
+            Err(_) => return Ok(0),
+        };
+        if free_addr == 0 {
+            Ok(self.allocator.alloc(layout))
+        } else {
+            self.allocator.dealloc(free_addr, layout);
+            Ok(0)
+        }
+    }
+}
+
+impl VM {
+    fn execute(&self, buf: &mut String, calldata: &[u8]) {
+        println!("running bpf with calldata:{}", hex::encode(calldata));
+
+        let executable =
+            EbpfVm::<UserError>::create_executable_from_elf(&self.code, None).expect("should work");
+        let mut vm = EbpfVm::<UserError>::new(executable.as_ref(), Config::default()).unwrap();
+
+        vm.register_syscall_with_context_ex("sol_log_", Box::new(Printer { buf }))
+            .unwrap();
+
+        let heap = vec![0_u8; DEFAULT_HEAP_SIZE];
+        let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START);
+        vm.register_syscall_with_context_ex(
+            "sol_alloc_free_",
+            Box::new(SyscallAllocFree {
+                allocator: BPFAllocator::new(heap, MM_HEAP_START),
+            }),
+        )
+        .unwrap();
+
+        let parameter_bytes = serialize_parameters(&calldata);
+
+        let res = vm
+            .execute_program(&parameter_bytes, &[], &[heap_region])
+            .unwrap();
+
+        assert_eq!(res, 0);
+    }
+
+    fn constructor(&mut self, args: &[Token]) {
+        let calldata = if let Some(constructor) = &self.abi.constructor {
+            constructor.encode_input(Vec::new(), args).unwrap()
+        } else {
+            Vec::new()
+        };
+
+        let mut buf = String::new();
+        self.execute(&mut buf, &calldata);
+        self.printbuf = buf;
+    }
+
+    fn function(&mut self, name: &str, args: &[Token]) {
+        let calldata = match self.abi.functions[name][0].encode_input(args) {
+            Ok(n) => n,
+            Err(x) => panic!(format!("{}", x)),
+        };
+
+        let mut buf = String::new();
+        self.execute(&mut buf, &calldata);
+        self.printbuf = buf;
+    }
+}
+
+#[test]
+fn simple() {
+    let mut vm = build_solidity(
+        r#"
+        contract foo {
+            constructor() {
+                print("Hello from constructor");
+            }
+
+            function test() public {
+                print("Hello from function");
+            }
+        }"#,
+    );
+
+    vm.constructor(&[]);
+
+    assert_eq!(vm.printbuf, "Hello from constructor");
+
+    vm.printbuf = String::new();
+
+    vm.function("test", &[]);
+
+    assert_eq!(vm.printbuf, "Hello from function");
+}
+
+/*
+#[test]
+fn basic() {
+    let mut vm = build_solidity(
+        r#"
+        contract foo {
+            function test(uint32 x, uint32 y) public {
+                if (x == 10) {
+                    print("x is 10");
+                }
+                if (x + 10 == y) {
+                    print("x plus 10 is y");
+                }
+            }
+        }"#,
+    );
+
+    vm.function(
+        "test",
+        &[
+            ethabi::Token::Int(ethereum_types::U256::from(10)),
+            ethabi::Token::Int(ethereum_types::U256::from(10)),
+        ],
+    );
+
+    assert_eq!(vm.printbuf, "Hello from function");
+
+    vm.function(
+        "test",
+        &[
+            ethabi::Token::Int(ethereum_types::U256::from(10)),
+            ethabi::Token::Int(ethereum_types::U256::from(20)),
+        ],
+    );
+
+    assert_eq!(vm.printbuf, "Hello from function");
+}
+*/

+ 44 - 0
tests/solana_helpers/allocator_bump.rs

@@ -0,0 +1,44 @@
+use std::alloc::Layout;
+
+#[derive(Debug)]
+pub struct BPFAllocator {
+    heap: Vec<u8>,
+    start: u64,
+    len: u64,
+    pos: u64,
+}
+
+impl BPFAllocator {
+    pub fn new(heap: Vec<u8>, virtual_address: u64) -> Self {
+        let len = heap.len() as u64;
+        Self {
+            heap,
+            start: virtual_address,
+            len,
+            pos: 0,
+        }
+    }
+}
+
+impl BPFAllocator {
+    pub fn alloc(&mut self, layout: Layout) -> u64 {
+        let bytes_to_align = (self.pos as *const u8).align_offset(layout.align()) as u64;
+        if self
+            .pos
+            .saturating_add(layout.size() as u64)
+            .saturating_add(bytes_to_align)
+            <= self.len
+        {
+            self.pos += bytes_to_align;
+            let addr = self.start + self.pos;
+            self.pos += layout.size() as u64;
+            addr
+        } else {
+            panic!("out of heap");
+        }
+    }
+
+    pub fn dealloc(&mut self, _addr: u64, _layout: Layout) {
+        // It's a bump allocator, free not supported
+    }
+}

+ 1 - 0
tests/solana_helpers/mod.rs

@@ -0,0 +1 @@
+pub mod allocator_bump;