diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 7f3d378517..89dc89233c 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -17,6 +17,7 @@ cfg-if = "0.1" cranelift-codegen = { path = "lib/codegen", version = "0.17.0" } cranelift-reader = { path = "lib/reader", version = "0.17.0" } cranelift-frontend = { path = "lib/frontend", version = "0.17.0" } +cranelift-serde = { path = "lib/serde", version = "0.17.0", optional = true } cranelift-wasm = { path = "lib/wasm", version = "0.17.0", optional = true } cranelift-native = { path = "lib/native", version = "0.17.0" } cranelift-filetests = { path = "lib/filetests", version = "0.17.0" } diff --git a/lib/serde/Cargo.toml b/lib/serde/Cargo.toml new file mode 100644 index 0000000000..248276c93c --- /dev/null +++ b/lib/serde/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "cranelift-serde" +version = "0.17.0" +authors = ["The Cranelift Project Developers"] +description = "Serializer/Deserializer for Cranelift IR" +repository = "https://github.com/CraneStation/cranelift" +license = "Apache-2.0" +readme = "README.md" +keywords = ["webassembly", "serde"] + +[[bin]] +name = "clif-json" +path = "src/clif-json.rs" + +[dependencies] +cfg-if = "0.1" +filecheck = "0.3.0" +docopt = "1" +serde = "1.0.8" +serde_derive = "1.0.8" +serde_json = "1.0" +term = "0.5.1" +target-lexicon = "0.0.2" +wasmparser = { version = "0.17.0", default-features = false } +cranelift-codegen = { path = "../codegen", version = "0.17.0", default-features = false } +cranelift-reader = { path = "../reader", version = "0.17.0", default-features = false } +cranelift-frontend = { path = "../frontend", version = "0.17.0", default-features = false } +failure = { version = "0.1.1", default-features = false, features = ["derive"] } +failure_derive = { version = "0.1.1", default-features = false } + +[features] +default = ["std"] +std = ["cranelift-codegen/std", "cranelift-frontend/std", "wasmparser/std", "target-lexicon/std"] +core = ["cranelift-codegen/core", "cranelift-frontend/core", "wasmparser/core"] + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "CraneStation/cranelift" } diff --git a/lib/serde/README.md b/lib/serde/README.md new file mode 100644 index 0000000000..89b54cca72 --- /dev/null +++ b/lib/serde/README.md @@ -0,0 +1,30 @@ +This crate performs serialization of the [Cranelift](https://crates.io/crates/cranelift) IR. + +This crate is structured as an optional ability to serialize and deserialize cranelift IR into JSON format. + +Status +------ + +Cranelift IR can be serialized into JSON. + +Deserialize is a work in progress, as it currently deserializes into the serializable data structure that can be utilized by serde instead of the actual Cranelift IR data structure. + + +Building and Using Cranelift Serde +---------------------------------- + +clif-json usage: + + clif-json serialize [-p] + clif-json deserialize + +Where the -p flag outputs Cranelift IR as pretty JSON. + +For example to build and use clif-json: + +``` {.sourceCode .sh} +cd lib/serde +cargo build +clif-json serialize -p test.clif +``` + diff --git a/lib/serde/src/clif-json.rs b/lib/serde/src/clif-json.rs new file mode 100644 index 0000000000..eb991343ce --- /dev/null +++ b/lib/serde/src/clif-json.rs @@ -0,0 +1,121 @@ +#![deny(trivial_numeric_casts)] +#![warn(unused_import_braces, unstable_features, unused_extern_crates)] +#![cfg_attr( + feature = "cargo-clippy", + warn( + float_arithmetic, mut_mut, nonminimal_bool, option_map_unwrap_or, option_map_unwrap_or_else, + unicode_not_nfc, use_self + ) +)] + +extern crate cranelift_reader; +extern crate docopt; +#[macro_use] +extern crate serde_derive; +extern crate cranelift_codegen; + +extern crate serde_json; + +use cranelift_reader::parse_functions; +use docopt::Docopt; +use std::fs::File; +use std::io::prelude::*; +use std::io::{self, Write}; +use std::process; +use std::vec::Vec; + +mod serde_clif_json; + +const USAGE: &str = " +Cranelift JSON serializer/deserializer utility + +Usage: + clif-json serialize [-p] + clif-json deserialize + +Options: + -p, --pretty print pretty json + +"; + +#[derive(Deserialize, Debug)] +struct Args { + cmd_serialize: bool, + cmd_deserialize: bool, + flag_pretty: bool, + arg_file: Vec, +} + +/// A command either succeeds or fails with an error message. +pub type CommandResult = Result<(), String>; + +fn call_ser(file: &str, pretty: bool) -> CommandResult { + let ret_of_parse = parse_functions(file); + match ret_of_parse { + Ok(funcs) => { + let ser_funcs = serde_clif_json::SerObj::new(&funcs); + let ser_str: String; + if pretty { + ser_str = serde_json::to_string_pretty(&ser_funcs).unwrap(); + } else { + ser_str = serde_json::to_string(&ser_funcs).unwrap(); + } + println!("{}", ser_str); + Ok(()) + } + Err(_pe) => Err(format!("this was a parsing error")), + } +} + +fn call_de(file: &File) -> CommandResult { + let de: serde_clif_json::SerObj = match serde_json::from_reader(file) { + Result::Ok(val) => val, + Result::Err(err) => panic!("{}", err), + }; + println!("{:?}", de); + Ok(()) +} + +/// Parse the command line arguments and run the requested command. +fn clif_json() -> CommandResult { + // Parse command line arguments. + let args: Args = Docopt::new(USAGE) + .and_then(|d| d.help(true).deserialize()) + .unwrap_or_else(|e| e.exit()); + + // Find the sub-command to execute. + let result = if args.cmd_serialize { + if let Some(first_file) = args.arg_file.first() { + let mut file = File::open(first_file).expect("Unable to open the file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Unable to read the file"); + call_ser(&contents, args.flag_pretty) + } else { + Err(format!("No file was passed")) + } + } else if args.cmd_deserialize { + if let Some(first_file) = args.arg_file.first() { + let mut file = File::open(first_file).expect("Unable to open the file"); + call_de(&file) + } else { + Err(format!("No file was passed")) + } + } else { + // Debugging / shouldn't happen with proper command line handling above. + Err(format!("Unhandled args: {:?}", args)) + }; + + result +} + +fn main() { + if let Err(mut msg) = clif_json() { + if !msg.ends_with('\n') { + msg.push('\n'); + } + io::stdout().flush().expect("flushing stdout"); + io::stderr().write_all(msg.as_bytes()).unwrap(); + process::exit(1); + } +} diff --git a/lib/serde/src/serde_clif_json.rs b/lib/serde/src/serde_clif_json.rs new file mode 100644 index 0000000000..d466e4d0d0 --- /dev/null +++ b/lib/serde/src/serde_clif_json.rs @@ -0,0 +1,801 @@ +use cranelift_codegen::ir::{Ebb, Function, Inst, InstructionData, Signature}; + +/// Serializable version of the original Cranelift IR +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum SerInstData { + Unary { + opcode: String, + arg: String, + }, + UnaryImm { + opcode: String, + imm: String, + }, + UnaryIeee32 { + opcode: String, + imm: String, + }, + UnaryIeee64 { + opcode: String, + imm: String, + }, + UnaryBool { + opcode: String, + imm: bool, + }, + UnaryGlobalValue { + opcode: String, + global_value: String, + }, + Binary { + opcode: String, + args: [String; 2], + }, + BinaryImm { + opcode: String, + arg: String, + imm: String, + }, + Ternary { + opcode: String, + args: [String; 3], + }, + MultiAry { + opcode: String, + args: Vec, + }, + NullAry { + opcode: String, + }, + InsertLane { + opcode: String, + args: [String; 2], + lane: String, + }, + ExtractLane { + opcode: String, + arg: String, + lane: String, + }, + IntCompare { + opcode: String, + args: [String; 2], + cond: String, + }, + IntCompareImm { + opcode: String, + arg: String, + cond: String, + imm: String, + }, + IntCond { + opcode: String, + arg: String, + cond: String, + }, + FloatCompare { + opcode: String, + args: [String; 2], + cond: String, + }, + FloatCond { + opcode: String, + arg: String, + cond: String, + }, + IntSelect { + opcode: String, + args: [String; 3], + cond: String, + }, + Jump { + opcode: String, + args: Vec, + destination: String, + }, + Branch { + opcode: String, + args: Vec, + destination: String, + }, + BranchInt { + opcode: String, + args: Vec, + cond: String, + destination: String, + }, + BranchFloat { + opcode: String, + args: Vec, + cond: String, + destination: String, + }, + BranchIcmp { + opcode: String, + args: Vec, + cond: String, + destination: String, + }, + BranchTable { + opcode: String, + arg: String, + table: String, + }, + Call { + opcode: String, + args: Vec, + func_ref: String, + }, + CallIndirect { + opcode: String, + args: Vec, + sig_ref: String, + }, + FuncAddr { + opcode: String, + func_ref: String, + }, + Load { + opcode: String, + arg: String, + flags: String, + offset: String, + }, + LoadComplex { + opcode: String, + args: Vec, + flags: String, + offset: String, + }, + Store { + opcode: String, + args: [String; 2], + flags: String, + offset: String, + }, + StoreComplex { + opcode: String, + args: Vec, + flags: String, + offset: String, + }, + StackLoad { + opcode: String, + stack_slot: String, + offset: String, + }, + StackStore { + opcode: String, + arg: String, + stack_slot: String, + offset: String, + }, + HeapAddr { + opcode: String, + arg: String, + heap: String, + imm: String, + }, + RegMove { + opcode: String, + arg: String, + src: String, + dst: String, + }, + CopySpecial { + opcode: String, + src: String, + dst: String, + }, + RegSpill { + opcode: String, + arg: String, + src: String, + dst: String, + }, + RegFill { + opcode: String, + arg: String, + src: String, + dst: String, + }, + Trap { + opcode: String, + code: String, + }, + CondTrap { + opcode: String, + arg: String, + code: String, + }, + IntCondTrap { + opcode: String, + arg: String, + cond: String, + code: String, + }, + FloatCondTrap { + opcode: String, + arg: String, + cond: String, + code: String, + }, +} + +pub fn get_inst_data(inst_index: Inst, func: &Function) -> SerInstData { + let inst = &func.dfg[inst_index]; + match *inst { + InstructionData::Unary { opcode, arg } => SerInstData::Unary { + opcode: opcode.to_string(), + arg: arg.to_string(), + }, + InstructionData::UnaryImm { opcode, imm } => SerInstData::UnaryImm { + opcode: opcode.to_string(), + imm: imm.to_string(), + }, + InstructionData::UnaryIeee32 { opcode, imm } => SerInstData::UnaryIeee32 { + opcode: opcode.to_string(), + imm: imm.to_string(), + }, + InstructionData::UnaryIeee64 { opcode, imm } => SerInstData::UnaryIeee64 { + opcode: opcode.to_string(), + imm: imm.to_string(), + }, + InstructionData::UnaryBool { opcode, imm } => SerInstData::UnaryBool { + opcode: opcode.to_string(), + imm: imm, + }, + InstructionData::UnaryGlobalValue { + opcode, + global_value, + } => SerInstData::UnaryGlobalValue { + opcode: opcode.to_string(), + global_value: global_value.to_string(), + }, + InstructionData::Binary { opcode, args } => { + let hold_args = [args[0].to_string(), args[1].to_string()]; + SerInstData::Binary { + opcode: opcode.to_string(), + args: hold_args, + } + } + InstructionData::BinaryImm { opcode, arg, imm } => SerInstData::BinaryImm { + opcode: opcode.to_string(), + arg: arg.to_string(), + imm: imm.to_string(), + }, + InstructionData::Ternary { opcode, args } => { + let hold_args = [ + args[0].to_string(), + args[1].to_string(), + args[2].to_string(), + ]; + SerInstData::Ternary { + opcode: opcode.to_string(), + args: hold_args, + } + } + InstructionData::MultiAry { opcode, ref args } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + + SerInstData::MultiAry { + opcode: opcode.to_string(), + args: hold_args, + } + } + InstructionData::NullAry { opcode } => SerInstData::NullAry { + opcode: opcode.to_string(), + }, + InstructionData::InsertLane { opcode, args, lane } => { + let hold_args = [args[0].to_string(), args[1].to_string()]; + SerInstData::InsertLane { + opcode: opcode.to_string(), + args: hold_args, + lane: lane.to_string(), + } + } + InstructionData::ExtractLane { opcode, arg, lane } => SerInstData::ExtractLane { + opcode: opcode.to_string(), + arg: arg.to_string(), + lane: lane.to_string(), + }, + InstructionData::IntCompare { opcode, args, cond } => { + let hold_args = [args[0].to_string(), args[1].to_string()]; + SerInstData::IntCompare { + opcode: opcode.to_string(), + args: hold_args, + cond: cond.to_string(), + } + } + InstructionData::IntCompareImm { + opcode, + arg, + cond, + imm, + } => SerInstData::IntCompareImm { + opcode: opcode.to_string(), + arg: arg.to_string(), + cond: cond.to_string(), + imm: imm.to_string(), + }, + InstructionData::IntCond { opcode, arg, cond } => SerInstData::IntCond { + opcode: opcode.to_string(), + arg: arg.to_string(), + cond: cond.to_string(), + }, + InstructionData::FloatCompare { opcode, args, cond } => { + let hold_args = [args[0].to_string(), args[1].to_string()]; + SerInstData::FloatCompare { + opcode: opcode.to_string(), + args: hold_args, + cond: cond.to_string(), + } + } + InstructionData::FloatCond { opcode, arg, cond } => SerInstData::FloatCond { + opcode: opcode.to_string(), + arg: arg.to_string(), + cond: cond.to_string(), + }, + InstructionData::IntSelect { opcode, args, cond } => { + let hold_args = [ + args[0].to_string(), + args[1].to_string(), + args[2].to_string(), + ]; + SerInstData::IntSelect { + opcode: opcode.to_string(), + args: hold_args, + cond: cond.to_string(), + } + } + InstructionData::Jump { + opcode, + ref args, + destination, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::Jump { + opcode: opcode.to_string(), + args: hold_args, + destination: destination.to_string(), + } + } + InstructionData::Branch { + opcode, + ref args, + destination, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::Branch { + opcode: opcode.to_string(), + args: hold_args, + destination: destination.to_string(), + } + } + InstructionData::BranchInt { + opcode, + ref args, + cond, + destination, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::BranchInt { + opcode: opcode.to_string(), + args: hold_args, + cond: cond.to_string(), + destination: destination.to_string(), + } + } + InstructionData::BranchFloat { + opcode, + ref args, + cond, + destination, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::BranchFloat { + opcode: opcode.to_string(), + args: hold_args, + cond: cond.to_string(), + destination: destination.to_string(), + } + } + InstructionData::BranchIcmp { + opcode, + ref args, + cond, + destination, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::BranchIcmp { + opcode: opcode.to_string(), + args: hold_args, + cond: cond.to_string(), + destination: destination.to_string(), + } + } + // to add: jump table serialization + InstructionData::BranchTable { opcode, arg, table } => SerInstData::BranchTable { + opcode: opcode.to_string(), + arg: arg.to_string(), + table: table.to_string(), + }, + InstructionData::Call { + opcode, + ref args, + func_ref, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::Call { + opcode: opcode.to_string(), + args: hold_args, + func_ref: func_ref.to_string(), + } + } + InstructionData::CallIndirect { + opcode, + ref args, + sig_ref, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::CallIndirect { + opcode: opcode.to_string(), + args: hold_args, + sig_ref: sig_ref.to_string(), + } + } + InstructionData::FuncAddr { opcode, func_ref } => SerInstData::FuncAddr { + opcode: opcode.to_string(), + func_ref: func_ref.to_string(), + }, + InstructionData::Load { + opcode, + arg, + flags, + offset, + } => SerInstData::Load { + opcode: opcode.to_string(), + arg: arg.to_string(), + flags: flags.to_string(), + offset: offset.to_string(), + }, + InstructionData::LoadComplex { + opcode, + ref args, + flags, + offset, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::LoadComplex { + opcode: opcode.to_string(), + args: hold_args, + flags: flags.to_string(), + offset: offset.to_string(), + } + } + InstructionData::Store { + opcode, + args, + flags, + offset, + } => { + let hold_args = [args[0].to_string(), args[1].to_string()]; + SerInstData::Store { + opcode: opcode.to_string(), + args: hold_args, + flags: flags.to_string(), + offset: offset.to_string(), + } + } + InstructionData::StoreComplex { + opcode, + ref args, + flags, + offset, + } => { + let mut hold_args = Vec::new(); + let args_iter = args.as_slice(&func.dfg.value_lists); + for arg in args_iter { + hold_args.push(arg.to_string()); + } + SerInstData::StoreComplex { + opcode: opcode.to_string(), + args: hold_args, + flags: flags.to_string(), + offset: offset.to_string(), + } + } + InstructionData::StackLoad { + opcode, + stack_slot, + offset, + } => SerInstData::StackLoad { + opcode: opcode.to_string(), + stack_slot: stack_slot.to_string(), + offset: offset.to_string(), + }, + InstructionData::StackStore { + opcode, + arg, + stack_slot, + offset, + } => SerInstData::StackStore { + opcode: opcode.to_string(), + arg: arg.to_string(), + stack_slot: stack_slot.to_string(), + offset: offset.to_string(), + }, + InstructionData::HeapAddr { + opcode, + arg, + heap, + imm, + } => SerInstData::HeapAddr { + opcode: opcode.to_string(), + arg: arg.to_string(), + heap: heap.to_string(), + imm: imm.to_string(), + }, + InstructionData::RegMove { + opcode, + arg, + src, + dst, + } => SerInstData::RegMove { + opcode: opcode.to_string(), + arg: arg.to_string(), + src: src.to_string(), + dst: dst.to_string(), + }, + InstructionData::CopySpecial { opcode, src, dst } => SerInstData::CopySpecial { + opcode: opcode.to_string(), + src: src.to_string(), + dst: dst.to_string(), + }, + InstructionData::RegSpill { + opcode, + arg, + src, + dst, + } => SerInstData::RegSpill { + opcode: opcode.to_string(), + arg: arg.to_string(), + src: src.to_string(), + dst: dst.to_string(), + }, + InstructionData::RegFill { + opcode, + arg, + src, + dst, + } => SerInstData::RegFill { + opcode: opcode.to_string(), + arg: arg.to_string(), + src: src.to_string(), + dst: dst.to_string(), + }, + InstructionData::Trap { opcode, code } => SerInstData::Trap { + opcode: opcode.to_string(), + code: code.to_string(), + }, + InstructionData::CondTrap { opcode, arg, code } => SerInstData::CondTrap { + opcode: opcode.to_string(), + arg: arg.to_string(), + code: code.to_string(), + }, + InstructionData::IntCondTrap { + opcode, + arg, + cond, + code, + } => SerInstData::IntCondTrap { + opcode: opcode.to_string(), + arg: arg.to_string(), + cond: cond.to_string(), + code: code.to_string(), + }, + InstructionData::FloatCondTrap { + opcode, + arg, + cond, + code, + } => SerInstData::FloatCondTrap { + opcode: opcode.to_string(), + arg: arg.to_string(), + cond: cond.to_string(), + code: code.to_string(), + }, + } +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct SerInst { + pub inst_name: String, + pub inst_data: SerInstData, +} + +impl SerInst { + pub fn new(inst: Inst, func: &Function) -> Self { + Self { + inst_name: inst.to_string(), + inst_data: get_inst_data(inst, func), + } + } +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct SerEbb { + pub ebb: String, + pub params: Vec, + pub insts: Vec, +} + +impl SerEbb { + pub fn new(name: String) -> Self { + Self { + ebb: name, + params: Vec::new(), + insts: Vec::new(), + } + } +} + +pub fn populate_inst(func: &Function, ebb: Ebb) -> Vec { + let mut ser_vec: Vec = Vec::new(); + let ret_iter = func.layout.ebb_insts(ebb); + for inst in ret_iter { + let mut ser_inst: SerInst = SerInst::new(inst, &func); + ser_vec.push(ser_inst); + } + ser_vec +} + +pub fn populate_params(func: &Function, ebb: &Ebb) -> Vec { + let mut ser_vec: Vec = Vec::new(); + let parameters = func.dfg.ebb_params(*ebb); + for param in parameters { + ser_vec.push(param.to_string()); + } + ser_vec +} + +/// Serializable Data Flow Graph +#[derive(Deserialize, Serialize, Debug)] +pub struct SerDataFlowGraph { + ebbs: Vec, +} + +pub fn populate_ebbs(func: &Function) -> Vec { + let mut ebb_vec: Vec = Vec::new(); + for ebb in func.layout.ebbs() { + let mut ser_ebb: SerEbb = SerEbb::new(ebb.to_string()); + ser_ebb.params = populate_params(&func, &ebb); + ser_ebb.insts = populate_inst(&func, ebb); + ebb_vec.push(ser_ebb); + } + ebb_vec +} + +impl SerDataFlowGraph { + pub fn create_new(func: &Function) -> Self { + Self { + ebbs: populate_ebbs(func), + } + } + + pub fn new(func: &Function) -> Self { + Self::create_new(func) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SerSignature { + pub func_params: Vec, + pub func_returns: Vec, +} + +impl SerSignature { + fn create_new(sig: &Signature) -> Self { + let mut params_vec: Vec = Vec::new(); + let mut returns_vec: Vec = Vec::new(); + for param in sig.params.iter() { + params_vec.push(param.to_string()); + } + for ret in sig.returns.iter() { + returns_vec.push(ret.to_string()); + } + Self { + func_params: params_vec, + func_returns: returns_vec, + } + } + + pub fn new(func: &Function) -> Self { + Self::create_new(&func.signature) + } +} + +/// Serializable Function type +#[derive(Serialize, Deserialize, Debug)] +pub struct SerFunction { + pub name: String, + pub signature: SerSignature, + pub globals: Vec, + pub dfg: SerDataFlowGraph, +} + +impl SerFunction { + fn create_new(func: &Function) -> Self { + let mut global_vec: Vec = Vec::new(); + for (glob_name, _) in func.global_values.iter() { + global_vec.push(glob_name.to_string()); + } + Self { + name: func.name.to_string(), + signature: SerSignature::new(&func), + globals: global_vec, + dfg: SerDataFlowGraph::new(&func), + } + } + + pub fn new(func: &Function) -> Self { + Self::create_new(func) + } +} + +/// Must have SerObj for deserialization +#[derive(Serialize, Deserialize, Debug)] +pub struct SerObj { + pub functions: Vec, +} + +impl SerObj { + fn create_new(funcs: Vec) -> Self { + Self { functions: funcs } + } + + pub fn new(funcs: &Vec) -> Self { + let mut func_vec: Vec = Vec::new(); + for func in funcs { + let mut ser_func: SerFunction = SerFunction::new(&func); + func_vec.push(ser_func); + } + Self::create_new(func_vec) + } +}