Skip to content

Commit

Permalink
feat(dojo-compiler): add sierra to cairo debug information (#2392)
Browse files Browse the repository at this point in the history
* feat: add sierra to cairo debug information

* refactor: clean up

---------

Co-authored-by: glihm <[email protected]>
  • Loading branch information
kariy and glihm authored Sep 6, 2024
1 parent dd4cd5f commit ecab100
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 54 deletions.
195 changes: 141 additions & 54 deletions crates/dojo-lang/src/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::io::Write;
use std::iter::zip;
use std::ops::DerefMut;
use std::rc::Rc;

use anyhow::{anyhow, Context, Result};
use cairo_lang_compiler::db::RootDatabase;
Expand All @@ -28,7 +28,7 @@ use dojo_world::manifest::{
BASE_CONTRACT_TAG, BASE_DIR, BASE_QUALIFIED_PATH, CONTRACTS_DIR, MANIFESTS_DIR, MODELS_DIR,
WORLD_CONTRACT_TAG, WORLD_QUALIFIED_PATH,
};
use itertools::Itertools;
use itertools::{izip, Itertools};
use scarb::compiler::helpers::{build_compiler_config, collect_main_crate_ids};
use scarb::compiler::{CairoCompilationUnit, CompilationUnitAttributes, Compiler};
use scarb::core::{PackageName, TargetKind, Workspace};
Expand All @@ -41,6 +41,19 @@ use starknet::core::types::Felt;
use tracing::{debug, trace, trace_span};

use crate::plugin::{DojoAuxData, Model};
use crate::scarb_internal::debug::SierraToCairoDebugInfo;

#[derive(Debug, Clone)]
pub struct CompiledArtifact {
/// THe class hash of the Sierra contract.
class_hash: Felt,
/// The actual compiled Sierra contract class.
contract_class: Rc<ContractClass>,
debug_info: Option<Rc<SierraToCairoDebugInfo>>,
}

/// A type alias for a map of compiled artifacts by their path.
type CompiledArtifactByPath = HashMap<String, CompiledArtifact>;

const CAIRO_PATH_SEPARATOR: &str = "::";

Expand Down Expand Up @@ -88,6 +101,7 @@ impl Compiler for DojoCompiler {
TargetKind::new("dojo")
}

// TODO: refacto the main loop here, could be much more simpler and efficient.
fn compile(
&self,
unit: CairoCompilationUnit,
Expand Down Expand Up @@ -129,18 +143,32 @@ impl Compiler for DojoCompiler {
compile_prepared_db(db, &contracts, compiler_config)?
};

let mut compiled_classes: HashMap<String, (Felt, ContractClass)> = HashMap::new();
// TODO: get the debug flag from the `dojo_<profile>.toml` file.
let with_debug_info = true;
let debug_info_classes: Vec<Option<SierraToCairoDebugInfo>> = if with_debug_info {
let debug_classes =
crate::scarb_internal::debug::compile_prepared_db_to_debug_info(db, &contracts)?;

debug_classes
.into_iter()
.map(|d| Some(crate::scarb_internal::debug::get_sierra_to_cairo_debug_info(&d, db)))
.collect()
} else {
vec![None; contracts.len()]
};

let mut compiled_classes: CompiledArtifactByPath = HashMap::new();
let list_selector = ListSelector::default();

for (decl, class) in zip(contracts, classes) {
for (decl, contract_class, debug_info) in izip!(contracts, classes, debug_info_classes) {
let contract_name = decl.submodule_id.name(db.upcast_mut());

// note that the qualified path is in snake case while
// the `full_path()` method of StructId uses the original struct name case.
// (see in `get_dojo_model_artifacts`)
let qualified_path = decl.module_id().full_path(db.upcast_mut());

match class.validate_version_compatible(list_selector.clone()) {
match contract_class.validate_version_compatible(list_selector.clone()) {
Ok(()) => {}
Err(AllowedLibfuncsError::UnsupportedLibfunc {
invalid_libfunc,
Expand All @@ -160,11 +188,22 @@ impl Compiler for DojoCompiler {
}
}

let class_hash = compute_class_hash_of_contract_class(&class).with_context(|| {
format!("problem computing class hash for contract `{}`", qualified_path.clone())
})?;

compiled_classes.insert(qualified_path, (class_hash, class));
let class_hash =
compute_class_hash_of_contract_class(&contract_class).with_context(|| {
format!(
"problem computing class hash for contract `{}`",
qualified_path.clone()
)
})?;

compiled_classes.insert(
qualified_path,
CompiledArtifact {
class_hash,
contract_class: Rc::new(contract_class),
debug_info: debug_info.map(Rc::new),
},
);
}

update_files(
Expand Down Expand Up @@ -256,7 +295,7 @@ fn update_files(
ws: &Workspace<'_>,
target_dir: &Filesystem,
crate_ids: &[CrateId],
compiled_artifacts: HashMap<String, (Felt, ContractClass)>,
compiled_artifacts: CompiledArtifactByPath,
external_contracts: Option<Vec<ContractSelector>>,
) -> anyhow::Result<()> {
let profile_name =
Expand All @@ -270,9 +309,9 @@ fn update_files(
let manifest_dir = ws.manifest_path().parent().unwrap().to_path_buf();

fn get_compiled_artifact_from_map<'a>(
artifacts: &'a HashMap<String, (Felt, ContractClass)>,
artifacts: &'a CompiledArtifactByPath,
qualified_artifact_path: &str,
) -> anyhow::Result<&'a (Felt, ContractClass)> {
) -> anyhow::Result<&'a CompiledArtifact> {
artifacts.get(qualified_artifact_path).context(format!(
"Contract `{qualified_artifact_path}` not found. Did you include `dojo` as a \
dependency?",
Expand All @@ -285,7 +324,7 @@ fn update_files(
for (qualified_path, tag) in
[(WORLD_QUALIFIED_PATH, WORLD_CONTRACT_TAG), (BASE_QUALIFIED_PATH, BASE_CONTRACT_TAG)]
{
let (hash, class) = get_compiled_artifact_from_map(&compiled_artifacts, qualified_path)?;
let artifact = get_compiled_artifact_from_map(&compiled_artifacts, qualified_path)?;
let filename = naming::get_filename_from_tag(tag);
write_manifest_and_abi(
&base_manifests_dir,
Expand All @@ -294,16 +333,20 @@ fn update_files(
&mut Manifest::new(
// abi path will be written by `write_manifest`
Class {
class_hash: *hash,
class_hash: artifact.class_hash,
abi: None,
original_class_hash: *hash,
original_class_hash: artifact.class_hash,
tag: tag.to_string(),
},
filename.clone(),
),
&class.abi,
&artifact.contract_class.abi,
)?;
save_json_artifact_file(ws, target_dir, class, &filename, tag)?;
save_json_artifact_file(ws, target_dir, &artifact.contract_class, &filename, tag)?;

if let Some(debug_info) = &artifact.debug_info {
save_json_artifact_debug_file(ws, target_dir, debug_info, &filename, tag)?;
}
}

let mut models = BTreeMap::new();
Expand Down Expand Up @@ -380,13 +423,13 @@ fn update_files(
std::fs::create_dir_all(&base_contracts_abis_dir)?;
}

for (_, (manifest, class, module_id)) in contracts.iter_mut() {
for (_, (manifest, module_id, artifact)) in contracts.iter_mut() {
write_manifest_and_abi(
&base_contracts_dir,
&base_contracts_abis_dir,
&manifest_dir,
manifest,
&class.abi,
&artifact.contract_class.abi,
)?;

let filename = naming::get_filename_from_tag(&manifest.inner.tag);
Expand All @@ -398,7 +441,23 @@ fn update_files(
&filename,
&manifest.inner.tag,
)?;
save_json_artifact_file(ws, &contracts_dir, class, &filename, &manifest.inner.tag)?;
save_json_artifact_file(
ws,
&contracts_dir,
&artifact.contract_class,
&filename,
&manifest.inner.tag,
)?;

if let Some(debug_info) = &artifact.debug_info {
save_json_artifact_debug_file(
ws,
&contracts_dir,
debug_info,
&filename,
&manifest.inner.tag,
)?;
}
}

let models_dir = target_dir.child(MODELS_DIR);
Expand All @@ -417,18 +476,34 @@ fn update_files(
std::fs::create_dir_all(&base_models_abis_dir)?;
}

for (_, (manifest, class, module_id)) in models.iter_mut() {
for (_, (manifest, module_id, artifact)) in models.iter_mut() {
write_manifest_and_abi(
&base_models_dir,
&base_models_abis_dir,
&manifest_dir,
manifest,
&class.abi,
&artifact.contract_class.abi,
)?;

let filename = naming::get_filename_from_tag(&manifest.inner.tag);
save_expanded_source_file(ws, *module_id, db, &models_dir, &filename, &manifest.inner.tag)?;
save_json_artifact_file(ws, &models_dir, class, &filename, &manifest.inner.tag)?;
save_json_artifact_file(
ws,
&models_dir,
&artifact.contract_class,
&filename,
&manifest.inner.tag,
)?;

if let Some(debug_info) = &artifact.debug_info {
save_json_artifact_debug_file(
ws,
&models_dir,
debug_info,
&filename,
&manifest.inner.tag,
)?;
}
}

Ok(())
Expand All @@ -441,8 +516,8 @@ fn get_dojo_model_artifacts(
db: &RootDatabase,
aux_data: &Vec<Model>,
module_id: ModuleId,
compiled_classes: &HashMap<String, (Felt, ContractClass)>,
) -> anyhow::Result<HashMap<String, (Manifest<DojoModel>, ContractClass, ModuleId)>> {
compiled_classes: &CompiledArtifactByPath,
) -> anyhow::Result<HashMap<String, (Manifest<DojoModel>, ModuleId, CompiledArtifact)>> {
let mut models = HashMap::with_capacity(aux_data.len());

for model in aux_data {
Expand All @@ -456,25 +531,18 @@ fn get_dojo_model_artifacts(
let compiled_class = compiled_classes.get(&qualified_path).cloned();
let tag = naming::get_tag(&model.namespace, &model.name);

if let Some((class_hash, class)) = compiled_class {
models.insert(
qualified_path.clone(),
(
Manifest::new(
DojoModel {
tag: tag.clone(),
class_hash,
abi: None,
members: model.members.clone(),
original_class_hash: class_hash,
qualified_path,
},
naming::get_filename_from_tag(&tag),
),
class,
module_id,
),
);
if let Some(artifact) = compiled_class {
let dojo_model = DojoModel {
abi: None,
tag: tag.clone(),
members: model.members.clone(),
class_hash: artifact.class_hash,
qualified_path: qualified_path.clone(),
original_class_hash: artifact.class_hash,
};

let manifest = Manifest::new(dojo_model, naming::get_filename_from_tag(&tag));
models.insert(qualified_path, (manifest, module_id, artifact.clone()));
} else {
println!("Model {} not found in target.", tag.clone());
}
Expand All @@ -489,9 +557,9 @@ fn get_dojo_contract_artifacts(
db: &RootDatabase,
module_id: &ModuleId,
tag: &str,
compiled_classes: &HashMap<String, (Felt, ContractClass)>,
compiled_classes: &CompiledArtifactByPath,
systems: &[String],
) -> anyhow::Result<HashMap<String, (Manifest<DojoContract>, ContractClass, ModuleId)>> {
) -> Result<HashMap<String, (Manifest<DojoContract>, ModuleId, CompiledArtifact)>> {
let mut result = HashMap::new();

if !matches!(naming::get_name_from_tag(tag).as_str(), "world" | "resource_metadata" | "base") {
Expand All @@ -501,24 +569,24 @@ fn get_dojo_contract_artifacts(
let contract_qualified_path =
format!("{}{}{}", module_id.full_path(db), CAIRO_PATH_SEPARATOR, contract_name);

if let Some((class_hash, class)) =
compiled_classes.get(&contract_qualified_path.to_string())
{
if let Some(artifact) = compiled_classes.get(&contract_qualified_path.to_string()) {
let manifest = Manifest::new(
DojoContract {
tag: tag.to_string(),
writes: vec![],
reads: vec![],
class_hash: *class_hash,
original_class_hash: *class_hash,
class_hash: artifact.class_hash,
original_class_hash: artifact.class_hash,
systems: systems.to_vec(),
..Default::default()
},
naming::get_filename_from_tag(tag),
);

result
.insert(contract_qualified_path.to_string(), (manifest, class.clone(), *module_id));
result.insert(
contract_qualified_path.to_string(),
(manifest, *module_id, artifact.clone()),
);
}
}

Expand Down Expand Up @@ -631,3 +699,22 @@ fn save_json_artifact_file(
.with_context(|| format!("failed to serialize contract artifact: {contract_tag}"))?;
Ok(())
}

fn save_json_artifact_debug_file(
ws: &Workspace<'_>,
contract_dir: &Filesystem,
debug_info: &SierraToCairoDebugInfo,
contract_basename: &str,
contract_tag: &str,
) -> anyhow::Result<()> {
let mut file = contract_dir.open_rw(
format!("{contract_basename}.debug.json"),
"class file",
ws.config(),
)?;

serde_json::to_writer_pretty(file.deref_mut(), debug_info)
.with_context(|| format!("failed to serialize contract debug artifact: {contract_tag}"))?;

Ok(())
}
Loading

0 comments on commit ecab100

Please sign in to comment.