Skip to content

Commit

Permalink
Manipulate module & component name and producers metadata (#898)
Browse files Browse the repository at this point in the history
* add a `wasm-tools producers` subcommand which uses wasmparser::ProducerSectionReader

* wip: a producers encoder

* corrections

* add a producers section to component with wit-component's version

* cli is nearly done

* add a producers section to a module which doesnt have one

* report on a whole nested component...

* wasm-encoder: add some convenience methods

* impl ComponentSection for ProducerSection - uncontreversial
* add as_custom to ComponentNameSection and NameSection to view them as a
CustomSection.
* add as_slice to Component, mirroring Module

* rename producers subcommand to metadata

* pull metadata implementation out into a crate

* check metadata feature

* docs and stuff

* wasm-metadata: dont add a name or producers section unless necessary

* wit-component: use wasm-metadata to add names & producers

add a module name to all adapter, shim, and fixup modules, and
add a processed-by section to the overall component, and the shim and fixup
modules, since this is the tool that created those modules from whole
cloth

* display impl for metadata

* wasm-metadata: borrow during serialization

* wit-component: preserve producers section across adapter gc

* add json output

* add to readme

* metadata needs serde_json

* readme wibble

* add tests to wasm-metadata

* i had fixed the readme wrong
  • Loading branch information
Pat Hickey committed Feb 3, 2023
1 parent 418fb09 commit bdbd315
Show file tree
Hide file tree
Showing 17 changed files with 1,082 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ jobs:
- run: cargo check --no-default-features --features compose
- run: cargo check --no-default-features --features demangle
- run: cargo check --no-default-features --features component
- run: cargo check --no-default-features --features metadata

doc:
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ url = "2.3.1"

wasm-encoder = { version = "0.22.0", path = "crates/wasm-encoder"}
wasm-compose = { version = "0.2.5", path = "crates/wasm-compose"}
wasm-metadata = { version = "0.1.0", path = "crates/wasm-metadata" }
wasm-mutate = { version = "0.2.16", path = "crates/wasm-mutate" }
wasm-shrink = { version = "0.1.17", path = "crates/wasm-shrink" }
wasm-smith = { version = "0.12.0", path = "crates/wasm-smith" }
Expand Down Expand Up @@ -102,6 +103,9 @@ wit-component = { workspace = true, optional = true, features = ['dummy-module']
wit-parser = { workspace = true, optional = true }
wast = { workspace = true, optional = true }

# Dependencies of `metadata`
wasm-metadata = { workspace = true, features = ["clap"], optional = true }

[dev-dependencies]
serde_json = "1.0"
tempfile = "3.1"
Expand Down Expand Up @@ -132,6 +136,7 @@ default = [
'compose',
'demangle',
'component',
'metadata',
]

# Each subcommand is gated behind a feature and lists the dependencies it needs
Expand All @@ -147,3 +152,4 @@ strip = ['wasm-encoder', 'wasmparser', 'regex']
compose = ['wasm-compose']
demangle = ['rustc-demangle', 'cpp_demangle', 'wasmparser', 'wasm-encoder']
component = ['wit-component', 'wit-parser', 'wast', 'wasm-encoder', 'wasmparser']
metadata = ['wasmparser', 'wasm-metadata', 'serde_json' ]
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ programmatically as well:
| `wasm-tools component new` | [wit-component] | Create a component from a core wasm binary |
| `wasm-tools component wit` | | Extract a `*.wit` interface from a component |
| `wasm-tools component embed` | | Embed a `component-type` custom section in a core wasm binary |
| `wasm-tools metadata show` | [wasm-metadata] | Show name and producer metadata in a component or module |
| `wasm-tools metadata add` | | Add name or producer metadata to a component or module |

[wasmparser]: https://crates.io/crates/wasmparser
[wat]: https://crates.io/crates/wat
Expand All @@ -65,6 +67,7 @@ programmatically as well:
[wasm-shrink]: https://crates.io/crates/wasm-shrink
[wit-component]: https://crates.io/crates/wit-component
[wasm-compose]: https://crates.io/crates/wasm-compose
[wasm-metadata]: https://crates.io/crates/wasm-metadata

The `wasm-tools` CLI is primarily intended to be a debugging aid. The various
subcommands all have `--help` explainer texts to describe more about their
Expand All @@ -89,6 +92,8 @@ implemented in this repository as well. These libraries are:
files and interfaces.
* [**`wit-component`**](crates/wit-component) - a crate to create components
from core wasm modules.
* [**`wasm-metadata`**](crates/wasm-metadata) - a crate to manipulate name and
producer metadata (custom sections) in a wasm module or component.

It's recommended to use the libraries directly rather than the CLI tooling when
embedding into a separate project.
Expand Down
13 changes: 12 additions & 1 deletion crates/wasm-encoder/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub use self::names::*;
pub use self::start::*;
pub use self::types::*;

use crate::{CustomSection, Encode};
use crate::{CustomSection, Encode, ProducersSection};

// Core sorts extended by the component model
const CORE_TYPE_SORT: u8 = 0x10;
Expand Down Expand Up @@ -122,6 +122,11 @@ impl Component {
section.encode(&mut self.bytes);
self
}

/// View the encoded bytes.
pub fn as_slice(&self) -> &[u8] {
&self.bytes
}
}

impl Default for Component {
Expand All @@ -135,3 +140,9 @@ impl ComponentSection for CustomSection<'_> {
ComponentSectionId::CoreCustom.into()
}
}

