Skip to content

Allow non-literal constants in typescript_custom_section #3308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup update --no-self-update 1.56.0 && rustup default 1.56.0
- run: rustup update --no-self-update 1.57.0 && rustup default 1.57.0
- run: cargo test -p wasm-bindgen-macro

build_examples:
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen"
version = "0.2.83"
version = "0.2.84"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
readme = "README.md"
Expand Down Expand Up @@ -35,7 +35,7 @@ strict-macro = ["wasm-bindgen-macro/strict-macro"]
xxx_debug_only_print_generated_code = ["wasm-bindgen-macro/xxx_debug_only_print_generated_code"]

[dependencies]
wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.83" }
wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.84" }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
cfg-if = "1.0.0"
Expand Down
4 changes: 2 additions & 2 deletions crates/backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-backend"
version = "0.2.83"
version = "0.2.84"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend"
Expand All @@ -22,4 +22,4 @@ once_cell = "1.12"
proc-macro2 = "1.0"
quote = '1.0'
syn = { version = '1.0', features = ['full'] }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.83" }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.84" }
13 changes: 12 additions & 1 deletion crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Program {
/// rust structs
pub structs: Vec<Struct>,
/// custom typescript sections to be included in the definition file
pub typescript_custom_sections: Vec<String>,
pub typescript_custom_sections: Vec<LiteralOrExpression>,
/// Inline JS snippets
pub inline_js: Vec<String>,
}
Expand Down Expand Up @@ -451,3 +451,14 @@ impl Function {
Ok(name[4..].to_string())
}
}

/// Either the value of a string literal, or the [`proc_macro2::TokenStream`]
/// representation of a constant expression.
#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub enum LiteralOrExpression {
/// Literal string.
Literal(String),
/// Expression computing a constant string.
Expression(syn::Expr),
}
124 changes: 103 additions & 21 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::ast;
use crate::encode;
use crate::util::ShortHash;
use crate::Diagnostic;
use once_cell::sync::Lazy;
use proc_macro2::{Ident, Literal, Span, TokenStream};
Expand Down Expand Up @@ -88,17 +87,8 @@ impl TryToTokens for ast::Program {
shared::SCHEMA_VERSION,
shared::version()
);
let prefix_json_len = prefix_json.len() as u32;
let encoded = encode::encode(self)?;
let len = prefix_json.len() as u32;
let bytes = [
&len.to_le_bytes()[..],
prefix_json.as_bytes(),
&encoded.custom_section,
]
.concat();

let generated_static_length = bytes.len();
let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site());

