use std::fmt;
use anyhow::Result;
use num_bigint::BigInt;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::debug_info::DebugInfo;
use crate::extensions::NamedLibfunc;
use crate::extensions::gas::{
BuiltinCostWithdrawGasLibfunc, RedepositGasLibfunc, WithdrawGasLibfunc,
};
use crate::ids::{
ConcreteLibfuncId, ConcreteTypeId, FunctionId, GenericLibfuncId, GenericTypeId, UserTypeId,
VarId,
};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum VersionedProgram {
V1 {
version: Version<1>,
#[serde(flatten)]
program: ProgramArtifact,
},
}
impl VersionedProgram {
pub fn v1(program: ProgramArtifact) -> Self {
Self::V1 { program, version: Version::<1> }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Version<const V: u8>;
#[derive(Debug, Error)]
#[error("Unsupported Sierra program version")]
struct VersionError;
impl<const V: u8> Serialize for Version<V> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(V)
}
}
impl<'de, const V: u8> Deserialize<'de> for Version<V> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
if value == V { Ok(Version::<V>) } else { Err(serde::de::Error::custom(VersionError)) }
}
}
impl From<ProgramArtifact> for VersionedProgram {
fn from(value: ProgramArtifact) -> Self {
VersionedProgram::v1(value)
}
}
impl VersionedProgram {
pub fn into_v1(self) -> Result<ProgramArtifact> {
match self {
VersionedProgram::V1 { program, .. } => Ok(program),
}
}
}
impl fmt::Display for VersionedProgram {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VersionedProgram::V1 { program, .. } => fmt::Display::fmt(program, f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ProgramArtifact {
#[serde(flatten)]
pub program: Program,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_info: Option<DebugInfo>,
}
impl ProgramArtifact {
pub fn stripped(program: Program) -> Self {
Self { program, debug_info: None }
}
pub fn with_debug_info(self, debug_info: DebugInfo) -> Self {
Self { program: self.program, debug_info: Some(debug_info) }
}
}
impl fmt::Display for ProgramArtifact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.program, f)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Program {
pub type_declarations: Vec<TypeDeclaration>,
pub libfunc_declarations: Vec<LibfuncDeclaration>,
pub statements: Vec<Statement>,
pub funcs: Vec<Function>,
}
impl Program {
pub fn get_statement(&self, id: &StatementIdx) -> Option<&Statement> {
self.statements.get(id.0)
}
pub fn into_artifact(self) -> VersionedProgram {
ProgramArtifact::stripped(self).into()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TypeDeclaration {
pub id: ConcreteTypeId,
pub long_id: ConcreteTypeLongId,
pub declared_type_info: Option<DeclaredTypeInfo>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct DeclaredTypeInfo {
pub storable: bool,
pub droppable: bool,
pub duplicatable: bool,
pub zero_sized: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ConcreteTypeLongId {
pub generic_id: GenericTypeId,
pub generic_args: Vec<GenericArg>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LibfuncDeclaration {
pub id: ConcreteLibfuncId,
pub long_id: ConcreteLibfuncLongId,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ConcreteLibfuncLongId {
pub generic_id: GenericLibfuncId,
pub generic_args: Vec<GenericArg>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FunctionSignature {
pub param_types: Vec<ConcreteTypeId>,
pub ret_types: Vec<ConcreteTypeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct GenFunction<StatementId> {
pub id: FunctionId,
pub signature: FunctionSignature,
pub params: Vec<Param>,
pub entry_point: StatementId,
}
impl<StatementId> GenFunction<StatementId> {
pub fn new(
id: FunctionId,
params: Vec<Param>,
ret_types: Vec<ConcreteTypeId>,
entry_point: StatementId,
) -> Self {
let param_types: Vec<_> = params.iter().map(|Param { id: _, ty }| ty.clone()).collect();
GenFunction {
id,
signature: FunctionSignature { param_types, ret_types },
params,
entry_point,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Param {
pub id: VarId,
pub ty: ConcreteTypeId,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, PartialOrd, Ord)]
pub struct StatementIdx(pub usize);
impl StatementIdx {
pub fn next(&self, target: &BranchTarget) -> StatementIdx {
match target {
BranchTarget::Fallthrough => StatementIdx(self.0 + 1),
BranchTarget::Statement(id) => *id,
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum GenericArg {
UserType(UserTypeId),
Type(ConcreteTypeId),
Value(BigInt),
UserFunc(FunctionId),
Libfunc(ConcreteLibfuncId),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum GenStatement<StatementId> {
Invocation(GenInvocation<StatementId>),
Return(Vec<VarId>),
}
impl<StatementId> GenStatement<StatementId> {
pub fn map<T>(self, f: impl Fn(StatementId) -> T) -> GenStatement<T> {
match self {
GenStatement::Invocation(invocation) => GenStatement::Invocation(GenInvocation {
libfunc_id: invocation.libfunc_id,
args: invocation.args.clone(),
branches: invocation
.branches
.into_iter()
.map(|branch| GenBranchInfo {
target: branch.target.map(&f),
results: branch.results.clone(),
})
.collect(),
}),
GenStatement::Return(results) => GenStatement::Return(results.clone()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct GenInvocation<StatementId> {
pub libfunc_id: ConcreteLibfuncId,
pub args: Vec<VarId>,
pub branches: Vec<GenBranchInfo<StatementId>>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct GenBranchInfo<StatementId> {
pub target: GenBranchTarget<StatementId>,
pub results: Vec<VarId>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum GenBranchTarget<StatementId> {
Fallthrough,
Statement(StatementId),
}
impl<StatementId> GenBranchTarget<StatementId> {
pub fn map<T>(self, f: impl Fn(StatementId) -> T) -> GenBranchTarget<T> {
match self {
GenBranchTarget::Fallthrough => GenBranchTarget::Fallthrough,
GenBranchTarget::Statement(id) => GenBranchTarget::Statement(f(id)),
}
}
}
pub type Function = GenFunction<StatementIdx>;
pub type Statement = GenStatement<StatementIdx>;
pub type Invocation = GenInvocation<StatementIdx>;
pub type BranchInfo = GenBranchInfo<StatementIdx>;
pub type BranchTarget = GenBranchTarget<StatementIdx>;
impl Program {
pub fn requires_gas_counter(&self) -> bool {
self.libfunc_declarations.iter().any(|decl| {
matches!(
decl.long_id.generic_id.0.as_str(),
WithdrawGasLibfunc::STR_ID
| BuiltinCostWithdrawGasLibfunc::STR_ID
| RedepositGasLibfunc::STR_ID
)
})
}
}