impl ComponentSection for ProducersSection {
fn id(&self) -> u8 {
ComponentSectionId::CoreCustom.into()
}
}
12 changes: 8 additions & 4 deletions crates/wasm-encoder/src/component/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,19 @@ impl ComponentNameSection {
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}

impl Encode for ComponentNameSection {
fn encode(&self, sink: &mut Vec<u8>) {
/// View the encoded section as a CustomSection.
pub fn as_custom<'a>(&'a self) -> CustomSection<'a> {
CustomSection {
name: "component-name",
data: &self.bytes,
}
.encode(sink);
}
}

impl Encode for ComponentNameSection {
fn encode(&self, sink: &mut Vec<u8>) {
self.as_custom().encode(sink);
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/wasm-encoder/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod imports;
mod linking;
mod memories;
mod names;
mod producers;
mod start;
mod tables;
mod tags;
Expand All @@ -25,6 +26,7 @@ pub use imports::*;
pub use linking::*;
pub use memories::*;
pub use names::*;
pub use producers::*;
pub use start::*;
pub use tables::*;
pub use tags::*;
Expand Down
12 changes: 8 additions & 4 deletions crates/wasm-encoder/src/core/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,19 @@ impl NameSection {
self.bytes.push(id as u8);
len.encode(&mut self.bytes);
}
}

impl Encode for NameSection {
fn encode(&self, sink: &mut Vec<u8>) {
/// View the encoded section as a CustomSection.
pub fn as_custom<'a>(&'a self) -> CustomSection<'a> {
CustomSection {
name: "name",
data: &self.bytes,
}
.encode(sink);
}
}

impl Encode for NameSection {
fn encode(&self, sink: &mut Vec<u8>) {
self.as_custom().encode(sink);
}
}

Expand Down
178 changes: 178 additions & 0 deletions crates/wasm-encoder/src/core/producers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use crate::{CustomSection, Encode, Section, SectionId};

/// An encoder for the [producers custom
/// section](https://github.com/WebAssembly/tool-conventions/blob/main/ProducersSection.md).
///
/// This section is a non-standard convention that is supported by many toolchains.
///
/// # Example
///
/// ```
/// use wasm_encoder::{ProducersSection, ProducersField, Module};
///
/// // Create a new producers section.
/// let mut field = ProducersField::new();
/// field.value("clang", "14.0.4");
/// field.value("rustc", "1.66.1 (90743e729 2023-01-10)");
/// let mut producers = ProducersSection::new();
/// producers.field("processed-by", &field);
///
/// // Add the producers section to a new Wasm module and get the encoded bytes.
/// let mut module = Module::new();
/// module.section(&producers);
/// let wasm_bytes = module.finish();
/// ```
#[derive(Clone, Debug)]
pub struct ProducersSection {
bytes: Vec<u8>,
num_fields: u32,
}

