Encode a custom "producers" section in wasm files

This commit implements WebAssembly/tool-conventions#65 for wasm files
produced by the Rust compiler. This adds a bit of metadata to wasm
modules to indicate that the file's language includes Rust and the
file's "processed-by" tools includes rustc.

The thinking with this section is to eventually have telemetry in
browsers tracking all this.
This commit is contained in:
Alex Crichton 2018-11-19 12:05:21 -08:00
parent 9e8a982a23
commit 089a50411f
2 changed files with 112 additions and 0 deletions

View File

@ -692,6 +692,11 @@ fn escape_string(s: &[u8]) -> String {
if sess.opts.target_triple.triple() == "wasm32-unknown-unknown" {
wasm::rewrite_imports(&out_filename, &codegen_results.crate_info.wasm_imports);
wasm::add_producer_section(
&out_filename,
&sess.edition().to_string(),
option_env!("CFG_VERSION").unwrap_or("unknown"),
);
}
}

View File

@ -17,6 +17,7 @@
// https://webassembly.github.io/spec/core/binary/modules.html#binary-importsec
const WASM_IMPORT_SECTION_ID: u8 = 2;
const WASM_CUSTOM_SECTION_ID: u8 = 0;
const WASM_EXTERNAL_KIND_FUNCTION: u8 = 0;
const WASM_EXTERNAL_KIND_TABLE: u8 = 1;
@ -121,6 +122,112 @@ fn rewrite_import_entry(wasm: &mut WasmDecoder,
}
}
/// Add or augment the existing `producers` section to encode information about
/// the Rust compiler used to produce the wasm file.
pub fn add_producer_section(
path: &Path,
rust_version: &str,
rustc_version: &str,
) {
struct Field<'a> {
name: &'a str,
values: Vec<FieldValue<'a>>,
}
#[derive(Copy, Clone)]
struct FieldValue<'a> {
name: &'a str,
version: &'a str,
}
let wasm = fs::read(path).expect("failed to read wasm output");
let mut ret = WasmEncoder::new();
ret.data.extend(&wasm[..8]);
// skip the 8 byte wasm/version header
let rustc_value = FieldValue {
name: "rustc",
version: rustc_version,
};
let rust_value = FieldValue {
name: "Rust",
version: rust_version,
};
let mut fields = Vec::new();
let mut wrote_rustc = false;
let mut wrote_rust = false;
// Move all sections from the original wasm file to our output, skipping
// everything except the producers section
for (id, raw) in WasmSections(WasmDecoder::new(&wasm[8..])) {
if id != WASM_CUSTOM_SECTION_ID {
ret.byte(id);
ret.bytes(raw);
continue
}
let mut decoder = WasmDecoder::new(raw);
if decoder.str() != "producers" {
ret.byte(id);
ret.bytes(raw);
continue
}
// Read off the producers section into our fields outside the loop,
// we'll re-encode the producers section when we're done (to handle an
// entirely missing producers section as well).
info!("rewriting existing producers section");
for _ in 0..decoder.u32() {
let name = decoder.str();
let mut values = Vec::new();
for _ in 0..decoder.u32() {
let name = decoder.str();
let version = decoder.str();
values.push(FieldValue { name, version });
}
if name == "language" {
values.push(rust_value);
wrote_rust = true;
} else if name == "processed-by" {
values.push(rustc_value);
wrote_rustc = true;
}
fields.push(Field { name, values });
}
}
if !wrote_rust {
fields.push(Field {
name: "language",
values: vec![rust_value],
});
}
if !wrote_rustc {
fields.push(Field {
name: "processed-by",
values: vec![rustc_value],
});
}
// Append the producers section to the end of the wasm file.
let mut section = WasmEncoder::new();
section.str("producers");
section.u32(fields.len() as u32);
for field in fields {
section.str(field.name);
section.u32(field.values.len() as u32);
for value in field.values {
section.str(value.name);
section.str(value.version);
}
}
ret.byte(WASM_CUSTOM_SECTION_ID);
ret.bytes(&section.data);
fs::write(path, &ret.data).expect("failed to write wasm output");
}
struct WasmSections<'a>(WasmDecoder<'a>);
impl<'a> Iterator for WasmSections<'a> {