// We already consumed the contents of included files when generating
// the custom section, but we want to make sure that updates to the
Expand All @@ -113,18 +103,110 @@ impl TryToTokens for ast::Program {
quote! { include_str!(#file) }
});

(quote! {
#[cfg(target_arch = "wasm32")]
#[automatically_derived]
const _: () = {
static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];
let encoded_tokens = if let [encode::CustomSectionSlice::Literal(custom_section)] =
&encoded.custom_section_slices[..]
{
let custom_section_len = custom_section.len() as u32;
let bytes = [
&prefix_json_len.to_le_bytes()[..],
prefix_json.as_bytes(),
&custom_section_len.to_le_bytes()[..],
custom_section,
]
.concat();

let generated_static_length = bytes.len();
let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site());

quote! {
#[cfg(target_arch = "wasm32")]
#[automatically_derived]
const _: () = {
static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];

#[link_section = "__wasm_bindgen_unstable"]
pub static _GENERATED: [u8; #generated_static_length] =
*#generated_static_value;
};
}
} else {
let (prefix_json_literal_len, prefix_json_literal) = {
let bytes = [&prefix_json_len.to_le_bytes()[..], prefix_json.as_bytes()].concat();

#[link_section = "__wasm_bindgen_unstable"]
pub static _GENERATED: [u8; #generated_static_length] =
*#generated_static_value;
(bytes.len(), syn::LitByteStr::new(&bytes, Span::call_site()))
};
})
.to_tokens(tokens);
let prefix_json_literal_len_literal =
syn::LitInt::new(&prefix_json_literal_len.to_string(), Span::call_site());
let concat_args = encoded
.custom_section_slices
.iter()
.map(|slice| match slice {
encode::CustomSectionSlice::Literal(bytes) => {
let literal = syn::LitByteStr::new(bytes, Span::call_site());

quote! { #literal }
}
encode::CustomSectionSlice::Expression(expr) => {
quote! {
{
// Store `#expr` into a constant first to generate
// better error messages if `#expr` isn't a string.
const _VALUE_STR: &str = #expr;

// `const` functions cannot both evaluate the length
// they need to allocate as well as allocate that
// length, so we need to evaluate strings in two
// steps.
// Additionally, the length is encoded using
// variable-length encoding, so we don't know ahead
// of time the size of the buffer needed to encode
// the length, so we have to compute a length and
// then allocate a buffer twice in total, and then
// concatenate these values.

const _VALUE: &[u8] = _VALUE_STR.as_bytes();
const _VALUE_LEN: usize = _VALUE.len();
const _VALUE_LEN_VLE_LEN: usize =
wasm_bindgen::macro_support::vle_len(_VALUE_LEN);
const _VALUE_LEN_VLE: [u8; _VALUE_LEN_VLE_LEN] =
wasm_bindgen::macro_support::vle(_VALUE_LEN);
Comment on lines +169 to +172
Copy link
Collaborator

@daxpedda daxpedda May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is VLE really necessary here? Just always using 4 bytes sounds good enough to me.

const _TOTAL_LEN: usize = _VALUE_LEN_VLE_LEN + _VALUE_LEN;

&wasm_bindgen::macro_support::concat::<_TOTAL_LEN>(
&[&_VALUE_LEN_VLE, _VALUE])
}
}
}
});
let concat_args_clone = concat_args.clone();

quote! {
#[cfg(target_arch = "wasm32")]
#[automatically_derived]
const _: () = {
static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];

// See comment above for why we use so many intermediate
// constants.
const _GENERATED_ARGS: &[&[u8]] = &[#(#concat_args_clone),*];
const _GENERATED_ARGS_LEN: usize =
wasm_bindgen::macro_support::concat_len(_GENERATED_ARGS);
const _GENERATED_LEN: usize =
#prefix_json_literal_len_literal + 4 + _GENERATED_ARGS_LEN;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just future-proofing for wasm64, don't laugh.

Suggested change
#prefix_json_literal_len_literal + 4 + _GENERATED_ARGS_LEN;
#prefix_json_literal_len_literal + std::mem::size_of::<usize>() + _GENERATED_ARGS_LEN;


#[link_section = "__wasm_bindgen_unstable"]
pub static _GENERATED: [u8; _GENERATED_LEN] =
wasm_bindgen::macro_support::concat::<_GENERATED_LEN>(&[
#prefix_json_literal,
&_GENERATED_ARGS_LEN.to_le_bytes(),
&wasm_bindgen::macro_support::concat::<_GENERATED_ARGS_LEN>(
_GENERATED_ARGS),
]);
};
}
};

encoded_tokens.to_tokens(tokens);

Ok(())
}
Expand Down
49 changes: 40 additions & 9 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::util::ShortHash;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::env;
Expand All @@ -9,16 +10,21 @@ use std::path::PathBuf;
use crate::ast;
use crate::Diagnostic;

pub enum CustomSectionSlice {
Literal(Vec<u8>),
Expression(proc_macro2::TokenStream),
}

pub struct EncodeResult {
pub custom_section: Vec<u8>,
pub custom_section_slices: Vec<CustomSectionSlice>,
pub included_files: Vec<PathBuf>,
}

pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> {
let mut e = Encoder::new();
let i = Interner::new();
shared_program(program, &i)?.encode(&mut e);
let custom_section = e.finish();
let custom_section_slices = e.finish();
let included_files = i
.files
.borrow()
Expand All @@ -27,7 +33,7 @@ pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> {
.cloned()
.collect();
Ok(EncodeResult {
custom_section,
custom_section_slices,
included_files,
})
}
Expand Down Expand Up @@ -144,7 +150,7 @@ fn shared_program<'a>(
typescript_custom_sections: prog
.typescript_custom_sections
.iter()
.map(|x| -> &'a str { &x })
.map(|x| shared_literal_or_expression(x, intern))
.collect(),
local_modules: intern
.files
Expand Down Expand Up @@ -337,32 +343,57 @@ fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> St
}
}

fn shared_literal_or_expression<'a>(
s: &'a ast::LiteralOrExpression,
_intern: &'a Interner,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover?

) -> LiteralOrExpression<'a> {
match s {
ast::LiteralOrExpression::Literal(lit) => LiteralOrExpression::Literal(lit.as_str()),
ast::LiteralOrExpression::Expression(expr) => LiteralOrExpression::Expression(expr),
}
}