impl ProducersSection {
/// Construct an empty encoder for the producers custom section.
pub fn new() -> Self {
Self::default()
}

/// Add a field to the section. The spec recommends names for this section
/// are "language", "processed-by", and "sdk". Each field in section must
/// have a unique name.
pub fn field(&mut self, name: &str, values: &ProducersField) -> &mut Self {
name.encode(&mut self.bytes);
values.encode(&mut self.bytes);
self.num_fields += 1;
self
}
}

impl Default for ProducersSection {
fn default() -> Self {
Self {
bytes: Vec::new(),
num_fields: 0,
}
}
}

impl Encode for ProducersSection {
fn encode(&self, sink: &mut Vec<u8>) {
let mut data = Vec::new();
self.num_fields.encode(&mut data);
data.extend(&self.bytes);

CustomSection {
name: "producers",
data: &data,
}
.encode(sink);
}
}

impl Section for ProducersSection {
fn id(&self) -> u8 {
SectionId::Custom.into()
}
}

/// The value of a field in the producers custom section
#[derive(Clone, Debug)]
pub struct ProducersField {
bytes: Vec<u8>,
num_values: u32,
}

impl ProducersField {
/// Construct an empty encoder for a producers field value
pub fn new() -> Self {
ProducersField::default()
}

/// Add a value to the field encoder. Each value in a field must have a
/// unique name. If there is no sensible value for `version`, use the
/// empty string.
pub fn value(&mut self, name: &str, version: &str) -> &mut Self {
name.encode(&mut self.bytes);
version.encode(&mut self.bytes);
self.num_values += 1;
self
}
}

impl Default for ProducersField {
fn default() -> Self {
Self {
bytes: Vec::new(),
num_values: 0,
}
}
}

impl Encode for ProducersField {
fn encode(&self, sink: &mut Vec<u8>) {
self.num_values.encode(sink);
sink.extend(&self.bytes);
}
}

#[cfg(test)]
mod test {
#[test]
fn roundtrip_example() {
use crate::{Module, ProducersField, ProducersSection};
use wasmparser::{Parser, Payload, ProducersSectionReader};

// Create a new producers section.
let mut field = ProducersField::new();
field.value("clang", "14.0.4");
field.value("rustc", "1.66.1");
let mut producers = ProducersSection::new();
producers.field("processed-by", &field);

// Add the producers section to a new Wasm module and get the encoded bytes.
let mut module = Module::new();
module.section(&producers);
let wasm_bytes = module.finish();

let mut parser = Parser::new(0).parse_all(&wasm_bytes);
let payload = parser
.next()
.expect("parser is not empty")
.expect("element is a payload");
match payload {
Payload::Version { .. } => {}
_ => panic!(""),
}
let payload = parser
.next()
.expect("parser is not empty")
.expect("element is a payload");
match payload {
Payload::CustomSection(c) => {
assert_eq!(c.name(), "producers");
let mut section = ProducersSectionReader::new(c.data(), c.data_offset())
.expect("readable as a producers section")
.into_iter();
let field = section
.next()
.expect("section has an element")
.expect("element is a producers field");
assert_eq!(field.name, "processed-by");
let mut values = field.values.into_iter();
let value = values
.next()
.expect("values has an element")
.expect("element is a producers field value");
assert_eq!(value.name, "clang");
assert_eq!(value.version, "14.0.4");

let value = values
.next()
.expect("values has another element")
.expect("element is a producers field value");
assert_eq!(value.name, "rustc");
assert_eq!(value.version, "1.66.1");
}
_ => panic!("unexpected payload"),
}
}
}
18 changes: 18 additions & 0 deletions crates/wasm-metadata/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "wasm-metadata"
version = "0.1.0"
edition.workspace = true
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-metadata"
description = "Read and manipulate WebAssembly metadata"

[dependencies]
clap = { workspace = true, optional = true }
anyhow = { workspace = true }
wasmparser = { workspace = true }
wasm-encoder = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
serde = { workspace = true }

[dev-dependencies]
wat = { workspace = true }
Loading

0 comments on commit bdbd315

Please sign in to comment.