-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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}; | ||||||
|
@@ -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 | ||||||
|
@@ -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); | ||||||
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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just future-proofing for wasm64, don't laugh.
Suggested change
|
||||||
|
||||||
#[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(()) | ||||||
} | ||||||
|
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; | ||
|
@@ -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() | ||
|
@@ -27,7 +33,7 @@ pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> { | |
.cloned() | ||
.collect(); | ||
Ok(EncodeResult { | ||
custom_section, | ||
custom_section_slices, | ||
included_files, | ||
}) | ||
} | ||
|
@@ -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 | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can't we store everything only in |
||
} | ||
|
||
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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
} | ||
} | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); |
There was a problem hiding this comment.
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.