diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 66cc6b2073..3d9222a2bb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -824,7 +824,24 @@ jobs: cairo-compile cairo_programs/array_sum.cairo --no_debug_info --output cairo_programs/array_sum.json cd examples/wasm-demo wasm-pack build --target=web - + + wasm-demo-cairo1: + name: Build the wasm demo cairo1 + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install cairo-lang and deps + run: | + npm install -g wasm-pack + - name: Build wasm-demo-cairo1 + run: | + cd examples/wasm-demo-cairo1 + wasm-pack build --target=web + compare-factorial-outputs-all-layouts: name: Compare factorial outputs for all layouts needs: [ build-programs, build-release ] diff --git a/CHANGELOG.md b/CHANGELOG.md index b07262604c..a6152c583e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -200,6 +200,8 @@ * chore: bump `cairo-lang-` dependencies to 2.8.0 [#1833](https://github.com/lambdaclass/cairo-vm/pull/1833/files) * chore: update Rust required version to 1.80.0 +* chore: make cairo 1.0 compatible with wasm [#1830](https://github.com/lambdaclass/cairo-vm/pull/1830) + * fix: Added the following VM fixes: [#1820](https://github.com/lambdaclass/cairo-vm/pull/1820) * Fix zero segment location. * Fix has_zero_segment naming. diff --git a/Cargo.lock b/Cargo.lock index 902fc28413..4ae9601c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -974,6 +974,7 @@ dependencies = [ "rstest", "serde_json", "thiserror", + "thiserror-no-std", ] [[package]] @@ -3573,6 +3574,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wasm-demo-cairo1" +version = "1.0.1" +dependencies = [ + "cairo-lang-sierra", + "cairo-vm", + "cairo1-run", + "console_error_panic_hook", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-test", +] + [[package]] name = "web-sys" version = "0.3.77" diff --git a/Cargo.toml b/Cargo.toml index dff1962cb3..7b747e6d3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "vm", "hint_accountant", "examples/wasm-demo", + "examples/wasm-demo-cairo1", "cairo1-run", "cairo-vm-tracer", "examples/hyper_threading", @@ -25,6 +26,7 @@ keywords = ["starknet", "cairo", "vm", "wasm", "no_std"] [workspace.dependencies] cairo-vm = { path = "./vm", version = "3.0.0-rc.1", default-features = false } +cairo1-run = { path = "./cairo1-run", version = "3.0.0-rc.1", default-features = false } cairo-vm-tracer = { path = "./cairo-vm-tracer", version = "3.0.0-rc.1", default-features = false } mimalloc = { version = "0.1.37", default-features = false } num-bigint = { version = "0.4", default-features = false, features = [ @@ -89,6 +91,9 @@ lto = "fat" # Tell `rustc` to optimize for small code size. opt-level = "s" +[profile.release.package.wasm-demo-cairo1] +opt-level = "s" + [profile.test.package.proptest] opt-level = 3 diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml index f8060a291e..f9496f5c28 100644 --- a/cairo1-run/Cargo.toml +++ b/cairo1-run/Cargo.toml @@ -9,7 +9,8 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cairo-vm = { workspace = true, features = ["std", "cairo-1-hints", "clap"] } +cairo-vm = {workspace = true, features = ["cairo-1-hints", "clap"]} + serde_json = { workspace = true } cairo-lang-sierra-type-size = { version = "2.12.0-dev.0", default-features = false } @@ -29,3 +30,9 @@ num-bigint.workspace = true [features] mod_builtin = ["cairo-vm/mod_builtin"] +cli = ["dep:thiserror", "cairo-vm/std"] + +[[bin]] +name = "cairo1-run" +path = "./src/main.rs" +required-features = ["cli"] diff --git a/cairo1-run/Makefile b/cairo1-run/Makefile index 7c7437a24f..660533f17f 100644 --- a/cairo1-run/Makefile +++ b/cairo1-run/Makefile @@ -3,10 +3,10 @@ CAIRO_1_FOLDER=../cairo_programs/cairo-1-programs $(CAIRO_1_FOLDER)/%.trace: $(CAIRO_1_FOLDER)/%.cairo - cargo run --release -F mod_builtin $< --trace_file $@ --layout all_cairo + cargo run --release --features cli -F mod_builtin $< --trace_file $@ --layout all_cairo $(CAIRO_1_FOLDER)/%.memory: $(CAIRO_1_FOLDER)/%.cairo - cargo run --release -F mod_builtin $< --memory_file $@ --layout all_cairo + cargo run --release --features cli -F mod_builtin $< --memory_file $@ --layout all_cairo CAIRO_1_PROGRAMS=$(wildcard ../cairo_programs/cairo-1-programs/*.cairo) TRACES:=$(patsubst $(CAIRO_1_FOLDER)/%.cairo, $(CAIRO_1_FOLDER)/%.trace, $(CAIRO_1_PROGRAMS)) @@ -20,7 +20,7 @@ deps: run: $(TRACES) $(MEMORY) test: - cargo test + cargo test --features cli clean: rm -rf corelib diff --git a/cairo1-run/README.md b/cairo1-run/README.md index 0aa67c0b22..1bd6eb98f7 100644 --- a/cairo1-run/README.md +++ b/cairo1-run/README.md @@ -8,6 +8,7 @@ To install the required dependencies(cairo corelib) run ```bash make deps +make deps ``` Now that you have the dependencies necessary to run the tests, you can run: @@ -16,18 +17,17 @@ Now that you have the dependencies necessary to run the tests, you can run: make test ``` -To execute a Cairo 1 program (either as Cairo 1 source file or Sierra) +To execute a Cairo 1 program (either as Cairo 1 source file or Sierra). Make sure the `cli` feature is active in order to use `cairo1-run` as a binary. ```bash -cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo +cargo run --features cli ../cairo_programs/cairo-1-programs/fibonacci.cairo ``` Arguments to generate the trace and memory files ```bash -cargo run ../cairo_programs/cairo-1-programs/fibonacci.cairo --trace_file ../cairo_programs/cairo-1-programs/fibonacci.trace --memory_file ../cairo_programs/cairo-1-programs/fibonacci.memory +cargo run --features cli ../cairo_programs/cairo-1-programs/fibonacci.cairo --trace_file ../cairo_programs/cairo-1-programs/fibonacci.trace --memory_file ../cairo_programs/cairo-1-programs/fibonacci.memory ``` - To pass arguments to `main` * Separate arguments with a whitespace inbetween @@ -37,7 +37,7 @@ Example: ```bash -cargo run ../cairo_programs/cairo-1-programs/with_input/array_input_sum.cairo --layout all_cairo --args '2 [1 2 3 4] 0 [9 8]' +cargo run --features cli ../cairo_programs/cairo-1-programs/with_input/array_input_sum.cairo --layout all_cairo --args '2 [1 2 3 4] 0 [9 8]' ``` @@ -90,7 +90,7 @@ Then run the compiled project's sierra file located at `project_name/target/proj Example: ```bash - cargo run path-to-project/target/project_name.sierra.json + cargo run --features cli path-to-project/target/project_name.sierra.json ``` # Known bugs & issues diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 51418afbc3..6cd3c25ce0 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -38,6 +38,7 @@ use cairo_vm::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, math_utils::signed_felt, serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams, ReferenceManager}, + stdlib::{cmp, collections::HashMap, iter::Peekable}, types::{ builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName, program::Program, relocatable::MaybeRelocatable, @@ -52,7 +53,6 @@ use cairo_vm::{ use itertools::{chain, Itertools}; use num_bigint::{BigInt, Sign}; use num_traits::{cast::ToPrimitive, Zero}; -use std::{collections::HashMap, iter::Peekable}; /// Representation of a cairo argument /// Can consist of a single Felt or an array of Felts @@ -1415,7 +1415,7 @@ fn serialize_output_inner<'a>( let mut max_variant_size = 0; for variant in &info.variants { let variant_size = type_sizes.get(variant).unwrap(); - max_variant_size = std::cmp::max(max_variant_size, *variant_size) + max_variant_size = cmp::max(max_variant_size, *variant_size) } for _ in 0..max_variant_size - type_sizes.get(variant_type_id).unwrap() { // Remove padding diff --git a/cairo1-run/src/error.rs b/cairo1-run/src/error.rs index 49f2c18cb9..f0ac6d7dcf 100644 --- a/cairo1-run/src/error.rs +++ b/cairo1-run/src/error.rs @@ -10,12 +10,16 @@ use cairo_vm::{ }, Felt252, }; +#[cfg(feature = "cli")] use thiserror::Error; +#[cfg(not(feature = "cli"))] +use thiserror_no_std::Error; #[derive(Debug, Error)] pub enum Error { #[error("Invalid arguments")] Cli(#[from] clap::Error), + #[cfg(feature = "cli")] #[error("Failed to interact with the file system")] IO(#[from] std::io::Error), #[error(transparent)] diff --git a/examples/wasm-demo-cairo1/Cargo.toml b/examples/wasm-demo-cairo1/Cargo.toml new file mode 100644 index 0000000000..648d63ceb7 --- /dev/null +++ b/examples/wasm-demo-cairo1/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "wasm-demo-cairo1" +description = "A demo using cairo-vm in a WASM environment" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme = "README.md" +keywords.workspace = true +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +serde_json.workspace = true +wasm-bindgen = "0.2.87" +cairo-lang-sierra = { workspace = true } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.6", optional = true } + +cairo-vm = { workspace = true } +cairo1-run = { workspace = true } + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" diff --git a/examples/wasm-demo-cairo1/README.md b/examples/wasm-demo-cairo1/README.md new file mode 100644 index 0000000000..5b3a466c53 --- /dev/null +++ b/examples/wasm-demo-cairo1/README.md @@ -0,0 +1,58 @@ +# Demo of `cairo-vm` on WebAssembly + +While cairo-vm is compatible with WebAssembly, it doesn't implement any bindings to it. +Instead, create a new WebAssembly crate with cairo-vm and cairo1-run as dependencies and implement the required functionality there. + +Since mimalloc is not automatically compilable to WebAssembly, the cairo-vm dependency should disable the default features, which will in turn disable mimalloc. + +A working example is provided in this repository. + +## Dependencies + +To compile and run the example you need: + +- an either Cairo 1 or Cairo 2 compiler +- the _wasm-pack_ crate +- some HTTP server (for example: the `live-server` npm module) + +> **Note** +> The first two dependencies can be installed via the repository's installation script (see ["Installation script"](../../README.md#installation-script)) + +## Building + +To build the example, first compile your Cairo 1 / 2 program: + +Cairo 1 + +```sh +../../cairo1/bin/cairo-compile -r ./bitwise.cairo bitwise.sierra +``` + +Cairo 2 + +```sh +../../cairo2/bin/cairo-compile -r ./bitwise.cairo bitwise.sierra +``` + +> It's important to use the `-r` flag. If not, the `main` function won't be recognized. + +And then the WebAssembly package: + +```sh +wasm-pack build --target=web +``` + +This will generate a javascript module that is directly loadable by the browser. + +## Running + +To run the example webpage, you need to run an HTTP server. +For example, using the _live-server_ npm module: + +```sh +# while in /examples/wasm-demo-cairo1 +npx live-server +``` + +> **Warning** +> Trying to run `index.html` directly (i.e. URL starts with `file://`) will result in a CORS error. diff --git a/examples/wasm-demo-cairo1/bitwise.cairo b/examples/wasm-demo-cairo1/bitwise.cairo new file mode 100644 index 0000000000..5b772d31c6 --- /dev/null +++ b/examples/wasm-demo-cairo1/bitwise.cairo @@ -0,0 +1,12 @@ +fn main() -> u128 { + let a = 1234_u128; + let b = 5678_u128; + + let c0 = a & b; + let c1 = a ^ b; + let c2 = a | b; + + let c3 = c0 + c1 + c2; + + c3 +} diff --git a/examples/wasm-demo-cairo1/bitwise.sierra b/examples/wasm-demo-cairo1/bitwise.sierra new file mode 100644 index 0000000000..e1131d67ec --- /dev/null +++ b/examples/wasm-demo-cairo1/bitwise.sierra @@ -0,0 +1,136 @@ +type u128 = u128; +type Bitwise = Bitwise; +type RangeCheck = RangeCheck; +type Tuple = Struct; +type felt252 = felt252; +type Array = Array; +type core::PanicResult::<(core::integer::u128,)> = Enum, Tuple, Array>; +type core::result::Result:: = Enum, u128, u128>; + +libfunc u128_const<1234> = u128_const<1234>; +libfunc u128_const<5678> = u128_const<5678>; +libfunc store_temp = store_temp; +libfunc dup = dup; +libfunc bitwise = bitwise; +libfunc drop = drop; +libfunc store_temp = store_temp; +libfunc function_call = function_call; +libfunc store_temp = store_temp; +libfunc enum_match> = enum_match>; +libfunc branch_align = branch_align; +libfunc struct_deconstruct> = struct_deconstruct>; +libfunc struct_construct> = struct_construct>; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp> = store_temp>; +libfunc enum_init, 1> = enum_init, 1>; +libfunc u128_overflowing_add = u128_overflowing_add; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc enum_init, 1> = enum_init, 1>; +libfunc felt252_const<39878429859757942499084499860145094553463> = felt252_const<39878429859757942499084499860145094553463>; +libfunc rename> = rename>; +libfunc store_temp = store_temp; +libfunc function_call::expect::> = function_call::expect::>; +libfunc enum_match> = enum_match>; +libfunc drop = drop; +libfunc array_new = array_new; +libfunc array_append = array_append; + +u128_const<1234>() -> ([2]); +u128_const<5678>() -> ([3]); +store_temp([2]) -> ([2]); +dup([2]) -> ([2], [8]); +store_temp([3]) -> ([3]); +dup([3]) -> ([3], [9]); +bitwise([1], [8], [9]) -> ([4], [5], [6], [7]); +drop([6]) -> (); +drop([7]) -> (); +dup([2]) -> ([2], [14]); +dup([3]) -> ([3], [15]); +bitwise([4], [14], [15]) -> ([10], [11], [12], [13]); +drop([11]) -> (); +drop([13]) -> (); +bitwise([10], [2], [3]) -> ([16], [17], [18], [19]); +drop([17]) -> (); +drop([18]) -> (); +store_temp([0]) -> ([22]); +store_temp([5]) -> ([23]); +store_temp([12]) -> ([24]); +function_call([22], [23], [24]) -> ([20], [21]); +store_temp([19]) -> ([19]); +store_temp([16]) -> ([16]); +enum_match>([21]) { fallthrough([25]) 45([26]) }; +branch_align() -> (); +struct_deconstruct>([25]) -> ([27]); +store_temp([20]) -> ([30]); +store_temp([27]) -> ([31]); +store_temp([19]) -> ([32]); +function_call([30], [31], [32]) -> ([28], [29]); +enum_match>([29]) { fallthrough([33]) 39([34]) }; +branch_align() -> (); +struct_deconstruct>([33]) -> ([35]); +struct_construct>([35]) -> ([36]); +enum_init, 0>([36]) -> ([37]); +store_temp([28]) -> ([38]); +store_temp([16]) -> ([39]); +store_temp>([37]) -> ([40]); +return([38], [39], [40]); +branch_align() -> (); +enum_init, 1>([34]) -> ([41]); +store_temp([28]) -> ([42]); +store_temp([16]) -> ([43]); +store_temp>([41]) -> ([44]); +return([42], [43], [44]); +branch_align() -> (); +drop([19]) -> (); +enum_init, 1>([26]) -> ([45]); +store_temp([20]) -> ([46]); +store_temp([16]) -> ([47]); +store_temp>([45]) -> ([48]); +return([46], [47], [48]); +u128_overflowing_add([0], [1], [2]) { fallthrough([3], [4]) 58([5], [6]) }; +branch_align() -> (); +enum_init, 0>([4]) -> ([7]); +store_temp([3]) -> ([8]); +store_temp>([7]) -> ([9]); +jump() { 62() }; +branch_align() -> (); +enum_init, 1>([6]) -> ([10]); +store_temp([5]) -> ([8]); +store_temp>([10]) -> ([9]); +felt252_const<39878429859757942499084499860145094553463>() -> ([11]); +rename>([9]) -> ([13]); +store_temp([11]) -> ([14]); +function_call::expect::>([13], [14]) -> ([12]); +enum_match>([12]) { fallthrough([15]) 74([16]) }; +branch_align() -> (); +struct_deconstruct>([15]) -> ([17]); +struct_construct>([17]) -> ([18]); +enum_init, 0>([18]) -> ([19]); +store_temp([8]) -> ([20]); +store_temp>([19]) -> ([21]); +return([20], [21]); +branch_align() -> (); +enum_init, 1>([16]) -> ([22]); +store_temp([8]) -> ([23]); +store_temp>([22]) -> ([24]); +return([23], [24]); +enum_match>([0]) { fallthrough([2]) 86([3]) }; +branch_align() -> (); +drop([1]) -> (); +struct_construct>([2]) -> ([4]); +enum_init, 0>([4]) -> ([5]); +store_temp>([5]) -> ([6]); +return([6]); +branch_align() -> (); +drop([3]) -> (); +array_new() -> ([7]); +array_append([7], [1]) -> ([8]); +enum_init, 1>([8]) -> ([9]); +store_temp>([9]) -> ([10]); +return([10]); + +bitwise::bitwise::main@0([0]: RangeCheck, [1]: Bitwise) -> (RangeCheck, Bitwise, core::PanicResult::<(core::integer::u128,)>); +core::integer::U128Add::add@52([0]: RangeCheck, [1]: u128, [2]: u128) -> (RangeCheck, core::PanicResult::<(core::integer::u128,)>); +core::result::ResultTraitImpl::::expect::@79([0]: core::result::Result::, [1]: felt252) -> (core::PanicResult::<(core::integer::u128,)>); diff --git a/examples/wasm-demo-cairo1/index.html b/examples/wasm-demo-cairo1/index.html new file mode 100644 index 0000000000..beb451b259 --- /dev/null +++ b/examples/wasm-demo-cairo1/index.html @@ -0,0 +1,28 @@ + + + + + + + + Cairo WebAssembly Demo + + + + + +

Result:

+ + diff --git a/examples/wasm-demo-cairo1/src/lib.rs b/examples/wasm-demo-cairo1/src/lib.rs new file mode 100644 index 0000000000..00ff0a2407 --- /dev/null +++ b/examples/wasm-demo-cairo1/src/lib.rs @@ -0,0 +1,58 @@ +mod utils; + +use cairo1_run::{cairo_run_program, Cairo1RunConfig}; +use cairo_lang_sierra::ProgramParser; +use cairo_vm::types::layout_name::LayoutName; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(msg: &str); +} + +#[wasm_bindgen(start)] +pub fn start() { + crate::utils::set_panic_hook(); +} + +// TODO: check why this is needed. Seems wasm-bindgen expects us to use +// `std::error::Error` even if it's not yet in `core` +macro_rules! wrap_error { + ($xp: expr) => { + $xp.map_err(|e| JsError::new(e.to_string().as_str())) + }; +} + +#[wasm_bindgen(js_name = runCairoProgram)] +pub fn run_cairo_program() -> Result { + let cairo_run_config = Cairo1RunConfig { + layout: LayoutName::all_cairo, + relocate_mem: true, + trace_enabled: true, + serialize_output: true, + ..Default::default() + }; + + // using cairo-lang 1.1.1 and wasm-demo-cairo1/bitwise.sierra + let sierra_program = match serde_json::from_slice(include_bytes!("../bitwise.sierra")) { + Ok(sierra) => sierra, + Err(_) => { + let program_str = include_str!("../bitwise.sierra"); + + let parser = ProgramParser::new(); + parser + .parse(program_str) + .map_err(|e| e.map_token(|t| t.to_string()))? + } + }; + + let (_, _, serielized_output_option) = + wrap_error!(cairo_run_program(&sierra_program, cairo_run_config))?; + + let output = serielized_output_option.unwrap(); + + log(&output); + + Ok(output) +} diff --git a/examples/wasm-demo-cairo1/src/utils.rs b/examples/wasm-demo-cairo1/src/utils.rs new file mode 100644 index 0000000000..b1d7929dc9 --- /dev/null +++ b/examples/wasm-demo-cairo1/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +}