Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ thiserror = { package = "miden-thiserror", version = "1.0" }
toml = { version = "0.8", features = ["preserve_order"] }
tokio = { version = "1.39.2", features = ["rt", "time", "macros", "rt-multi-thread"] }
wat = "1.0.69"
wasmprinter = "0.227"
wasmparser = { version = "0.227", default-features = false, features = [
"features",
"component-model",
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ To run the compiler test suite:

This will run all of the unit tests in the workspace, as well as all of our `lit` tests.

## Debugging

### Emitting internal sources/artifacts

- `MIDENC_EMIT`: Environment-variable equivalent of `--emit`. Accepts the same `KIND[=PATH]` syntax
(comma-delimited), where `PATH` is treated either as folder e.g. `MIDENC_EMIT=ir=target/emit` or file `MIDENC_EMIT=hir=my_name.hir`.
- `MIDENC_EMIT_MACRO_EXPAND[=<dir>]`: When set, integration tests dump `cargo expand`
output for Rust fixtures to `<fixture>.expanded.rs` files in `<dir>` (or the CWD if empty/`1`).

## Docs

The documentation in the `docs/external` folder is built using Docusaurus and is automatically absorbed into the main [miden-docs](https://github.com/0xMiden/miden-docs) repository for the main documentation website. Changes to the `next` branch trigger an automated deployment workflow. The docs folder requires npm packages to be installed before building.
Expand Down
1 change: 1 addition & 0 deletions codegen/masm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ default = ["std"]
std = ["midenc-hir/std", "midenc-dialect-hir/std", "petgraph/std"]

[dependencies]
anyhow.workspace = true
inventory.workspace = true
log.workspace = true
miden-assembly.workspace = true
Expand Down
25 changes: 24 additions & 1 deletion codegen/masm/src/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use miden_core::{Program, Word};
use miden_mast_package::{MastArtifact, Package, ProcedureName};
use midenc_hir::{constants::ConstantData, dialects::builtin, interner::Symbol};
use midenc_session::{
Session,
Emit, OutputMode, OutputType, Session, Writer,
diagnostics::{Report, SourceSpan, Span},
};

Expand Down Expand Up @@ -38,6 +38,29 @@ pub struct MasmComponent {
pub modules: Vec<Arc<masm::Module>>,
}

impl Emit for MasmComponent {
fn name(&self) -> Option<Symbol> {
None
}

fn output_type(&self, _mode: OutputMode) -> OutputType {
OutputType::Masm
}

fn write_to<W: Writer>(
&self,
mut writer: W,
mode: OutputMode,
_session: &Session,
) -> anyhow::Result<()> {
if mode != OutputMode::Text {
anyhow::bail!("masm emission does not support binary mode");
}
writer.write_fmt(core::format_args!("{self}"))?;
Ok(())
}
}

/// Represents a read-only data segment, combined with its content digest
#[derive(Clone, PartialEq, Eq)]
pub struct Rodata {
Expand Down
6 changes: 3 additions & 3 deletions docs/external/src/guides/debugger.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ The debugger may also be used as a library, but that is left as an exercise for

```shell
# Compile a program to MAST from a rustc-generated Wasm module
midenc compile foo.wasm -o foo.masl
midenc compile foo.wasm -o foo.masp

# Load that program into the debugger and start executing it
midenc debug foo.masl
midenc debug foo.masp
```

## Program inputs
Expand All @@ -48,7 +48,7 @@ To specify the contents of the operand stack, you can do so by following the raw
Each operand must be a valid field element value, in either decimal or hexadecimal format. For example:

```shell
midenc debug foo.masl -- 1 2 0xdeadbeef
midenc debug foo.masp -- 1 2 0xdeadbeef
```

If you pass arguments via the command line in conjunction with `--inputs`, then the command line arguments
Expand Down
3 changes: 2 additions & 1 deletion frontend/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition.workspace = true

[features]
default = ["std"]
std = ["wasmparser/std", "gimli/std", "midenc-hir-symbol/std"]
std = ["wasmparser/std", "gimli/std", "midenc-hir-symbol/std", "dep:wasmprinter"]

[dependencies]
anyhow.workspace = true
Expand All @@ -32,6 +32,7 @@ midenc-hir-symbol.workspace = true
midenc-session.workspace = true
thiserror.workspace = true
wasmparser.workspace = true
wasmprinter = { workspace = true, optional = true }

[dev-dependencies]
# Use local paths for dev-only dependency to avoid relying on crates.io during packaging
Expand Down
108 changes: 108 additions & 0 deletions frontend/wasm/src/emit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Utilities for emitting frontend-specific artifacts.

use alloc::string::String;

use midenc_session::{Emit, OutputMode, OutputType, Session, Writer};

/// A wrapper that emits WebAssembly text format (WAT).
#[derive(Debug, Copy, Clone)]
pub struct WatEmit<'a>(pub &'a str);

impl Emit for WatEmit<'_> {
fn name(&self) -> Option<midenc_hir::interner::Symbol> {
None
}

fn output_type(&self, _mode: OutputMode) -> OutputType {
OutputType::Wat
}

fn write_to<W: Writer>(
&self,
mut writer: W,
mode: OutputMode,
_session: &Session,
) -> anyhow::Result<()> {
if mode != OutputMode::Text {
anyhow::bail!("wat emission does not support binary mode");
}
writer.write_fmt(core::format_args!("{}", self.0))?;
Ok(())
}
}

/// Convert a WebAssembly binary to WAT text, filtering out highly variable custom sections.
#[cfg(feature = "std")]
pub fn wasm_to_wat(wasm_bytes: &[u8]) -> anyhow::Result<String> {
use core::fmt;

// Disable printing of the various custom sections, e.g. "producers", either because they
// contain strings which are highly variable (but not important), or because they are debug info
// related.
struct NoCustomSectionsPrinter<T: wasmprinter::Print>(T);
impl<T: wasmprinter::Print> wasmprinter::Print for NoCustomSectionsPrinter<T> {
fn write_str(&mut self, s: &str) -> std::io::Result<()> {
self.0.write_str(s)
}

fn newline(&mut self) -> std::io::Result<()> {
self.0.newline()
}

fn start_line(&mut self, binary_offset: Option<usize>) {
self.0.start_line(binary_offset);
}

fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> std::io::Result<()> {
self.0.write_fmt(args)
}

fn print_custom_section(
&mut self,
name: &str,
binary_offset: usize,
data: &[u8],
) -> std::io::Result<bool> {
match name {
"producers" | "target_features" => Ok(true),
debug if debug.starts_with(".debug") => Ok(true),
_ => self.0.print_custom_section(name, binary_offset, data),
}
}

fn start_literal(&mut self) -> std::io::Result<()> {
self.0.start_literal()
}

fn start_name(&mut self) -> std::io::Result<()> {
self.0.start_name()
}

fn start_keyword(&mut self) -> std::io::Result<()> {
self.0.start_keyword()
}

fn start_type(&mut self) -> std::io::Result<()> {
self.0.start_type()
}

fn start_comment(&mut self) -> std::io::Result<()> {
self.0.start_comment()
}

fn reset_color(&mut self) -> std::io::Result<()> {
self.0.reset_color()
}

fn supports_async_color(&self) -> bool {
self.0.supports_async_color()
}
}

// WAT text should be at least ~3x larger than the binary Wasm representation
let mut wat = String::with_capacity(wasm_bytes.len() * 3);
let config = wasmprinter::Config::new();
let mut wasm_printer = NoCustomSectionsPrinter(wasmprinter::PrintFmtWrite(&mut wat));
config.print(wasm_bytes, &mut wasm_printer)?;
Ok(wat)
}
5 changes: 4 additions & 1 deletion frontend/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod callable;
mod code_translator;
mod component;
mod config;
mod emit;
mod error;
mod intrinsics;
mod miden_abi;
Expand All @@ -29,7 +30,9 @@ use midenc_hir::{Context, dialects::builtin};
use module::build_ir::translate_module_as_component;
use wasmparser::WasmFeatures;

pub use self::{config::*, error::WasmError};
#[cfg(feature = "std")]
pub use self::emit::wasm_to_wat;
pub use self::{config::*, emit::WatEmit, error::WasmError};

/// The output of the frontend Wasm translation stage
pub struct FrontendOutput {
Expand Down
1 change: 1 addition & 0 deletions midenc-compile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ std = [
]

[dependencies]
anyhow.workspace = true
clap = { workspace = true, optional = true }
log.workspace = true
inventory.workspace = true
Expand Down
5 changes: 4 additions & 1 deletion midenc-compile/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,10 @@ impl Compiler {

// Initialize output types
let mut output_types = OutputTypes::new(self.output_types).unwrap_or_else(|err| err.exit());
if output_types.is_empty() {
let has_final_output =
output_types.keys().any(|ty| matches!(ty, OutputType::Mast | OutputType::Masp));
if !has_final_output {
// By default, we always produce a final artifact; `--emit` selects additional outputs.
output_types.insert(OutputType::Masp, output_file.clone());
} else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
// The -o flag overrides --emit
Expand Down
4 changes: 1 addition & 3 deletions midenc-compile/src/stages/assemble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use alloc::{string::ToString, vec, vec::Vec};

use miden_assembly::ast::QualifiedProcedureName;
use miden_mast_package::{Dependency, MastArtifact, Package, PackageExport};
use midenc_session::{Session, diagnostics::IntoDiagnostic};
use midenc_session::Session;

use super::*;

Expand Down Expand Up @@ -43,8 +43,6 @@ impl Stage for AssembleStage {
"successfully assembled mast artifact with digest {}",
DisplayHex::new(&mast.digest().as_bytes())
);
session.emit(OutputMode::Text, &mast).into_diagnostic()?;
session.emit(OutputMode::Binary, &mast).into_diagnostic()?;
Ok(Artifact::Assembled(build_package(mast, &input, session)))
} else {
log::debug!(
Expand Down
9 changes: 4 additions & 5 deletions midenc-compile/src/stages/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ impl Stage for CodegenStage {
component.borrow().to_masm_component(analysis_manager).map(Box::new)?;

let session = context.session();
if session.should_emit(OutputType::Masm) {
for module in masm_component.modules.iter() {
session.emit(OutputMode::Text, module).into_diagnostic()?;
}
}

// Ensure intrinsics modules are linked
for intrinsics_module in required_intrinsics_modules(session) {
Expand All @@ -74,6 +69,10 @@ impl Stage for CodegenStage {
masm_component.modules.push(module);
}

if session.should_emit(OutputType::Masm) {
session.emit(OutputMode::Text, masm_component.as_ref()).into_diagnostic()?;
}

Ok(CodegenOutput {
component: Arc::from(masm_component),
link_libraries,
Expand Down
38 changes: 37 additions & 1 deletion midenc-compile/src/stages/parse.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#[cfg(feature = "std")]
use alloc::borrow::Cow;
#[cfg(feature = "std")]
use alloc::string::ToString;
use alloc::{format, rc::Rc, sync::Arc};

use miden_assembly::utils::Deserializable;
#[cfg(feature = "std")]
use miden_assembly::utils::ReadAdapter;
#[cfg(feature = "std")]
use midenc_frontend_wasm::{WatEmit, wasm_to_wat};
#[cfg(feature = "std")]
use midenc_session::{FileName, Path};
use midenc_session::{
InputFile, InputType,
Expand Down Expand Up @@ -122,13 +126,45 @@ impl Stage for ParseStage {
ParseOutput::Module(ref module) => {
context.session().emit(OutputMode::Text, module).into_diagnostic()?;
}
ParseOutput::Wasm(_) | ParseOutput::Library(_) | ParseOutput::Package(_) => (),
#[cfg(feature = "std")]
ParseOutput::Wasm(ref wasm_input) => {
self.emit_wat_for_wasm_input(wasm_input, context.session())?;
}
#[cfg(not(feature = "std"))]
ParseOutput::Wasm(_) => (),
ParseOutput::Library(_) | ParseOutput::Package(_) => (),
}

Ok(parsed)
}
}
impl ParseStage {
#[cfg(feature = "std")]
fn emit_wat_for_wasm_input(&self, input: &InputType, session: &Session) -> CompilerResult<()> {
if !session.should_emit(midenc_session::OutputType::Wat) {
return Ok(());
}

let wasm_bytes: Cow<'_, [u8]> = match input {
InputType::Real(path) => {
Cow::Owned(std::fs::read(path).into_diagnostic().wrap_err_with(|| {
format!("failed to read wasm input from '{}'", path.display())
})?)
}
InputType::Stdin { input, .. } => Cow::Borrowed(input),
};

let wat = wasm_to_wat(wasm_bytes.as_ref())
.into_diagnostic()
.wrap_err("failed to convert wasm to wat")?;
let artifact = WatEmit(&wat);
session
.emit(OutputMode::Text, &artifact)
.into_diagnostic()
.wrap_err("failed to emit wat output")?;
Ok(())
}

#[cfg(feature = "std")]
fn parse_wasm_from_wat_file(&self, path: &Path) -> CompilerResult<ParseOutput> {
let wasm = wat::parse_file(path).into_diagnostic().wrap_err("failed to parse wat")?;
Expand Down
Loading