trait Encode {
fn encode(&self, dst: &mut Encoder);
}

struct Encoder {
slices: Vec<CustomSectionSlice>,
dst: Vec<u8>,
Comment on lines +361 to 362
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we store everything only in Vec<CustomSectionSlice>, feels like I'm missing something.

}

impl Encoder {
fn new() -> Encoder {
Encoder {
dst: vec![0, 0, 0, 0],
slices: Vec::new(),
dst: Vec::new(),
}
}

fn finish(mut self) -> Vec<u8> {
let len = (self.dst.len() - 4) as u32;
self.dst[..4].copy_from_slice(&len.to_le_bytes()[..]);
self.dst
fn finish(self) -> Vec<CustomSectionSlice> {
let Self { mut slices, dst } = self;
if !dst.is_empty() {
slices.push(CustomSectionSlice::Literal(dst));
}
slices
}

fn byte(&mut self, byte: u8) {
self.dst.push(byte);
}
}

impl Encode for syn::Expr {
fn encode(&self, dst: &mut Encoder) {
let tokens = self.to_token_stream();
if !dst.dst.is_empty() {
dst.slices
.push(CustomSectionSlice::Literal(std::mem::take(&mut dst.dst)));
}
dst.slices.push(CustomSectionSlice::Expression(tokens));
}
}

impl Encode for bool {
fn encode(&self, dst: &mut Encoder) {
dst.byte(*self as u8);
Expand Down
14 changes: 7 additions & 7 deletions crates/cli-support/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-cli-support"
version = "0.2.83"
version = "0.2.84"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support"
Expand All @@ -19,12 +19,12 @@ rustc-demangle = "0.1.13"
serde_json = "1.0"
tempfile = "3.0"
walrus = "0.19.0"
wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.83' }
wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.83' }
wasm-bindgen-shared = { path = "../shared", version = '=0.2.83' }
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.83' }
wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.83' }
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.83' }
wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.84' }
wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.84' }
wasm-bindgen-shared = { path = "../shared", version = '=0.2.84' }
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.84' }
wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.84' }
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.84' }
wit-text = "0.8.0"
wit-walrus = "0.6.0"
wit-validator = "0.2.0"
9 changes: 8 additions & 1 deletion crates/cli-support/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub trait Decode<'src>: Sized {

fn decode_all(mut data: &'src [u8]) -> Self {
let ret = Self::decode(&mut data);
assert!(data.len() == 0);
assert_eq!(data.len(), 0);
return ret;
}
}
Expand Down Expand Up @@ -154,4 +154,11 @@ macro_rules! decode_api {
);
}

mod syn {
/// Alias `syn::Expr` to `str` when decoding; an expression is evaluated as
/// a Rust constant during compilation, so when we decode the buffer we can
/// expect a string instead.
pub type Expr = str;
}
Comment on lines +157 to +162
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pass this into the macro instead?


wasm_bindgen_shared::shared_api!(decode_api);
5 changes: 5 additions & 0 deletions crates/cli-support/src/wit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,11 @@ impl<'a> Context<'a> {
self.struct_(struct_)?;
}
for section in typescript_custom_sections {
let section = match section {
decode::LiteralOrExpression::Literal(section) => section,
decode::LiteralOrExpression::Expression(x) => x, // match *x {},
};

self.aux.extra_typescript.push_str(section);
self.aux.extra_typescript.push_str("\n\n");
}
Expand Down
Loading