diff --git a/.cargo/config.toml b/.cargo/config.toml index 6440da0591e..e787276b404 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,12 @@ [target.'cfg(target_arch = "wasm32")'] runner = 'cargo run -p wasm-bindgen-cli --bin wasm-bindgen-test-runner --' + +[build] + +[target.'cfg(all(target_arch = "wasm32", target_os = "emscripten"))'] +rustflags = [ + "-Cllvm-args=-enable-emscripten-cxx-exceptions=0", + "-Clink-arg=-sERROR_ON_UNDEFINED_SYMBOLS=0", + "-Clink-arg=-Wno-undefined", + "-Crelocation-model=static", +] diff --git a/0001-Add-additional-licenses-to-futures-and-web-sys-crate.patch b/0001-Add-additional-licenses-to-futures-and-web-sys-crate.patch new file mode 100644 index 00000000000..e456fc130f3 --- /dev/null +++ b/0001-Add-additional-licenses-to-futures-and-web-sys-crate.patch @@ -0,0 +1,830 @@ +From f601a40e0b27e9b761af626468df61b742fe584f Mon Sep 17 00:00:00 2001 +From: Martin Braenne +Date: Wed, 19 Mar 2025 14:48:22 +0000 +Subject: [PATCH] Add additional licenses to "futures" and "web-sys" crates. + +Various source files mention these licenses, but they have not so far +been included as LICENSE files or referenced in the Cargo.toml files for +the crates. + +The MPL 2.0 license is used by +creates/futures/src/task/wait_async_polyfill.rs as well as many files in +crates/web-sys/webidls/enabled. + +The BSD 3-Clause license is used by +crates/web-sys/webidls/enabled/Streams.webidl. +--- + crates/futures/Cargo.toml | 2 +- + crates/futures/LICENSE-MPL-2.0 | 373 ++++++++++++++++++++++++++++ + crates/web-sys/Cargo.toml | 2 +- + crates/web-sys/LICENSE-BSD-3-Clause | 11 + + crates/web-sys/LICENSE-MPL-2.0 | 373 ++++++++++++++++++++++++++++ + 5 files changed, 759 insertions(+), 2 deletions(-) + create mode 100644 crates/futures/LICENSE-MPL-2.0 + create mode 100644 crates/web-sys/LICENSE-BSD-3-Clause + create mode 100644 crates/web-sys/LICENSE-MPL-2.0 + +diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml +index caf381c89..7b7f54cc8 100644 +--- a/crates/futures/Cargo.toml ++++ b/crates/futures/Cargo.toml +@@ -5,7 +5,7 @@ documentation = "https://docs.rs/wasm-bindgen-futures" + edition = "2021" + homepage = "https://rustwasm.github.io/wasm-bindgen/" + include = ["/LICENSE-*", "/src"] +-license = "MIT OR Apache-2.0" ++license = "(MIT OR Apache-2.0) AND MPL-2.0" + name = "wasm-bindgen-futures" + readme = "./README.md" + repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures" +diff --git a/crates/futures/LICENSE-MPL-2.0 b/crates/futures/LICENSE-MPL-2.0 +new file mode 100644 +index 000000000..ee6256cdb +--- /dev/null ++++ b/crates/futures/LICENSE-MPL-2.0 +@@ -0,0 +1,373 @@ ++Mozilla Public License Version 2.0 ++================================== ++ ++1. Definitions ++-------------- ++ ++1.1. "Contributor" ++ means each individual or legal entity that creates, contributes to ++ the creation of, or owns Covered Software. ++ ++1.2. "Contributor Version" ++ means the combination of the Contributions of others (if any) used ++ by a Contributor and that particular Contributor's Contribution. ++ ++1.3. "Contribution" ++ means Covered Software of a particular Contributor. ++ ++1.4. "Covered Software" ++ means Source Code Form to which the initial Contributor has attached ++ the notice in Exhibit A, the Executable Form of such Source Code ++ Form, and Modifications of such Source Code Form, in each case ++ including portions thereof. ++ ++1.5. "Incompatible With Secondary Licenses" ++ means ++ ++ (a) that the initial Contributor has attached the notice described ++ in Exhibit B to the Covered Software; or ++ ++ (b) that the Covered Software was made available under the terms of ++ version 1.1 or earlier of the License, but not also under the ++ terms of a Secondary License. ++ ++1.6. "Executable Form" ++ means any form of the work other than Source Code Form. ++ ++1.7. "Larger Work" ++ means a work that combines Covered Software with other material, in ++ a separate file or files, that is not Covered Software. ++ ++1.8. "License" ++ means this document. ++ ++1.9. "Licensable" ++ means having the right to grant, to the maximum extent possible, ++ whether at the time of the initial grant or subsequently, any and ++ all of the rights conveyed by this License. ++ ++1.10. "Modifications" ++ means any of the following: ++ ++ (a) any file in Source Code Form that results from an addition to, ++ deletion from, or modification of the contents of Covered ++ Software; or ++ ++ (b) any new file in Source Code Form that contains any Covered ++ Software. ++ ++1.11. "Patent Claims" of a Contributor ++ means any patent claim(s), including without limitation, method, ++ process, and apparatus claims, in any patent Licensable by such ++ Contributor that would be infringed, but for the grant of the ++ License, by the making, using, selling, offering for sale, having ++ made, import, or transfer of either its Contributions or its ++ Contributor Version. ++ ++1.12. "Secondary License" ++ means either the GNU General Public License, Version 2.0, the GNU ++ Lesser General Public License, Version 2.1, the GNU Affero General ++ Public License, Version 3.0, or any later versions of those ++ licenses. ++ ++1.13. "Source Code Form" ++ means the form of the work preferred for making modifications. ++ ++1.14. "You" (or "Your") ++ means an individual or a legal entity exercising rights under this ++ License. For legal entities, "You" includes any entity that ++ controls, is controlled by, or is under common control with You. For ++ purposes of this definition, "control" means (a) the power, direct ++ or indirect, to cause the direction or management of such entity, ++ whether by contract or otherwise, or (b) ownership of more than ++ fifty percent (50%) of the outstanding shares or beneficial ++ ownership of such entity. ++ ++2. License Grants and Conditions ++-------------------------------- ++ ++2.1. Grants ++ ++Each Contributor hereby grants You a world-wide, royalty-free, ++non-exclusive license: ++ ++(a) under intellectual property rights (other than patent or trademark) ++ Licensable by such Contributor to use, reproduce, make available, ++ modify, display, perform, distribute, and otherwise exploit its ++ Contributions, either on an unmodified basis, with Modifications, or ++ as part of a Larger Work; and ++ ++(b) under Patent Claims of such Contributor to make, use, sell, offer ++ for sale, have made, import, and otherwise transfer either its ++ Contributions or its Contributor Version. ++ ++2.2. Effective Date ++ ++The licenses granted in Section 2.1 with respect to any Contribution ++become effective for each Contribution on the date the Contributor first ++distributes such Contribution. ++ ++2.3. Limitations on Grant Scope ++ ++The licenses granted in this Section 2 are the only rights granted under ++this License. No additional rights or licenses will be implied from the ++distribution or licensing of Covered Software under this License. ++Notwithstanding Section 2.1(b) above, no patent license is granted by a ++Contributor: ++ ++(a) for any code that a Contributor has removed from Covered Software; ++ or ++ ++(b) for infringements caused by: (i) Your and any other third party's ++ modifications of Covered Software, or (ii) the combination of its ++ Contributions with other software (except as part of its Contributor ++ Version); or ++ ++(c) under Patent Claims infringed by Covered Software in the absence of ++ its Contributions. ++ ++This License does not grant any rights in the trademarks, service marks, ++or logos of any Contributor (except as may be necessary to comply with ++the notice requirements in Section 3.4). ++ ++2.4. Subsequent Licenses ++ ++No Contributor makes additional grants as a result of Your choice to ++distribute the Covered Software under a subsequent version of this ++License (see Section 10.2) or under the terms of a Secondary License (if ++permitted under the terms of Section 3.3). ++ ++2.5. Representation ++ ++Each Contributor represents that the Contributor believes its ++Contributions are its original creation(s) or it has sufficient rights ++to grant the rights to its Contributions conveyed by this License. ++ ++2.6. Fair Use ++ ++This License is not intended to limit any rights You have under ++applicable copyright doctrines of fair use, fair dealing, or other ++equivalents. ++ ++2.7. Conditions ++ ++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted ++in Section 2.1. ++ ++3. Responsibilities ++------------------- ++ ++3.1. Distribution of Source Form ++ ++All distribution of Covered Software in Source Code Form, including any ++Modifications that You create or to which You contribute, must be under ++the terms of this License. You must inform recipients that the Source ++Code Form of the Covered Software is governed by the terms of this ++License, and how they can obtain a copy of this License. You may not ++attempt to alter or restrict the recipients' rights in the Source Code ++Form. ++ ++3.2. Distribution of Executable Form ++ ++If You distribute Covered Software in Executable Form then: ++ ++(a) such Covered Software must also be made available in Source Code ++ Form, as described in Section 3.1, and You must inform recipients of ++ the Executable Form how they can obtain a copy of such Source Code ++ Form by reasonable means in a timely manner, at a charge no more ++ than the cost of distribution to the recipient; and ++ ++(b) You may distribute such Executable Form under the terms of this ++ License, or sublicense it under different terms, provided that the ++ license for the Executable Form does not attempt to limit or alter ++ the recipients' rights in the Source Code Form under this License. ++ ++3.3. Distribution of a Larger Work ++ ++You may create and distribute a Larger Work under terms of Your choice, ++provided that You also comply with the requirements of this License for ++the Covered Software. If the Larger Work is a combination of Covered ++Software with a work governed by one or more Secondary Licenses, and the ++Covered Software is not Incompatible With Secondary Licenses, this ++License permits You to additionally distribute such Covered Software ++under the terms of such Secondary License(s), so that the recipient of ++the Larger Work may, at their option, further distribute the Covered ++Software under the terms of either this License or such Secondary ++License(s). ++ ++3.4. Notices ++ ++You may not remove or alter the substance of any license notices ++(including copyright notices, patent notices, disclaimers of warranty, ++or limitations of liability) contained within the Source Code Form of ++the Covered Software, except that You may alter any license notices to ++the extent required to remedy known factual inaccuracies. ++ ++3.5. Application of Additional Terms ++ ++You may choose to offer, and to charge a fee for, warranty, support, ++indemnity or liability obligations to one or more recipients of Covered ++Software. However, You may do so only on Your own behalf, and not on ++behalf of any Contributor. You must make it absolutely clear that any ++such warranty, support, indemnity, or liability obligation is offered by ++You alone, and You hereby agree to indemnify every Contributor for any ++liability incurred by such Contributor as a result of warranty, support, ++indemnity or liability terms You offer. You may include additional ++disclaimers of warranty and limitations of liability specific to any ++jurisdiction. ++ ++4. Inability to Comply Due to Statute or Regulation ++--------------------------------------------------- ++ ++If it is impossible for You to comply with any of the terms of this ++License with respect to some or all of the Covered Software due to ++statute, judicial order, or regulation then You must: (a) comply with ++the terms of this License to the maximum extent possible; and (b) ++describe the limitations and the code they affect. Such description must ++be placed in a text file included with all distributions of the Covered ++Software under this License. Except to the extent prohibited by statute ++or regulation, such description must be sufficiently detailed for a ++recipient of ordinary skill to be able to understand it. ++ ++5. Termination ++-------------- ++ ++5.1. The rights granted under this License will terminate automatically ++if You fail to comply with any of its terms. However, if You become ++compliant, then the rights granted under this License from a particular ++Contributor are reinstated (a) provisionally, unless and until such ++Contributor explicitly and finally terminates Your grants, and (b) on an ++ongoing basis, if such Contributor fails to notify You of the ++non-compliance by some reasonable means prior to 60 days after You have ++come back into compliance. Moreover, Your grants from a particular ++Contributor are reinstated on an ongoing basis if such Contributor ++notifies You of the non-compliance by some reasonable means, this is the ++first time You have received notice of non-compliance with this License ++from such Contributor, and You become compliant prior to 30 days after ++Your receipt of the notice. ++ ++5.2. If You initiate litigation against any entity by asserting a patent ++infringement claim (excluding declaratory judgment actions, ++counter-claims, and cross-claims) alleging that a Contributor Version ++directly or indirectly infringes any patent, then the rights granted to ++You by any and all Contributors for the Covered Software under Section ++2.1 of this License shall terminate. ++ ++5.3. In the event of termination under Sections 5.1 or 5.2 above, all ++end user license agreements (excluding distributors and resellers) which ++have been validly granted by You or Your distributors under this License ++prior to termination shall survive termination. ++ ++************************************************************************ ++* * ++* 6. Disclaimer of Warranty * ++* ------------------------- * ++* * ++* Covered Software is provided under this License on an "as is" * ++* basis, without warranty of any kind, either expressed, implied, or * ++* statutory, including, without limitation, warranties that the * ++* Covered Software is free of defects, merchantable, fit for a * ++* particular purpose or non-infringing. The entire risk as to the * ++* quality and performance of the Covered Software is with You. * ++* Should any Covered Software prove defective in any respect, You * ++* (not any Contributor) assume the cost of any necessary servicing, * ++* repair, or correction. This disclaimer of warranty constitutes an * ++* essential part of this License. No use of any Covered Software is * ++* authorized under this License except under this disclaimer. * ++* * ++************************************************************************ ++ ++************************************************************************ ++* * ++* 7. Limitation of Liability * ++* -------------------------- * ++* * ++* Under no circumstances and under no legal theory, whether tort * ++* (including negligence), contract, or otherwise, shall any * ++* Contributor, or anyone who distributes Covered Software as * ++* permitted above, be liable to You for any direct, indirect, * ++* special, incidental, or consequential damages of any character * ++* including, without limitation, damages for lost profits, loss of * ++* goodwill, work stoppage, computer failure or malfunction, or any * ++* and all other commercial damages or losses, even if such party * ++* shall have been informed of the possibility of such damages. This * ++* limitation of liability shall not apply to liability for death or * ++* personal injury resulting from such party's negligence to the * ++* extent applicable law prohibits such limitation. Some * ++* jurisdictions do not allow the exclusion or limitation of * ++* incidental or consequential damages, so this exclusion and * ++* limitation may not apply to You. * ++* * ++************************************************************************ ++ ++8. Litigation ++------------- ++ ++Any litigation relating to this License may be brought only in the ++courts of a jurisdiction where the defendant maintains its principal ++place of business and such litigation shall be governed by laws of that ++jurisdiction, without reference to its conflict-of-law provisions. ++Nothing in this Section shall prevent a party's ability to bring ++cross-claims or counter-claims. ++ ++9. Miscellaneous ++---------------- ++ ++This License represents the complete agreement concerning the subject ++matter hereof. If any provision of this License is held to be ++unenforceable, such provision shall be reformed only to the extent ++necessary to make it enforceable. Any law or regulation which provides ++that the language of a contract shall be construed against the drafter ++shall not be used to construe this License against a Contributor. ++ ++10. Versions of the License ++--------------------------- ++ ++10.1. New Versions ++ ++Mozilla Foundation is the license steward. Except as provided in Section ++10.3, no one other than the license steward has the right to modify or ++publish new versions of this License. Each version will be given a ++distinguishing version number. ++ ++10.2. Effect of New Versions ++ ++You may distribute the Covered Software under the terms of the version ++of the License under which You originally received the Covered Software, ++or under the terms of any subsequent version published by the license ++steward. ++ ++10.3. Modified Versions ++ ++If you create software not governed by this License, and you want to ++create a new license for such software, you may create and use a ++modified version of this License if you rename the license and remove ++any references to the name of the license steward (except to note that ++such modified license differs from this License). ++ ++10.4. Distributing Source Code Form that is Incompatible With Secondary ++Licenses ++ ++If You choose to distribute Source Code Form that is Incompatible With ++Secondary Licenses under the terms of this version of the License, the ++notice described in Exhibit B of this License must be attached. ++ ++Exhibit A - Source Code Form License Notice ++------------------------------------------- ++ ++ This Source Code Form is subject to the terms of the Mozilla Public ++ License, v. 2.0. If a copy of the MPL was not distributed with this ++ file, You can obtain one at https://mozilla.org/MPL/2.0/. ++ ++If it is not possible or desirable to put the notice in a particular ++file, then You may include the notice in a location (such as a LICENSE ++file in a relevant directory) where a recipient would be likely to look ++for such a notice. ++ ++You may add additional accurate notices of copyright ownership. ++ ++Exhibit B - "Incompatible With Secondary Licenses" Notice ++--------------------------------------------------------- ++ ++ This Source Code Form is "Incompatible With Secondary Licenses", as ++ defined by the Mozilla Public License, v. 2.0. +diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml +index c3a73eaa3..24c73d8de 100644 +--- a/crates/web-sys/Cargo.toml ++++ b/crates/web-sys/Cargo.toml +@@ -7,7 +7,7 @@ documentation = "https://rustwasm.github.io/wasm-bindgen/api/web_sys/" + edition = "2021" + homepage = "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html" + include = ["/LICENSE-*", "/src"] +-license = "MIT OR Apache-2.0" ++license = "(MIT OR Apache-2.0) AND MPL-2.0 AND BSD-3-Clause" + name = "web-sys" + readme = "./README.md" + repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys" +diff --git a/crates/web-sys/LICENSE-BSD-3-Clause b/crates/web-sys/LICENSE-BSD-3-Clause +new file mode 100644 +index 000000000..db7d3cd12 +--- /dev/null ++++ b/crates/web-sys/LICENSE-BSD-3-Clause +@@ -0,0 +1,11 @@ ++Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). ++ ++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: ++ ++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. ++ ++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. ++ ++3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. ++ ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +diff --git a/crates/web-sys/LICENSE-MPL-2.0 b/crates/web-sys/LICENSE-MPL-2.0 +new file mode 100644 +index 000000000..ee6256cdb +--- /dev/null ++++ b/crates/web-sys/LICENSE-MPL-2.0 +@@ -0,0 +1,373 @@ ++Mozilla Public License Version 2.0 ++================================== ++ ++1. Definitions ++-------------- ++ ++1.1. "Contributor" ++ means each individual or legal entity that creates, contributes to ++ the creation of, or owns Covered Software. ++ ++1.2. "Contributor Version" ++ means the combination of the Contributions of others (if any) used ++ by a Contributor and that particular Contributor's Contribution. ++ ++1.3. "Contribution" ++ means Covered Software of a particular Contributor. ++ ++1.4. "Covered Software" ++ means Source Code Form to which the initial Contributor has attached ++ the notice in Exhibit A, the Executable Form of such Source Code ++ Form, and Modifications of such Source Code Form, in each case ++ including portions thereof. ++ ++1.5. "Incompatible With Secondary Licenses" ++ means ++ ++ (a) that the initial Contributor has attached the notice described ++ in Exhibit B to the Covered Software; or ++ ++ (b) that the Covered Software was made available under the terms of ++ version 1.1 or earlier of the License, but not also under the ++ terms of a Secondary License. ++ ++1.6. "Executable Form" ++ means any form of the work other than Source Code Form. ++ ++1.7. "Larger Work" ++ means a work that combines Covered Software with other material, in ++ a separate file or files, that is not Covered Software. ++ ++1.8. "License" ++ means this document. ++ ++1.9. "Licensable" ++ means having the right to grant, to the maximum extent possible, ++ whether at the time of the initial grant or subsequently, any and ++ all of the rights conveyed by this License. ++ ++1.10. "Modifications" ++ means any of the following: ++ ++ (a) any file in Source Code Form that results from an addition to, ++ deletion from, or modification of the contents of Covered ++ Software; or ++ ++ (b) any new file in Source Code Form that contains any Covered ++ Software. ++ ++1.11. "Patent Claims" of a Contributor ++ means any patent claim(s), including without limitation, method, ++ process, and apparatus claims, in any patent Licensable by such ++ Contributor that would be infringed, but for the grant of the ++ License, by the making, using, selling, offering for sale, having ++ made, import, or transfer of either its Contributions or its ++ Contributor Version. ++ ++1.12. "Secondary License" ++ means either the GNU General Public License, Version 2.0, the GNU ++ Lesser General Public License, Version 2.1, the GNU Affero General ++ Public License, Version 3.0, or any later versions of those ++ licenses. ++ ++1.13. "Source Code Form" ++ means the form of the work preferred for making modifications. ++ ++1.14. "You" (or "Your") ++ means an individual or a legal entity exercising rights under this ++ License. For legal entities, "You" includes any entity that ++ controls, is controlled by, or is under common control with You. For ++ purposes of this definition, "control" means (a) the power, direct ++ or indirect, to cause the direction or management of such entity, ++ whether by contract or otherwise, or (b) ownership of more than ++ fifty percent (50%) of the outstanding shares or beneficial ++ ownership of such entity. ++ ++2. License Grants and Conditions ++-------------------------------- ++ ++2.1. Grants ++ ++Each Contributor hereby grants You a world-wide, royalty-free, ++non-exclusive license: ++ ++(a) under intellectual property rights (other than patent or trademark) ++ Licensable by such Contributor to use, reproduce, make available, ++ modify, display, perform, distribute, and otherwise exploit its ++ Contributions, either on an unmodified basis, with Modifications, or ++ as part of a Larger Work; and ++ ++(b) under Patent Claims of such Contributor to make, use, sell, offer ++ for sale, have made, import, and otherwise transfer either its ++ Contributions or its Contributor Version. ++ ++2.2. Effective Date ++ ++The licenses granted in Section 2.1 with respect to any Contribution ++become effective for each Contribution on the date the Contributor first ++distributes such Contribution. ++ ++2.3. Limitations on Grant Scope ++ ++The licenses granted in this Section 2 are the only rights granted under ++this License. No additional rights or licenses will be implied from the ++distribution or licensing of Covered Software under this License. ++Notwithstanding Section 2.1(b) above, no patent license is granted by a ++Contributor: ++ ++(a) for any code that a Contributor has removed from Covered Software; ++ or ++ ++(b) for infringements caused by: (i) Your and any other third party's ++ modifications of Covered Software, or (ii) the combination of its ++ Contributions with other software (except as part of its Contributor ++ Version); or ++ ++(c) under Patent Claims infringed by Covered Software in the absence of ++ its Contributions. ++ ++This License does not grant any rights in the trademarks, service marks, ++or logos of any Contributor (except as may be necessary to comply with ++the notice requirements in Section 3.4). ++ ++2.4. Subsequent Licenses ++ ++No Contributor makes additional grants as a result of Your choice to ++distribute the Covered Software under a subsequent version of this ++License (see Section 10.2) or under the terms of a Secondary License (if ++permitted under the terms of Section 3.3). ++ ++2.5. Representation ++ ++Each Contributor represents that the Contributor believes its ++Contributions are its original creation(s) or it has sufficient rights ++to grant the rights to its Contributions conveyed by this License. ++ ++2.6. Fair Use ++ ++This License is not intended to limit any rights You have under ++applicable copyright doctrines of fair use, fair dealing, or other ++equivalents. ++ ++2.7. Conditions ++ ++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted ++in Section 2.1. ++ ++3. Responsibilities ++------------------- ++ ++3.1. Distribution of Source Form ++ ++All distribution of Covered Software in Source Code Form, including any ++Modifications that You create or to which You contribute, must be under ++the terms of this License. You must inform recipients that the Source ++Code Form of the Covered Software is governed by the terms of this ++License, and how they can obtain a copy of this License. You may not ++attempt to alter or restrict the recipients' rights in the Source Code ++Form. ++ ++3.2. Distribution of Executable Form ++ ++If You distribute Covered Software in Executable Form then: ++ ++(a) such Covered Software must also be made available in Source Code ++ Form, as described in Section 3.1, and You must inform recipients of ++ the Executable Form how they can obtain a copy of such Source Code ++ Form by reasonable means in a timely manner, at a charge no more ++ than the cost of distribution to the recipient; and ++ ++(b) You may distribute such Executable Form under the terms of this ++ License, or sublicense it under different terms, provided that the ++ license for the Executable Form does not attempt to limit or alter ++ the recipients' rights in the Source Code Form under this License. ++ ++3.3. Distribution of a Larger Work ++ ++You may create and distribute a Larger Work under terms of Your choice, ++provided that You also comply with the requirements of this License for ++the Covered Software. If the Larger Work is a combination of Covered ++Software with a work governed by one or more Secondary Licenses, and the ++Covered Software is not Incompatible With Secondary Licenses, this ++License permits You to additionally distribute such Covered Software ++under the terms of such Secondary License(s), so that the recipient of ++the Larger Work may, at their option, further distribute the Covered ++Software under the terms of either this License or such Secondary ++License(s). ++ ++3.4. Notices ++ ++You may not remove or alter the substance of any license notices ++(including copyright notices, patent notices, disclaimers of warranty, ++or limitations of liability) contained within the Source Code Form of ++the Covered Software, except that You may alter any license notices to ++the extent required to remedy known factual inaccuracies. ++ ++3.5. Application of Additional Terms ++ ++You may choose to offer, and to charge a fee for, warranty, support, ++indemnity or liability obligations to one or more recipients of Covered ++Software. However, You may do so only on Your own behalf, and not on ++behalf of any Contributor. You must make it absolutely clear that any ++such warranty, support, indemnity, or liability obligation is offered by ++You alone, and You hereby agree to indemnify every Contributor for any ++liability incurred by such Contributor as a result of warranty, support, ++indemnity or liability terms You offer. You may include additional ++disclaimers of warranty and limitations of liability specific to any ++jurisdiction. ++ ++4. Inability to Comply Due to Statute or Regulation ++--------------------------------------------------- ++ ++If it is impossible for You to comply with any of the terms of this ++License with respect to some or all of the Covered Software due to ++statute, judicial order, or regulation then You must: (a) comply with ++the terms of this License to the maximum extent possible; and (b) ++describe the limitations and the code they affect. Such description must ++be placed in a text file included with all distributions of the Covered ++Software under this License. Except to the extent prohibited by statute ++or regulation, such description must be sufficiently detailed for a ++recipient of ordinary skill to be able to understand it. ++ ++5. Termination ++-------------- ++ ++5.1. The rights granted under this License will terminate automatically ++if You fail to comply with any of its terms. However, if You become ++compliant, then the rights granted under this License from a particular ++Contributor are reinstated (a) provisionally, unless and until such ++Contributor explicitly and finally terminates Your grants, and (b) on an ++ongoing basis, if such Contributor fails to notify You of the ++non-compliance by some reasonable means prior to 60 days after You have ++come back into compliance. Moreover, Your grants from a particular ++Contributor are reinstated on an ongoing basis if such Contributor ++notifies You of the non-compliance by some reasonable means, this is the ++first time You have received notice of non-compliance with this License ++from such Contributor, and You become compliant prior to 30 days after ++Your receipt of the notice. ++ ++5.2. If You initiate litigation against any entity by asserting a patent ++infringement claim (excluding declaratory judgment actions, ++counter-claims, and cross-claims) alleging that a Contributor Version ++directly or indirectly infringes any patent, then the rights granted to ++You by any and all Contributors for the Covered Software under Section ++2.1 of this License shall terminate. ++ ++5.3. In the event of termination under Sections 5.1 or 5.2 above, all ++end user license agreements (excluding distributors and resellers) which ++have been validly granted by You or Your distributors under this License ++prior to termination shall survive termination. ++ ++************************************************************************ ++* * ++* 6. Disclaimer of Warranty * ++* ------------------------- * ++* * ++* Covered Software is provided under this License on an "as is" * ++* basis, without warranty of any kind, either expressed, implied, or * ++* statutory, including, without limitation, warranties that the * ++* Covered Software is free of defects, merchantable, fit for a * ++* particular purpose or non-infringing. The entire risk as to the * ++* quality and performance of the Covered Software is with You. * ++* Should any Covered Software prove defective in any respect, You * ++* (not any Contributor) assume the cost of any necessary servicing, * ++* repair, or correction. This disclaimer of warranty constitutes an * ++* essential part of this License. No use of any Covered Software is * ++* authorized under this License except under this disclaimer. * ++* * ++************************************************************************ ++ ++************************************************************************ ++* * ++* 7. Limitation of Liability * ++* -------------------------- * ++* * ++* Under no circumstances and under no legal theory, whether tort * ++* (including negligence), contract, or otherwise, shall any * ++* Contributor, or anyone who distributes Covered Software as * ++* permitted above, be liable to You for any direct, indirect, * ++* special, incidental, or consequential damages of any character * ++* including, without limitation, damages for lost profits, loss of * ++* goodwill, work stoppage, computer failure or malfunction, or any * ++* and all other commercial damages or losses, even if such party * ++* shall have been informed of the possibility of such damages. This * ++* limitation of liability shall not apply to liability for death or * ++* personal injury resulting from such party's negligence to the * ++* extent applicable law prohibits such limitation. Some * ++* jurisdictions do not allow the exclusion or limitation of * ++* incidental or consequential damages, so this exclusion and * ++* limitation may not apply to You. * ++* * ++************************************************************************ ++ ++8. Litigation ++------------- ++ ++Any litigation relating to this License may be brought only in the ++courts of a jurisdiction where the defendant maintains its principal ++place of business and such litigation shall be governed by laws of that ++jurisdiction, without reference to its conflict-of-law provisions. ++Nothing in this Section shall prevent a party's ability to bring ++cross-claims or counter-claims. ++ ++9. Miscellaneous ++---------------- ++ ++This License represents the complete agreement concerning the subject ++matter hereof. If any provision of this License is held to be ++unenforceable, such provision shall be reformed only to the extent ++necessary to make it enforceable. Any law or regulation which provides ++that the language of a contract shall be construed against the drafter ++shall not be used to construe this License against a Contributor. ++ ++10. Versions of the License ++--------------------------- ++ ++10.1. New Versions ++ ++Mozilla Foundation is the license steward. Except as provided in Section ++10.3, no one other than the license steward has the right to modify or ++publish new versions of this License. Each version will be given a ++distinguishing version number. ++ ++10.2. Effect of New Versions ++ ++You may distribute the Covered Software under the terms of the version ++of the License under which You originally received the Covered Software, ++or under the terms of any subsequent version published by the license ++steward. ++ ++10.3. Modified Versions ++ ++If you create software not governed by this License, and you want to ++create a new license for such software, you may create and use a ++modified version of this License if you rename the license and remove ++any references to the name of the license steward (except to note that ++such modified license differs from this License). ++ ++10.4. Distributing Source Code Form that is Incompatible With Secondary ++Licenses ++ ++If You choose to distribute Source Code Form that is Incompatible With ++Secondary Licenses under the terms of this version of the License, the ++notice described in Exhibit B of this License must be attached. ++ ++Exhibit A - Source Code Form License Notice ++------------------------------------------- ++ ++ This Source Code Form is subject to the terms of the Mozilla Public ++ License, v. 2.0. If a copy of the MPL was not distributed with this ++ file, You can obtain one at https://mozilla.org/MPL/2.0/. ++ ++If it is not possible or desirable to put the notice in a particular ++file, then You may include the notice in a location (such as a LICENSE ++file in a relevant directory) where a recipient would be likely to look ++for such a notice. ++ ++You may add additional accurate notices of copyright ownership. ++ ++Exhibit B - "Incompatible With Secondary Licenses" Notice ++--------------------------------------------------------- ++ ++ This Source Code Form is "Incompatible With Secondary Licenses", as ++ defined by the Mozilla Public License, v. 2.0. +-- +2.49.0.rc1.451.g8f38331e32-goog + diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 73056b70390..f1bb1ffe1f5 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -162,7 +162,7 @@ impl TryToTokens for ast::Program { let prefix_json_bytes = syn::LitByteStr::new(&prefix_json_bytes, Span::call_site()); (quote! { - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] #[automatically_derived] const _: () = { use #wasm_bindgen::__rt::{flat_len, flat_byte_slices}; @@ -281,12 +281,12 @@ impl ToTokens for ast::Struct { let ptr = #wasm_bindgen::convert::IntoWasmAbi::into_abi(value); #[link(wasm_import_module = "__wbindgen_placeholder__")] - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] extern "C" { fn #new_fn(ptr: u32) -> u32; } - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] + #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))))] unsafe fn #new_fn(_: u32) -> u32 { panic!("cannot convert to JsValue outside of the Wasm target") } @@ -298,7 +298,7 @@ impl ToTokens for ast::Struct { } } - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { @@ -388,12 +388,12 @@ impl ToTokens for ast::Struct { let idx = #wasm_bindgen::convert::IntoWasmAbi::into_abi(&value); #[link(wasm_import_module = "__wbindgen_placeholder__")] - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] extern "C" { fn #unwrap_fn(ptr: u32) -> u32; } - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] + #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))))] unsafe fn #unwrap_fn(_: u32) -> u32 { panic!("cannot convert from JsValue outside of the Wasm target") } @@ -501,7 +501,7 @@ impl ToTokens for ast::StructField { #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { - #[cfg_attr(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), no_mangle)] + #[cfg_attr(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")), no_mangle)] #[doc(hidden)] pub unsafe extern "C" fn #getter(js: u32) -> #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi> @@ -540,7 +540,7 @@ impl ToTokens for ast::StructField { let (args, names) = splat(wasm_bindgen, &Ident::new("val", rust_name.span()), &abi); (quote! { - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { @@ -847,7 +847,7 @@ impl TryToTokens for ast::Export { #wasm_bindgen::__wbindgen_coverage! { #(#attrs)* #[cfg_attr( - all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), + all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")), export_name = #export_name, )] pub unsafe extern "C" fn #generated_name(#(#args),*) -> #wasm_bindgen::convert::WasmRet<#projection::Abi> { @@ -1124,11 +1124,11 @@ impl ToTokens for ast::ImportType { impl JsCast for #rust_name { fn instanceof(val: &JsValue) -> bool { #[link(wasm_import_module = "__wbindgen_placeholder__")] - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] extern "C" { fn #instanceof_shim(val: u32) -> u32; } - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] + #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))))] unsafe fn #instanceof_shim(_: u32) -> u32 { panic!("cannot check instanceof on non-wasm targets"); } @@ -1838,12 +1838,12 @@ fn static_init(wasm_bindgen: &syn::Path, ty: &syn::Type, shim_name: &Ident) -> T }; quote! { #[link(wasm_import_module = "__wbindgen_placeholder__")] - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] extern "C" { fn #shim_name() -> #abi_ret; } - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] + #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))))] unsafe fn #shim_name() -> #abi_ret { panic!("cannot access imported statics on non-wasm targets") } @@ -1888,7 +1888,7 @@ impl ToTokens for Descriptor<'_, T> { let attrs = &self.attrs; let wasm_bindgen = &self.wasm_bindgen; (quote! { - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] #[automatically_derived] const _: () = { #wasm_bindgen::__wbindgen_coverage! { @@ -1916,14 +1916,14 @@ fn extern_fn( abi_ret: TokenStream, ) -> TokenStream { quote! { - #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] + #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))] #(#attrs)* #[link(wasm_import_module = "__wbindgen_placeholder__")] extern "C" { fn #import_name(#(#abi_arguments),*) -> #abi_ret; } - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] + #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))))] unsafe fn #import_name(#(#abi_arguments),*) -> #abi_ret { #( drop(#abi_argument_names); diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index bb4ba82842e..f0257ae60f7 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -17,6 +17,7 @@ version = "0.2.100" anyhow = "1.0" base64 = "0.22" log = "0.4" +regex = "1" rustc-demangle = "0.1.13" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 09501a67b40..c04b0f5a83d 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -9,6 +9,7 @@ use crate::wit::InstructionData; use crate::wit::{ Adapter, AdapterId, AdapterKind, AdapterType, AuxFunctionArgumentData, Instruction, }; +use crate::OutputMode; use anyhow::{anyhow, bail, Error}; use std::collections::HashSet; use std::fmt::Write; @@ -130,6 +131,7 @@ impl<'a, 'b> Builder<'a, 'b> { debug_name: &str, ret_ty_override: &Option, ret_desc: &Option, + import_deps: &mut HashSet, ) -> Result { if self .cx @@ -194,6 +196,7 @@ impl<'a, 'b> Builder<'a, 'b> { &instr.instr, &mut self.log_error, &self.constructor, + import_deps, )?; } @@ -255,7 +258,7 @@ impl<'a, 'b> Builder<'a, 'b> { }; if self.catch { - js.cx.expose_handle_error()?; + js.cx.expose_handle_error(import_deps)?; } // Generate a try/catch block in debug mode which handles unexpected and @@ -263,7 +266,7 @@ impl<'a, 'b> Builder<'a, 'b> { // logs what happened, but keeps the exception being thrown to propagate // elsewhere. if self.log_error { - js.cx.expose_log_error(); + js.cx.expose_log_error(import_deps); } code.push_str(&call); @@ -610,42 +613,42 @@ impl<'a, 'b> JsBuilder<'a, 'b> { self.stack.push(arg); } - fn assert_class(&mut self, arg: &str, class: &str) { - self.cx.expose_assert_class(); + fn assert_class(&mut self, arg: &str, class: &str, import_deps: &mut HashSet) { + self.cx.expose_assert_class(import_deps); self.prelude(&format!("_assertClass({}, {});", arg, class)); } - fn assert_number(&mut self, arg: &str) { + fn assert_number(&mut self, arg: &str, import_deps: &mut HashSet) { if !self.cx.config.debug { return; } - self.cx.expose_assert_num(); + self.cx.expose_assert_num(import_deps); self.prelude(&format!("_assertNum({});", arg)); } - fn assert_bigint(&mut self, arg: &str) { + fn assert_bigint(&mut self, arg: &str, import_deps: &mut HashSet) { if !self.cx.config.debug { return; } - self.cx.expose_assert_bigint(); + self.cx.expose_assert_bigint(import_deps); self.prelude(&format!("_assertBigInt({});", arg)); } - fn assert_bool(&mut self, arg: &str) { + fn assert_bool(&mut self, arg: &str, import_deps: &mut HashSet) { if !self.cx.config.debug { return; } - self.cx.expose_assert_bool(); + self.cx.expose_assert_bool(import_deps); self.prelude(&format!("_assertBoolean({});", arg)); } - fn assert_optional_number(&mut self, arg: &str) { + fn assert_optional_number(&mut self, arg: &str, import_deps: &mut HashSet) { if !self.cx.config.debug { return; } - self.cx.expose_is_like_none(); + self.cx.expose_is_like_none(import_deps); self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_number(arg); + self.assert_number(arg, import_deps); self.prelude("}"); } @@ -659,23 +662,23 @@ impl<'a, 'b> JsBuilder<'a, 'b> { self.prelude(&format!("_assertChar({});", arg)); } - fn assert_optional_bigint(&mut self, arg: &str) { + fn assert_optional_bigint(&mut self, arg: &str, import_deps: &mut HashSet) { if !self.cx.config.debug { return; } - self.cx.expose_is_like_none(); + self.cx.expose_is_like_none(import_deps); self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_bigint(arg); + self.assert_bigint(arg, import_deps); self.prelude("}"); } - fn assert_optional_bool(&mut self, arg: &str) { + fn assert_optional_bool(&mut self, arg: &str, import_deps: &mut HashSet) { if !self.cx.config.debug { return; } - self.cx.expose_is_like_none(); + self.cx.expose_is_like_none(import_deps); self.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_bool(arg); + self.assert_bool(arg, import_deps); self.prelude("}"); } @@ -698,8 +701,9 @@ impl<'a, 'b> JsBuilder<'a, 'b> { mem: walrus::MemoryId, malloc: walrus::FunctionId, realloc: Option, + import_deps: &mut HashSet, ) -> Result<(), Error> { - let pass = self.cx.expose_pass_string_to_wasm(mem)?; + let pass = self.cx.expose_pass_string_to_wasm(mem, import_deps)?; let val = self.pop(); let malloc = self.cx.export_name_of(malloc); let i = self.tmp(); @@ -718,6 +722,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> { self.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); self.push(format!("ptr{}", i)); self.push(format!("len{}", i)); + import_deps.insert(format!("'${}'", pass)); Ok(()) } } @@ -727,6 +732,7 @@ fn instruction( instr: &Instruction, log_error: &mut bool, constructor: &Option, + import_deps: &mut HashSet, ) -> Result<(), Error> { fn wasm_to_string_enum(name: &str, index: &str) -> String { // e.g. ["a","b","c"][someIndex] @@ -807,7 +813,7 @@ fn instruction( } // Call the function through an export of the underlying module. - let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; + let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error, import_deps)?; // And then figure out how to actually handle where the call // happens. This is pretty conditional depending on the number of @@ -833,7 +839,7 @@ fn instruction( Instruction::Int32ToWasm => { let val = js.pop(); - js.assert_number(&val); + js.assert_number(&val, import_deps); js.push(val); } Instruction::WasmToInt32 { unsigned_32 } => { @@ -850,7 +856,7 @@ fn instruction( Instruction::Int64ToWasm => { let val = js.pop(); - js.assert_bigint(&val); + js.assert_bigint(&val, import_deps); js.push(val); } Instruction::WasmToInt64 { unsigned } => { @@ -864,7 +870,7 @@ fn instruction( Instruction::Int128ToWasm => { let val = js.pop(); - js.assert_bigint(&val); + js.assert_bigint(&val, import_deps); let (low, high) = int128_to_int64x2(&val); js.push(low); js.push(high); @@ -877,8 +883,8 @@ fn instruction( Instruction::OptionInt128ToWasm => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_bigint(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_bigint(&val, import_deps); let (low, high) = int128_to_int64x2(&val); js.push(format!("!isLikeNone({val})")); js.push(format!("isLikeNone({val}) ? BigInt(0) : {low}")); @@ -921,7 +927,7 @@ fn instruction( let enum_val = js.pop(); js.cx.expose_string_enum(name); let enum_val_expr = string_enum_to_wasm(name, *invalid, &enum_val); - js.cx.expose_is_like_none(); + js.cx.expose_is_like_none(import_deps); // e.g. isLikeNone(someEnumVal) ? 4 : (string_enum_to_wasm(someEnumVal)) js.push(format!( @@ -941,7 +947,7 @@ fn instruction( malloc, realloc, } => { - js.string_to_memory(*mem, *malloc, *realloc)?; + js.string_to_memory(*mem, *malloc, *realloc, import_deps)?; } Instruction::Retptr { size } => { @@ -966,7 +972,7 @@ fn instruction( // Note that we always assume the return pointer is argument 0, // which is currently the case for LLVM. let val = js.pop(); - let expr = format!( + let mut expr = format!( "{}().{}({} + {} * {}, {}, true);", mem, method, @@ -975,6 +981,16 @@ fn instruction( offset, val, ); + if matches!(js.cx.config.mode, OutputMode::Emscripten) { + expr = format!( + "HEAP_DATA_VIEW.{}({} + {} * {}, {}, true);", + method, + js.arg(0), + size, + offset, + val, + ); + } js.prelude(&expr); } @@ -994,17 +1010,25 @@ fn instruction( // If we're loading from the return pointer then we must have pushed // it earlier, and we always push the same value, so load that value // here - let expr = format!( + let mut expr = format!( "{}().{}(retptr + {} * {}, true)", mem, method, size, scaled_offset ); + + if matches!(js.cx.config.mode, OutputMode::Emscripten) { + expr = format!( + "HEAP_DATA_VIEW.{}(retptr + {} * {}, true)", + method, size, scaled_offset + ); + } + js.prelude(&format!("var r{} = {};", offset, expr)); js.push(format!("r{}", offset)); } Instruction::I32FromBool => { let val = js.pop(); - js.assert_bool(&val); + js.assert_bool(&val, import_deps); // JS will already coerce booleans into numbers for us js.push(val); } @@ -1025,7 +1049,7 @@ fn instruction( } Instruction::I32FromExternrefBorrow => { - js.cx.expose_borrowed_objects(); + js.cx.expose_borrowed_objects(import_deps); js.cx.expose_global_stack_pointer(); let val = js.pop(); js.push(format!("addBorrowedObject({})", val)); @@ -1034,7 +1058,7 @@ fn instruction( Instruction::I32FromExternrefRustOwned { class } => { let val = js.pop(); - js.assert_class(&val, class); + js.assert_class(&val, class, import_deps); js.assert_not_moved(&val); let i = js.tmp(); js.prelude(&format!("var ptr{} = {}.__destroy_into_raw();", i, val)); @@ -1043,18 +1067,18 @@ fn instruction( Instruction::I32FromExternrefRustBorrow { class } => { let val = js.pop(); - js.assert_class(&val, class); + js.assert_class(&val, class, import_deps); js.assert_not_moved(&val); js.push(format!("{}.__wbg_ptr", val)); } Instruction::I32FromOptionRust { class } => { let val = js.pop(); - js.cx.expose_is_like_none(); + js.cx.expose_is_like_none(import_deps); let i = js.tmp(); js.prelude(&format!("let ptr{} = 0;", i)); js.prelude(&format!("if (!isLikeNone({0})) {{", val)); - js.assert_class(&val, class); + js.assert_class(&val, class, import_deps); js.assert_not_moved(&val); js.prelude(&format!("ptr{} = {}.__destroy_into_raw();", i, val)); js.prelude("}"); @@ -1063,10 +1087,12 @@ fn instruction( Instruction::I32FromOptionExternref { table_and_alloc } => { let val = js.pop(); - js.cx.expose_is_like_none(); + js.cx.expose_is_like_none(import_deps); match table_and_alloc { Some((table, alloc)) => { - let alloc = js.cx.expose_add_to_externref_table(*table, *alloc)?; + let alloc = js + .cx + .expose_add_to_externref_table(*table, *alloc, import_deps)?; js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); } None => { @@ -1078,22 +1104,22 @@ fn instruction( Instruction::I32FromOptionU32Sentinel => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_number(&val, import_deps); js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); } Instruction::I32FromOptionBool => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_bool(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_bool(&val, import_deps); js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); } Instruction::I32FromOptionChar => { let val = js.pop(); let i = js.tmp(); - js.cx.expose_is_like_none(); + js.cx.expose_is_like_none(import_deps); js.prelude(&format!( "const char{i} = isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0);", val @@ -1108,15 +1134,15 @@ fn instruction( Instruction::I32FromOptionEnum { hole } => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_number(&val, import_deps); js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); } Instruction::F64FromOptionSentinelInt { signed } => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_number(&val, import_deps); // We need to convert the given number to a 32-bit integer before // passing it to the ABI for 2 reasons: @@ -1138,8 +1164,8 @@ fn instruction( } Instruction::F64FromOptionSentinelF32 => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_number(&val, import_deps); // Similar to the above 32-bit integer variant, we convert the // number to a 32-bit *float* before passing it to the ABI. This @@ -1153,11 +1179,11 @@ fn instruction( Instruction::FromOptionNative { ty } => { let val = js.pop(); - js.cx.expose_is_like_none(); + js.cx.expose_is_like_none(import_deps); if *ty == ValType::I64 { - js.assert_optional_bigint(&val); + js.assert_optional_bigint(&val, import_deps); } else { - js.assert_optional_number(&val); + js.assert_optional_number(&val, import_deps); } js.push(format!("!isLikeNone({0})", val)); js.push(format!( @@ -1174,7 +1200,9 @@ fn instruction( Instruction::VectorToMemory { kind, malloc, mem } => { let val = js.pop(); - let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?; + let func = js + .cx + .pass_to_wasm_function(kind.clone(), *mem, import_deps)?; let malloc = js.cx.export_name_of(*malloc); let i = js.tmp(); js.prelude(&format!( @@ -1187,6 +1215,7 @@ fn instruction( js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); js.push(format!("ptr{}", i)); js.push(format!("len{}", i)); + import_deps.insert(format!("'${}'", func)); } Instruction::UnwrapResult { table_and_drop } => { @@ -1195,7 +1224,7 @@ fn instruction( .expose_take_from_externref_table(table, drop)? .to_string() } else { - js.cx.expose_take_object(); + js.cx.expose_take_object(import_deps); "takeObject".to_string() }; // is_err is popped first. The original layout was: ResultAbi { @@ -1224,7 +1253,7 @@ fn instruction( .expose_take_from_externref_table(table, drop)? .to_string() } else { - js.cx.expose_take_object(); + js.cx.expose_take_object(import_deps); "takeObject".to_string() }; let is_err = js.pop(); @@ -1257,8 +1286,8 @@ fn instruction( malloc, realloc, } => { - let func = js.cx.expose_pass_string_to_wasm(*mem)?; - js.cx.expose_is_like_none(); + let func = js.cx.expose_pass_string_to_wasm(*mem, import_deps)?; + js.cx.expose_is_like_none(import_deps); let i = js.tmp(); let malloc = js.cx.export_name_of(*malloc); let val = js.pop(); @@ -1277,11 +1306,14 @@ fn instruction( js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); js.push(format!("ptr{}", i)); js.push(format!("len{}", i)); + import_deps.insert(format!("'${}'", func)); } Instruction::OptionVector { kind, mem, malloc } => { - let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?; - js.cx.expose_is_like_none(); + let func = js + .cx + .pass_to_wasm_function(kind.clone(), *mem, import_deps)?; + js.cx.expose_is_like_none(import_deps); let i = js.tmp(); let malloc = js.cx.export_name_of(*malloc); let val = js.pop(); @@ -1295,12 +1327,15 @@ fn instruction( js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); js.push(format!("ptr{}", i)); js.push(format!("len{}", i)); + import_deps.insert(format!("'${}'", func)); } Instruction::MutableSliceToMemory { kind, malloc, mem } => { // Copy the contents of the typed array into wasm. let val = js.pop(); - let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?; + let func = js + .cx + .pass_to_wasm_function(kind.clone(), *mem, import_deps)?; let malloc = js.cx.export_name_of(*malloc); let i = js.tmp(); js.prelude(&format!( @@ -1317,6 +1352,7 @@ fn instruction( // Then we give Wasm a reference to the original typed array, so that it can // update it with modifications made on the Wasm side before returning. js.push(val); + import_deps.insert(format!("'${}'", func)); } Instruction::BoolFromI32 => { @@ -1330,7 +1366,7 @@ fn instruction( .expose_take_from_externref_table(table, drop)? .to_string() } else { - js.cx.expose_take_object(); + js.cx.expose_take_object(import_deps); "takeObject".to_string() }; let val = js.pop(); @@ -1381,7 +1417,9 @@ fn instruction( let ptr = js.pop(); let tmp = js.tmp(); - let get = js.cx.expose_get_cached_string_from_wasm(*mem, *table)?; + let get = js + .cx + .expose_get_cached_string_from_wasm(*mem, *table, import_deps)?; js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); @@ -1400,7 +1438,7 @@ fn instruction( Instruction::TableGet => { let val = js.pop(); - js.cx.expose_get_object(); + js.cx.expose_get_object(import_deps); js.push(format!("getObject({})", val)); } @@ -1418,6 +1456,7 @@ fn instruction( .collect::>() .join(", "); let wrapper = js.cx.adapter_name(*adapter); + import_deps.insert(format!("'${}'", wrapper)); if *mutable { // Mutable closures need protection against being called // recursively, so ensure that we clear out one of the @@ -1456,7 +1495,9 @@ fn instruction( Instruction::VectorLoad { kind, mem, free } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; + let f = js + .cx + .expose_get_vector_from_wasm(kind.clone(), *mem, import_deps)?; let i = js.tmp(); let free = js.cx.export_name_of(*free); js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); @@ -1473,7 +1514,9 @@ fn instruction( Instruction::OptionVectorLoad { kind, mem, free } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; + let f = js + .cx + .expose_get_vector_from_wasm(kind.clone(), *mem, import_deps)?; let i = js.tmp(); let free = js.cx.export_name_of(*free); js.prelude(&format!("let v{};", i)); @@ -1493,14 +1536,18 @@ fn instruction( Instruction::View { kind, mem } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; + let f = js + .cx + .expose_get_vector_from_wasm(kind.clone(), *mem, import_deps)?; js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); } Instruction::OptionView { kind, mem } => { let len = js.pop(); let ptr = js.pop(); - let f = js.cx.expose_get_vector_from_wasm(kind.clone(), *mem)?; + let f = js + .cx + .expose_get_vector_from_wasm(kind.clone(), *mem, import_deps)?; js.push(format!( "{ptr} === 0 ? undefined : {f}({ptr}, {len})", ptr = ptr, @@ -1563,8 +1610,8 @@ fn instruction( Instruction::I32FromOptionNonNull => { let val = js.pop(); - js.cx.expose_is_like_none(); - js.assert_optional_number(&val); + js.cx.expose_is_like_none(import_deps); + js.assert_optional_number(&val, import_deps); js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); } @@ -1637,6 +1684,7 @@ impl Invocation { args: &[String], prelude: &mut String, log_error: &mut bool, + import_deps: &mut HashSet, ) -> Result { match self { Invocation::Core { id, .. } => { @@ -1656,7 +1704,7 @@ impl Invocation { if cx.import_never_log_error(import) { *log_error = false; } - cx.invoke_import(import, kind, args, variadic, prelude) + cx.invoke_import(import, kind, args, variadic, prelude, import_deps) } } } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 3ba5be3a719..1d682b865fa 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -10,6 +10,7 @@ use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{reset_indentation, Bindgen, EncodeInto, OutputMode, PLACEHOLDER_MODULE}; use anyhow::{anyhow, bail, Context as _, Error}; use binding::TsReference; +use regex::Regex; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt; @@ -23,6 +24,8 @@ mod binding; pub struct Context<'a> { globals: String, + emscripten_library: String, + emscripten_deps: HashSet, imports_post: String, typescript: String, exposed_globals: Option>>, @@ -146,6 +149,8 @@ impl<'a> Context<'a> { ) -> Result, Error> { Ok(Context { globals: String::new(), + emscripten_library: String::new(), + emscripten_deps: HashSet::new(), imports_post: String::new(), typescript: "/* tslint:disable */\n/* eslint-disable */\n".to_string(), exposed_globals: Some(Default::default()), @@ -230,6 +235,27 @@ impl<'a> Context<'a> { format!("export const {} = {};\n", export_name, expr) } }, + OutputMode::Emscripten => match export { + ExportJs::Class(class) => { + assert_eq!(export_name, definition_name); + format!("{}\nModule.{} = {};\n", class, export_name, export_name) + } + ExportJs::Function(function) => { + let body = function.strip_prefix("function").unwrap(); + if export_name == definition_name { + format!("Module.{} = function{};\n", export_name, body) + } else { + format!( + "function {}{}\nexport {{ {} as {} }};\n", + definition_name, body, definition_name, export_name, + ) + } + } + ExportJs::Expression(expr) => { + assert_eq!(export_name, definition_name); + format!("export const {} = {};\n", export_name, expr) + } + }, }; self.global(&global); Ok(()) @@ -598,7 +624,7 @@ __wbg_set_wasm(wasm);" // browsers don't support natively importing Wasm right now so we // expose the same initialization function as `--target no-modules` // as the default export of the module. - OutputMode::Web => { + OutputMode::Web | OutputMode::Emscripten => { self.imports_post.push_str("let wasm;\n"); init = self.gen_init(needs_manual_start, Some(&mut imports))?; footer.push_str("export { initSync };\n"); @@ -635,15 +661,36 @@ __wbg_set_wasm(wasm);" } }; - push_with_newline(&imports); - push_with_newline(&self.imports_post); + let set_to_list = |set: &HashSet| -> Vec { set.iter().cloned().collect() }; - // Emit all our exports from this module - push_with_newline(&self.globals); + if matches!(self.config.mode, OutputMode::Emscripten) { + push_with_newline("var LibraryWbg = {\n"); + push_with_newline(&self.emscripten_library); + push_with_newline(&init_js); + push_with_newline( + "$initBindgen: () => {\n + wasmExports['__wbindgen_start']();", + ); + self.globals = self.globals.replace("wasm.", "_"); + push_with_newline(&self.globals); + push_with_newline("},"); + let deps: Vec = set_to_list(&self.emscripten_deps); + push_with_newline(&format!( + "}};\n + extraLibraryFuncs.push('$initBindgen','$addOnInit',{}); + addToLibrary(LibraryWbg);", + deps.join(",") + )); + } else { + push_with_newline(&imports); + push_with_newline(&self.imports_post); + // Emit all our exports from this module + push_with_newline(&self.globals); + // Generate the initialization glue, if there was any + push_with_newline(&init_js); + push_with_newline(&footer); + } - // Generate the initialization glue, if there was any - push_with_newline(&init_js); - push_with_newline(&footer); if self.config.mode.no_modules() { js.push_str("})();\n"); } @@ -698,7 +745,8 @@ __wbg_set_wasm(wasm);" OutputMode::Bundler { .. } | OutputMode::Node { module: true } | OutputMode::Web - | OutputMode::Deno => { + | OutputMode::Deno + | OutputMode::Emscripten => { for (module, items) in crate::sorted_iter(&self.js_imports) { imports.push_str("import { "); for (i, (item, rename)) in items.iter().enumerate() { @@ -805,7 +853,11 @@ __wbg_set_wasm(wasm);" needs_manual_start: bool, mut imports: Option<&mut String>, ) -> Result<(String, String), Error> { - let module_name = "wbg"; + let module_name = if matches!(self.config.mode, OutputMode::Emscripten) { + "env" + } else { + "wbg" + }; let mut init_memory_arg = ""; let mut init_memory = String::new(); let mut has_memory = false; @@ -858,20 +910,35 @@ __wbg_set_wasm(wasm);" // directed to wire up. let mut imports_init = String::new(); - imports_init.push_str("imports."); - imports_init.push_str(module_name); - imports_init.push_str(" = {};\n"); + if !matches!(self.config.mode, OutputMode::Emscripten) { + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push_str(" = {};\n"); + } for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) { let import = self.module.imports.get_mut(*id); import.module = module_name.to_string(); - imports_init.push_str("imports."); - imports_init.push_str(module_name); - imports_init.push('.'); - imports_init.push_str(&import.name); - imports_init.push_str(" = "); - imports_init.push_str(js.trim()); - imports_init.push_str(";\n"); + if !matches!(self.config.mode, OutputMode::Emscripten) { + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push('.'); + imports_init.push_str(&import.name); + imports_init.push_str(" = "); + imports_init.push_str(js.trim()); + imports_init.push_str(";\n"); + } else { + imports_init.push_str(&import.name); + if import.name == "__wbindgen_init_externref_table" { + imports_init.push_str(": () =>"); + imports_init.push_str(js.trim().strip_prefix("function()").unwrap()); + imports_init.push_str(",\n"); + } else { + imports_init.push_str(": "); + imports_init.push_str(&js.trim().replace("wasm.", "_")); + imports_init.push_str(",\n"); + } + } } let extra_modules = self @@ -895,7 +962,9 @@ __wbg_set_wasm(wasm);" ), }; imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra)); - imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i)); + if !matches!(self.config.mode, OutputMode::Emscripten) { + imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i)); + } } let mut init_memviews = String::new(); @@ -914,7 +983,39 @@ __wbg_set_wasm(wasm);" } } - let js = format!( + let js = match &self.config.mode { + OutputMode::Emscripten => { + let mut global_emscripten_initializer: String = Default::default(); + for global_dep in self.emscripten_deps.iter() { + let mut global: String = "".to_string(); + if global_dep == "'$WASM_VECTOR_LEN'" { + global = "$WASM_VECTOR_LEN: '0',".to_string(); + } else if global_dep == "'$TextEncoder'" { + global = "$textEncoder: \"new TextEncoder()\",".to_string(); + } else if global_dep == "'$heap'" { + global = format!(" + $heap: \"new Array({}).fill(undefined)\", + \"heap.push({})\",", INITIAL_HEAP_OFFSET, INITIAL_HEAP_VALUES.join(", ")); + } else if global_dep == "'$stack_pointer'" { + global = format!("$stack_pointer : \"{}\",", INITIAL_HEAP_OFFSET) + } + + if !global.is_empty() { + global_emscripten_initializer = format!( + "{}{}\n", + global_emscripten_initializer, global + ); + } + } + format!( + "\ + {} + {} + $initBindgen__deps: ['$addOnInit'], + $initBindgen__postset: 'addOnInit(initBindgen);', + ", imports_init, global_emscripten_initializer + )} + _ => format!( "\ async function __wbg_load(module, imports) {{ if (typeof Response === 'function' && module instanceof Response) {{ @@ -1050,8 +1151,9 @@ __wbg_set_wasm(wasm);" ) } else { String::new() - }, - ); + } + ) + }; Ok((js, ts)) } @@ -1066,7 +1168,9 @@ __wbg_set_wasm(wasm);" fn write_class(&mut self, name: &str, class: &ExportedClass) -> Result<(), Error> { let mut dst = format!("class {} {{\n", name); let mut ts_dst = format!("export {}", dst); - + if matches!(self.config.mode, OutputMode::Emscripten) { + ts_dst = format!("{}: {{\n", name); + } if !class.has_constructor { // declare the constructor as private to prevent direct instantiation ts_dst.push_str(" private constructor();\n"); @@ -1339,7 +1443,9 @@ __wbg_set_wasm(wasm);" } } - fn expose_drop_ref(&mut self) { + fn expose_drop_ref(&mut self, import_deps: &mut HashSet) { + let func_name = "dropObject"; + import_deps.insert(format!("'${func_name}'")); if !self.should_write_global("drop_ref") { return; } @@ -1354,16 +1460,19 @@ __wbg_set_wasm(wasm);" // // Otherwise the free operation here is pretty simple, just appending to // the linked list of heap slots that are free. - self.global(&format!( - " - function dropObject(idx) {{ + self.write_js_function( + &format!( + " if (idx < {}) return; heap[idx] = heap_next; heap_next = idx; - }} ", - INITIAL_HEAP_OFFSET + INITIAL_HEAP_VALUES.len(), - )); + INITIAL_HEAP_OFFSET + INITIAL_HEAP_VALUES.len(), + ), + "dropObject", + "(idx)", + &[], + ); } fn expose_global_heap(&mut self) { @@ -1371,6 +1480,10 @@ __wbg_set_wasm(wasm);" return; } assert!(!self.config.externref); + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_deps.insert("'$heap'".to_string()); + return; + } self.global(&format!( "const heap = new Array({}).fill(undefined);", INITIAL_HEAP_OFFSET @@ -1386,7 +1499,9 @@ __wbg_set_wasm(wasm);" self.global("let heap_next = heap.length;"); } - fn expose_get_object(&mut self) { + fn expose_get_object(&mut self, import_deps: &mut HashSet) { + let func_name = "getObject"; + import_deps.insert(format!("'$_{func_name}'")); if !self.should_write_global("get_object") { return; } @@ -1404,37 +1519,41 @@ __wbg_set_wasm(wasm);" self.global("function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"); } - fn expose_assert_num(&mut self) { + fn expose_assert_num(&mut self, import_deps: &mut HashSet) { + let func_name = "_assertNum"; + import_deps.insert(format!("'$_{func_name}'")); if !self.should_write_global("assert_num") { return; } - self.global( + self.write_js_function( " - function _assertNum(n) { if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); - } - ", + ", func_name, "(n)", &[] ); } - fn expose_assert_bigint(&mut self) { + fn expose_assert_bigint(&mut self, import_deps: &mut HashSet) { + let func_name = "_assertBigInt"; + import_deps.insert(format!("'$_{func_name}'")); if !self.should_write_global("assert_bigint") { return; } - self.global( + self.write_js_function( " function _assertBigInt(n) { if (typeof(n) !== 'bigint') throw new Error(`expected a bigint argument, found ${typeof(n)}`); } - ", + ", func_name, "(n)", &[] ); } - fn expose_assert_bool(&mut self) { + fn expose_assert_bool(&mut self, import_deps: &mut HashSet) { + let func_name = "_assertBoolean"; + import_deps.insert(format!("'$_{func_name}'")); if !self.should_write_global("assert_bool") { return; } - self.global( + self.write_js_function( " function _assertBoolean(n) { if (typeof(n) !== 'boolean') { @@ -1442,18 +1561,31 @@ __wbg_set_wasm(wasm);" } } ", + func_name, + "(n)", + &[], ); } - fn expose_wasm_vector_len(&mut self) { + fn expose_wasm_vector_len(&mut self, import_deps: &mut HashSet) { + import_deps.insert("'$WASM_VECTOR_LEN'".to_string()); if !self.should_write_global("wasm_vector_len") { return; } - self.global("let WASM_VECTOR_LEN = 0;"); + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_deps + .insert("'$WASM_VECTOR_LEN'".to_string()); + } else { + self.global("let WASM_VECTOR_LEN = 0;"); + } } - fn expose_pass_string_to_wasm(&mut self, memory: MemoryId) -> Result { - self.expose_wasm_vector_len(); + fn expose_pass_string_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { + self.expose_wasm_vector_len(import_deps); let debug = if self.config.debug { " @@ -1473,55 +1605,101 @@ __wbg_set_wasm(wasm);" } self.expose_text_encoder()?; + let mut text_encoder = "cachedTextEncoder"; + let mut mem_formatted = format!("{mem}()"); + if matches!(self.config.mode, OutputMode::Emscripten) { + text_encoder = "textEncoder"; + mem_formatted = format!("{}", mem.name); + }; // The first implementation we have for this is to use // `TextEncoder#encode` which has been around for quite some time. - let encode = "function (arg, view) { - const buf = cachedTextEncoder.encode(arg); + let encode = format!( + "function (arg, view) {{ + const buf = {text_encoder}.encode(arg); view.set(buf); - return { + return {{ read: arg.length, written: buf.length - }; - }"; + }}; + }}" + ); // Another possibility is to use `TextEncoder#encodeInto` which is much // newer and isn't implemented everywhere yet. It's more efficient, // however, because it allows us to elide an intermediate allocation. - let encode_into = "function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); - }"; + let encode_into = format!( + "function (arg, view) {{ + return {text_encoder}.encodeInto(arg, view); + }}" + ); // Looks like `encodeInto` doesn't currently work when the memory passed // in is backed by a `SharedArrayBuffer`, so force usage of `encode` if // a `SharedArrayBuffer` is in use. let shared = self.module.memories.get(memory).shared; - match self.config.encode_into { - EncodeInto::Always if !shared => { - self.global(&format!( - " - const encodeString = {}; - ", - encode_into - )); - } - EncodeInto::Test if !shared => { - self.global(&format!( - " - const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' - ? {} - : {}); - ", - encode_into, encode - )); + if matches!(self.config.mode, OutputMode::Emscripten) { + match self.config.encode_into { + EncodeInto::Always if !shared => { + self.emscripten_library.push_str(&format!( + " + $encodeString : {}, + ", + encode_into + )); + } + EncodeInto::Test if !shared => { + self.emscripten_library.push_str(&format!( + " + $encodeString : function (arg, view) {{ + if (typeof TextEncoder.encodeInto === 'function') {{ + return {} + }} + return {} + }}, + ", + encode_into, encode + )); + } + _ => { + self.emscripten_library.push_str(&format!( + " + $encodeString : {}, + ", + encode + )); + } } - _ => { - self.global(&format!( - " - const encodeString = {}; - ", - encode - )); + self.emscripten_library + .push_str("$encodeString__deps: ['$textEncoder'],\n"); + } else { + match self.config.encode_into { + EncodeInto::Always if !shared => { + self.global(&format!( + " + const encodeString = {}; + ", + encode_into + )); + } + EncodeInto::Test if !shared => { + self.global(&format!( + " + const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? {} + : {}); + ", + encode_into, encode + )); + } + _ => { + self.global(&format!( + " + const encodeString = {}; + ", + encode + )); + } } } @@ -1537,9 +1715,9 @@ __wbg_set_wasm(wasm);" let encode_as_ascii = format!( "\ if (realloc === undefined) {{ - const buf = cachedTextEncoder.encode(arg); + const buf = {text_encoder}.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; - {mem}().subarray(ptr, ptr + buf.length).set(buf); + {mem_formatted}.subarray(ptr, ptr + buf.length).set(buf); WASM_VECTOR_LEN = buf.length; return ptr; }} @@ -1547,7 +1725,7 @@ __wbg_set_wasm(wasm);" let len = arg.length; let ptr = malloc(len, 1) >>> 0; - const mem = {mem}(); + const mem = {mem_formatted}; let offset = 0; @@ -1556,12 +1734,11 @@ __wbg_set_wasm(wasm);" if (code > 0x7F) break; mem[ptr + offset] = code; }} - ", - mem = mem, + " ); - - self.global(&format!( - "function {name}(arg, malloc, realloc) {{ + self.write_js_function( + &format!( + " {debug} {ascii} if (offset !== len) {{ @@ -1569,7 +1746,7 @@ __wbg_set_wasm(wasm);" arg = arg.slice(offset); }} ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; - const view = {mem}().subarray(ptr + offset, ptr + len); + const view = {mem_formatted}.subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); {debug_end} offset += ret.written; @@ -1577,53 +1754,87 @@ __wbg_set_wasm(wasm);" }} WASM_VECTOR_LEN = offset; - return ptr; - }}", - name = ret, - debug = debug, - ascii = encode_as_ascii, - mem = mem, - debug_end = if self.config.debug { - "if (ret.read !== arg.length) throw new Error('failed to pass whole string');" - } else { - "" - }, - )); + return ptr;", + debug = debug, + ascii = encode_as_ascii, + mem_formatted = mem_formatted, + debug_end = if self.config.debug { + "if (ret.read !== arg.length) throw new Error('failed to pass whole string');" + } else { + "" + }, + ), + &format!("{ret}"), + "(arg, malloc, realloc)", + &[ + "'$encodeString'".to_string(), + format!("'${text_encoder}'"), + "'$WASM_VECTOR_LEN'".to_string(), + ], + ); Ok(ret) } - fn expose_pass_array8_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array8_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let view = self.expose_uint8_memory(memory); - self.pass_array_to_wasm("passArray8ToWasm", view, 1) + self.pass_array_to_wasm("passArray8ToWasm", view, 1, import_deps) } - fn expose_pass_array16_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array16_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let view = self.expose_uint16_memory(memory); - self.pass_array_to_wasm("passArray16ToWasm", view, 2) + self.pass_array_to_wasm("passArray16ToWasm", view, 2, import_deps) } - fn expose_pass_array32_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array32_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let view = self.expose_uint32_memory(memory); - self.pass_array_to_wasm("passArray32ToWasm", view, 4) + self.pass_array_to_wasm("passArray32ToWasm", view, 4, import_deps) } - fn expose_pass_array64_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array64_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let view = self.expose_uint64_memory(memory); - self.pass_array_to_wasm("passArray64ToWasm", view, 8) + self.pass_array_to_wasm("passArray64ToWasm", view, 8, import_deps) } - fn expose_pass_array_f32_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array_f32_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let view = self.expose_f32_memory(memory); - self.pass_array_to_wasm("passArrayF32ToWasm", view, 4) + self.pass_array_to_wasm("passArrayF32ToWasm", view, 4, import_deps) } - fn expose_pass_array_f64_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array_f64_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let view = self.expose_f64_memory(memory); - self.pass_array_to_wasm("passArrayF64ToWasm", view, 8) + self.pass_array_to_wasm("passArrayF64ToWasm", view, 8, import_deps) } - fn expose_pass_array_jsvalue_to_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_pass_array_jsvalue_to_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let mem = self.expose_dataview_memory(memory); let ret = MemView { name: "passArrayJsValueToWasm".into(), @@ -1632,15 +1843,15 @@ __wbg_set_wasm(wasm);" if !self.should_write_global(ret.to_string()) { return Ok(ret); } - self.expose_wasm_vector_len(); + self.expose_wasm_vector_len(import_deps); match (self.aux.externref_table, self.aux.externref_alloc) { (Some(table), Some(alloc)) => { // TODO: using `addToExternrefTable` goes back and forth between wasm // and JS a lot, we should have a bulk operation for this. - let add = self.expose_add_to_externref_table(table, alloc)?; - self.global(&format!( - " - function {ret}(array, malloc) {{ + let add = self.expose_add_to_externref_table(table, alloc, import_deps)?; + self.write_js_function( + &format!( + " const ptr = malloc(array.length * 4, 4) >>> 0; for (let i = 0; i < array.length; i++) {{ const add = {add}(array[i]); @@ -1648,14 +1859,18 @@ __wbg_set_wasm(wasm);" }} WASM_VECTOR_LEN = array.length; return ptr; - }} ", - )); + ), + &format!("{ret}"), + "(array, malloc)", + &[format!("'${add}'"), "'$WASM_VECTOR_LEN'".to_string()], + ); } _ => { self.expose_add_heap_object(); - self.global(&format!( - " + self.write_js_function( + &format!( + " function {ret}(array, malloc) {{ const ptr = malloc(array.length * 4, 4) >>> 0; const mem = {mem}(); @@ -1666,7 +1881,14 @@ __wbg_set_wasm(wasm);" return ptr; }} ", - )); + ), + &format!("{ret}"), + "(array, malloc)", + &[ + "'$addHeapObject'".to_string(), + "'$WASM_VECTOR_LEN'".to_string(), + ], + ); } } Ok(ret) @@ -1677,6 +1899,7 @@ __wbg_set_wasm(wasm);" name: &'static str, view: MemView, size: usize, + import_deps: &mut HashSet, ) -> Result { let ret = MemView { name: name.into(), @@ -1685,20 +1908,27 @@ __wbg_set_wasm(wasm);" if !self.should_write_global(ret.to_string()) { return Ok(ret); } - self.expose_wasm_vector_len(); - self.global(&format!( - " - function {}(arg, malloc) {{ + self.expose_wasm_vector_len(import_deps); + let view_formatted = if matches!(self.config.mode, OutputMode::Emscripten) { + &format!("{}", view.name) + } else { + &format!("{view}()") + }; + self.write_js_function( + &format!( + " const ptr = malloc(arg.length * {size}, {size}) >>> 0; - {}().set(arg, ptr / {size}); + {}.set(arg, ptr / {size}); WASM_VECTOR_LEN = arg.length; return ptr; - }} ", - ret, - view, - size = size - )); + view_formatted, + size = size + ), + &format!("{ret}"), + "(arg, malloc)", + &["'$WASM_VECTOR_LEN'".to_string()], + ); Ok(ret) } @@ -1714,6 +1944,7 @@ __wbg_set_wasm(wasm);" if !self.should_write_global("text_encoder") { return Ok(()); } + self.emscripten_deps.insert("'$TextEncoder'".to_string()); self.expose_text_processor("TextEncoder", "encode", "('utf-8')", None) } @@ -1774,6 +2005,8 @@ __wbg_set_wasm(wasm);" | OutputMode::Bundler { browser_only: true } => { self.global(&format!("const cached{0} = (typeof {0} !== 'undefined' ? new {0}{1} : {{ {2}: () => {{ throw Error('{0} not available') }} }} );", s, args, op)) } + OutputMode::Emscripten => { + } }; if let Some(init) = init { @@ -1789,6 +2022,7 @@ __wbg_set_wasm(wasm);" "if (typeof {} !== 'undefined') {{ {} }};", s, init )), + OutputMode::Emscripten => {} } } @@ -1817,16 +2051,27 @@ __wbg_set_wasm(wasm);" // string itself. let is_shared = self.module.memories.get(memory).shared; let method = if is_shared { "slice" } else { "subarray" }; - - self.global(&format!( - " - function {}(ptr, len) {{ - ptr = ptr >>> 0; - return cachedTextDecoder.decode({}().{}(ptr, ptr + len)); - }} - ", - ret, mem, method - )); + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_library.push_str(&format!( + " + ${}(ptr, len) {{ + ptr = ptr >>> 0; + return UTF8Decoder.decode({}.{}(ptr, ptr + len)); + }},\n + ", + ret, mem.name, method + )); + } else { + self.global(&format!( + " + function {}(ptr, len) {{ + ptr = ptr >>> 0; + return cachedTextDecoder.decode({}().{}(ptr, ptr + len)); + }} + ", + ret, mem, method + )); + } Ok(ret) } @@ -1834,11 +2079,12 @@ __wbg_set_wasm(wasm);" &mut self, memory: MemoryId, table: Option, + import_deps: &mut HashSet, ) -> Result { let get_object = if let Some(table) = table { self.expose_get_from_externref_table(table)?.to_string() } else { - self.expose_get_object(); + self.expose_get_object(import_deps); "getObject".to_string() }; let get_string = self.expose_get_string_from_wasm(memory)?; @@ -1859,8 +2105,9 @@ __wbg_set_wasm(wasm);" // // If `ptr` and `len` are both `0` then that means it's `None`, in that case we rely upon // the fact that `getObject(0)` is guaranteed to be `undefined`. - self.global(&format!( - " + self.write_js_function( + &format!( + " function {name}(ptr, len) {{ if (ptr === 0) {{ return {get_object}(len); @@ -1869,31 +2116,45 @@ __wbg_set_wasm(wasm);" }} }} ", - name = ret, - get_string = get_string, - get_object = get_object - )); + name = ret, + get_string = get_string, + get_object = get_object + ), + &format!("{ret}"), + "(ptr, len)", + &[format!("'${get_object}'"), format!("'${get_string}'")], + ); Ok(ret) } - fn expose_get_array_js_value_from_wasm(&mut self, memory: MemoryId) -> Result { + fn expose_get_array_js_value_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { let mem = self.expose_dataview_memory(memory); let ret = MemView { name: "getArrayJsValueFromWasm".into(), num: mem.num, }; + import_deps.insert(format!("'${ret}'")); if !self.should_write_global(ret.to_string()) { return Ok(ret); } + let mem_formatted = if matches!(self.config.mode, OutputMode::Emscripten) { + format!("{mem}()") + } else { + format!("{}", mem.name) + }; match (self.aux.externref_table, self.aux.externref_drop_slice) { (Some(table), Some(drop)) => { let table = self.export_name_of(table); let drop = self.export_name_of(drop); - self.global(&format!( - " - function {}(ptr, len) {{ + self.write_js_function( + &format!( + " ptr = ptr >>> 0; - const mem = {}(); + const mem = {}; const result = []; for (let i = ptr; i < ptr + 4 * len; i += 4) {{ result.push(wasm.{}.get(mem.getUint32(i, true))); @@ -1902,16 +2163,20 @@ __wbg_set_wasm(wasm);" return result; }} ", - ret, mem, table, drop, - )); + mem_formatted, table, drop + ), + &format!("{ret}"), + "(ptr, len)", + &[format!("'${table}'"), format!("'${drop}'")], + ); } _ => { - self.expose_take_object(); - self.global(&format!( - " - function {}(ptr, len) {{ + self.expose_take_object(import_deps); + self.write_js_function( + &format!( + " ptr = ptr >>> 0; - const mem = {}(); + const mem = {}; const result = []; for (let i = ptr; i < ptr + 4 * len; i += 4) {{ result.push(takeObject(mem.getUint32(i, true))); @@ -1919,69 +2184,123 @@ __wbg_set_wasm(wasm);" return result; }} ", - ret, mem, - )); + mem_formatted, + ), + &format!("{ret}"), + "(ptr, len)", + &[format!("'$takeObject'")], + ); } } Ok(ret) } - fn expose_get_array_i8_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_i8_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_int8_memory(memory); - self.arrayget("getArrayI8FromWasm", view, 1) + self.arrayget("getArrayI8FromWasm", view, 1, import_deps) } - fn expose_get_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_u8_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_uint8_memory(memory); - self.arrayget("getArrayU8FromWasm", view, 1) + self.arrayget("getArrayU8FromWasm", view, 1, import_deps) } - fn expose_get_clamped_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_clamped_array_u8_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_clamped_uint8_memory(memory); - self.arrayget("getClampedArrayU8FromWasm", view, 1) + self.arrayget("getClampedArrayU8FromWasm", view, 1, import_deps) } - fn expose_get_array_i16_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_i16_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_int16_memory(memory); - self.arrayget("getArrayI16FromWasm", view, 2) + self.arrayget("getArrayI16FromWasm", view, 2, import_deps) } - fn expose_get_array_u16_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_u16_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_uint16_memory(memory); - self.arrayget("getArrayU16FromWasm", view, 2) + self.arrayget("getArrayU16FromWasm", view, 2, import_deps) } - fn expose_get_array_i32_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_i32_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_int32_memory(memory); - self.arrayget("getArrayI32FromWasm", view, 4) + self.arrayget("getArrayI32FromWasm", view, 4, import_deps) } - fn expose_get_array_u32_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_u32_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_uint32_memory(memory); - self.arrayget("getArrayU32FromWasm", view, 4) + self.arrayget("getArrayU32FromWasm", view, 4, import_deps) } - fn expose_get_array_i64_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_i64_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_int64_memory(memory); - self.arrayget("getArrayI64FromWasm", view, 8) + self.arrayget("getArrayI64FromWasm", view, 8, import_deps) } - fn expose_get_array_u64_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_u64_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_uint64_memory(memory); - self.arrayget("getArrayU64FromWasm", view, 8) + self.arrayget("getArrayU64FromWasm", view, 8, import_deps) } - fn expose_get_array_f32_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_f32_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_f32_memory(memory); - self.arrayget("getArrayF32FromWasm", view, 4) + self.arrayget("getArrayF32FromWasm", view, 4, import_deps) } - fn expose_get_array_f64_from_wasm(&mut self, memory: MemoryId) -> MemView { + fn expose_get_array_f64_from_wasm( + &mut self, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> MemView { let view = self.expose_f64_memory(memory); - self.arrayget("getArrayF64FromWasm", view, 8) + self.arrayget("getArrayF64FromWasm", view, 8, import_deps) } - fn arrayget(&mut self, name: &'static str, view: MemView, size: usize) -> MemView { + fn arrayget( + &mut self, + name: &'static str, + view: MemView, + size: usize, + import_deps: &mut HashSet, + ) -> MemView { let ret = MemView { name: name.into(), num: view.num, @@ -1989,17 +2308,25 @@ __wbg_set_wasm(wasm);" if !self.should_write_global(name) { return ret; } - self.global(&format!( - " - function {name}(ptr, len) {{ + let mem = if !matches!(self.config.mode, OutputMode::Emscripten) { + format!("{view}()") + } else { + format!("{}", view.name) + }; + import_deps.insert(format!("'${ret}'")); + self.write_js_function( + &format!( + " ptr = ptr >>> 0; - return {mem}().subarray(ptr / {size}, ptr / {size} + len); - }} + return {mem}.subarray(ptr / {size}, ptr / {size} + len); ", - name = ret, - mem = view, - size = size, - )); + mem = mem, + size = size, + ), + &format!("{ret}"), + "(ptr, len)", + &[], + ); ret } @@ -2052,6 +2379,26 @@ __wbg_set_wasm(wasm);" } fn memview(&mut self, kind: &'static str, memory: walrus::MemoryId) -> MemView { + if matches!(self.config.mode, OutputMode::Emscripten) { + // Emscripten provides its own version of getMemory + // so don't write out the memory function. + // See https://emscripten.org/docs/api_reference/preamble.js.html#type-accessors-for-the-memory-model + // for more details. + let emscripten_heap: &'static str = match kind { + "Int8Array" => "HEAP8", + "Uint8Array" | "Uint8ClampedArray" => "HEAPU8", + "Int16Array" => "HEAP16", + "Uint16Array" => "HEAPU16", + "Int32Array" => "HEAP32", + "Uint32Array" => "HEAPU32", + "Float32Array" => "HEAPF32", + "Float64Array" => "HEAPF64", + "DataView" => "HEAP_DATA_VIEW", + _ => "", + }; + return self.memview_memory(emscripten_heap, memory); + } + let view = self.memview_memory(kind, memory); if !self.should_write_global(view.name.clone()) { return view; @@ -2106,9 +2453,16 @@ __wbg_set_wasm(wasm);" .entry(memory) .or_insert((next, Default::default())); kinds.insert(kind); - MemView { - name: format!("get{}Memory", kind).into(), - num, + if matches!(self.config.mode, OutputMode::Emscripten) { + MemView { + name: format!("{}", kind).into(), + num, + } + } else { + MemView { + name: format!("get{}Memory", kind).into(), + num, + } } } @@ -2121,18 +2475,21 @@ __wbg_set_wasm(wasm);" } } - fn expose_assert_class(&mut self) { + fn expose_assert_class(&mut self, import_deps: &mut HashSet) { + let func_name = "_assertClass"; + import_deps.insert(format!("'${func_name}'")); if !self.should_write_global("assert_class") { return; } - self.global( + self.write_js_function( " - function _assertClass(instance, klass) { if (!(instance instanceof klass)) { throw new Error(`expected instance of ${klass.name}`); } - } ", + func_name, + "(instance, klass)", + &[], ); } @@ -2140,10 +2497,16 @@ __wbg_set_wasm(wasm);" if !self.should_write_global("stack_pointer") { return; } + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_deps.insert("'$stack_pointer'".to_string()); + return; + } self.global(&format!("let stack_pointer = {};", INITIAL_HEAP_OFFSET)); } - fn expose_borrowed_objects(&mut self) { + fn expose_borrowed_objects(&mut self, import_deps: &mut HashSet) { + let func_name = "addBorrowedObject"; + import_deps.insert(format!("'${func_name}'")); if !self.should_write_global("borrowed_objects") { return; } @@ -2154,31 +2517,35 @@ __wbg_set_wasm(wasm);" // after executing this. Once we've reserved stack space we write the // value. Eventually underflow will throw an exception, but JS sort of // just handles it today... - self.global( + self.write_js_function( " - function addBorrowedObject(obj) { if (stack_pointer == 1) throw new Error('out of js stack'); heap[--stack_pointer] = obj; return stack_pointer; - } ", + func_name, + "(obj)", + &[], ); } - fn expose_take_object(&mut self) { + fn expose_take_object(&mut self, import_deps: &mut HashSet) { + let func_name = "takeObject"; + import_deps.insert(format!("'${func_name}'")); if !self.should_write_global("take_object") { return; } - self.expose_get_object(); - self.expose_drop_ref(); - self.global( + self.expose_get_object(import_deps); + self.expose_drop_ref(import_deps); + self.write_js_function( " - function takeObject(idx) { const ret = getObject(idx); dropObject(idx); return ret; - } ", + func_name, + "(idx)", + &["'$getObject'".to_string(), "'$dropObject'".to_string()], ); } @@ -2217,7 +2584,8 @@ __wbg_set_wasm(wasm);" )); } - fn expose_handle_error(&mut self) -> Result<(), Error> { + fn expose_handle_error(&mut self, import_deps: &mut HashSet) -> Result<(), Error> { + import_deps.insert("'$handleError'".to_string()); if !self.should_write_global("handle_error") { return Ok(()); } @@ -2228,47 +2596,53 @@ __wbg_set_wasm(wasm);" let store = self.export_name_of(store); match (self.aux.externref_table, self.aux.externref_alloc) { (Some(table), Some(alloc)) => { - let add = self.expose_add_to_externref_table(table, alloc)?; - self.global(&format!( - "\ - function handleError(f, args) {{ + let add = self.expose_add_to_externref_table(table, alloc, import_deps)?; + self.write_js_function( + &format!( + "\ try {{ return f.apply(this, args); }} catch (e) {{ const idx = {}(e); wasm.{}(idx); }} - }} ", - add, store, - )); + add, store, + ), + "handleError", + "(f, args)", + &[format!("'${add}'")], + ); } _ => { self.expose_add_heap_object(); - self.global(&format!( - "\ - function handleError(f, args) {{ + self.write_js_function( + &format!( + "\ try {{ return f.apply(this, args); }} catch (e) {{ wasm.{}(addHeapObject(e)); }} - }} ", - store, - )); + store, + ), + "handleError", + "(f, args)", + &["'$addHeapObject'".to_string()], + ); } } Ok(()) } - fn expose_log_error(&mut self) { + fn expose_log_error(&mut self, import_deps: &mut HashSet) { + import_deps.insert("'$logError'".to_string()); if !self.should_write_global("log_error") { return; } - self.global( + self.write_js_function( "\ - function logError(f, args) { try { return f.apply(this, args); } catch (e) { @@ -2286,24 +2660,39 @@ __wbg_set_wasm(wasm);" error); throw e; } - } ", + "logError", + "(f, args)", + &[], ); } - fn pass_to_wasm_function(&mut self, t: VectorKind, memory: MemoryId) -> Result { + fn pass_to_wasm_function( + &mut self, + t: VectorKind, + memory: MemoryId, + import_deps: &mut HashSet, + ) -> Result { match t { - VectorKind::String => self.expose_pass_string_to_wasm(memory), + VectorKind::String => self.expose_pass_string_to_wasm(memory, import_deps), VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => { - self.expose_pass_array8_to_wasm(memory) + self.expose_pass_array8_to_wasm(memory, import_deps) + } + VectorKind::U16 | VectorKind::I16 => { + self.expose_pass_array16_to_wasm(memory, import_deps) + } + VectorKind::I32 | VectorKind::U32 => { + self.expose_pass_array32_to_wasm(memory, import_deps) + } + VectorKind::I64 | VectorKind::U64 => { + self.expose_pass_array64_to_wasm(memory, import_deps) + } + VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory, import_deps), + VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory, import_deps), + VectorKind::Externref => self.expose_pass_array_jsvalue_to_wasm(memory, import_deps), + VectorKind::NamedExternref(_) => { + self.expose_pass_array_jsvalue_to_wasm(memory, import_deps) } - VectorKind::U16 | VectorKind::I16 => self.expose_pass_array16_to_wasm(memory), - VectorKind::I32 | VectorKind::U32 => self.expose_pass_array32_to_wasm(memory), - VectorKind::I64 | VectorKind::U64 => self.expose_pass_array64_to_wasm(memory), - VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory), - VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory), - VectorKind::Externref => self.expose_pass_array_jsvalue_to_wasm(memory), - VectorKind::NamedExternref(_) => self.expose_pass_array_jsvalue_to_wasm(memory), } } @@ -2311,22 +2700,29 @@ __wbg_set_wasm(wasm);" &mut self, ty: VectorKind, memory: MemoryId, + import_deps: &mut HashSet, ) -> Result { Ok(match ty { VectorKind::String => self.expose_get_string_from_wasm(memory)?, - VectorKind::I8 => self.expose_get_array_i8_from_wasm(memory), - VectorKind::U8 => self.expose_get_array_u8_from_wasm(memory), - VectorKind::ClampedU8 => self.expose_get_clamped_array_u8_from_wasm(memory), - VectorKind::I16 => self.expose_get_array_i16_from_wasm(memory), - VectorKind::U16 => self.expose_get_array_u16_from_wasm(memory), - VectorKind::I32 => self.expose_get_array_i32_from_wasm(memory), - VectorKind::U32 => self.expose_get_array_u32_from_wasm(memory), - VectorKind::I64 => self.expose_get_array_i64_from_wasm(memory), - VectorKind::U64 => self.expose_get_array_u64_from_wasm(memory), - VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory), - VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory), - VectorKind::Externref => self.expose_get_array_js_value_from_wasm(memory)?, - VectorKind::NamedExternref(_) => self.expose_get_array_js_value_from_wasm(memory)?, + VectorKind::I8 => self.expose_get_array_i8_from_wasm(memory, import_deps), + VectorKind::U8 => self.expose_get_array_u8_from_wasm(memory, import_deps), + VectorKind::ClampedU8 => { + self.expose_get_clamped_array_u8_from_wasm(memory, import_deps) + } + VectorKind::I16 => self.expose_get_array_i16_from_wasm(memory, import_deps), + VectorKind::U16 => self.expose_get_array_u16_from_wasm(memory, import_deps), + VectorKind::I32 => self.expose_get_array_i32_from_wasm(memory, import_deps), + VectorKind::U32 => self.expose_get_array_u32_from_wasm(memory, import_deps), + VectorKind::I64 => self.expose_get_array_i64_from_wasm(memory, import_deps), + VectorKind::U64 => self.expose_get_array_u64_from_wasm(memory, import_deps), + VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory, import_deps), + VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory, import_deps), + VectorKind::Externref => { + self.expose_get_array_js_value_from_wasm(memory, import_deps)? + } + VectorKind::NamedExternref(_) => { + self.expose_get_array_js_value_from_wasm(memory, import_deps)? + } }) } @@ -2357,17 +2753,31 @@ __wbg_set_wasm(wasm);" ); } - fn expose_is_like_none(&mut self) { + fn expose_is_like_none(&mut self, import_deps: &mut HashSet) { + if matches!(self.config.mode, OutputMode::Emscripten) { + import_deps.insert("'$isLikeNone'".to_string()); + } if !self.should_write_global("is_like_none") { return; } - self.global( - " - function isLikeNone(x) { - return x === undefined || x === null; - } + + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_library.push_str( + " + $isLikeNone: function(x) { + return x == null; + }, ", - ); + ); + } else { + self.global( + " + function isLikeNone(x) { + return x == null; + } + ", + ); + } } fn expose_assert_non_null(&mut self) { @@ -2410,34 +2820,68 @@ __wbg_set_wasm(wasm);" // while we invoke it. If we finish and the closure wasn't // destroyed, then we put back the pointer so a future // invocation can succeed. - self.global(&format!( - " - function makeMutClosure(arg0, arg1, dtor, f) {{ - const state = {{ a: arg0, b: arg1, cnt: 1, dtor }}; - const real = (...args) => {{ - // First up with a closure we increment the internal reference - // count. This ensures that the Rust closure environment won't - // be deallocated while we're invoking it. - state.cnt++; - const a = state.a; - state.a = 0; - try {{ - return f(a, state.b, ...args); - }} finally {{ - if (--state.cnt === 0) {{ - wasm.{table}.get(state.dtor)(a, state.b); - CLOSURE_DTORS.unregister(state); - }} else {{ - state.a = a; + + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_library.push_str(&format!( + " + $makeMutClosure: function(arg0, arg1, dtor, f) {{ + const state = {{ a: arg0, b: arg1, cnt: 1, dtor }}; + const real = (...args) => {{ + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try {{ + return f(a, state.b, ...args); + }} finally {{ + if (--state.cnt === 0) {{ + wasmExports['{table}'].get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + }} else {{ + state.a = a; + }} }} - }} - }}; - real.original = state; - CLOSURE_DTORS.register(real, state, state); - return real; - }} - ", - )); + }}; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; + }}, + $makeMutClosure__deps: ['$CLOSURE_DTORS'],\n + ", + )); + self.emscripten_deps.insert("'$CLOSURE_DTORS'".to_string()); + } else { + self.global(&format!( + " + function makeMutClosure(arg0, arg1, dtor, f) {{ + const state = {{ a: arg0, b: arg1, cnt: 1, dtor }}; + const real = (...args) => {{ + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try {{ + return f(a, state.b, ...args); + }} finally {{ + if (--state.cnt === 0) {{ + wasm.{table}.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + }} else {{ + state.a = a; + }} + }} + }}; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; + }} + ", + )); + } Ok(()) } @@ -2456,32 +2900,61 @@ __wbg_set_wasm(wasm);" // executing the destructor, however, we clear out the // `this.a` pointer to prevent it being used again the // future. - self.global(&format!( - " - function makeClosure(arg0, arg1, dtor, f) {{ - const state = {{ a: arg0, b: arg1, cnt: 1, dtor }}; - const real = (...args) => {{ - // First up with a closure we increment the internal reference - // count. This ensures that the Rust closure environment won't - // be deallocated while we're invoking it. - state.cnt++; - try {{ - return f(state.a, state.b, ...args); - }} finally {{ - if (--state.cnt === 0) {{ - wasm.{table}.get(state.dtor)(state.a, state.b); - state.a = 0; - CLOSURE_DTORS.unregister(state); + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_library.push_str(&format!( + " + $makeClosure: function(arg0, arg1, dtor, f) {{ + const state = {{ a: arg0, b: arg1, cnt: 1, dtor }}; + const real = (...args) => {{ + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + try {{ + return f(state.a, state.b, ...args); + }} finally {{ + if (--state.cnt === 0) {{ + wasmExports['{table}'].get(state.dtor)(state.a, state.b); + state.a = 0; + CLOSURE_DTORS.unregister(state); + }} }} - }} - }}; - real.original = state; - CLOSURE_DTORS.register(real, state, state); - return real; - }} - ", - )); - + }}; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; + }}, + $makeClosure__deps: ['$CLOSURE_DTORS'],\n + " + )); + self.emscripten_deps.insert("'$CLOSURE_DTORS'".to_string()); + } else { + self.global(&format!( + " + function makeClosure(arg0, arg1, dtor, f) {{ + const state = {{ a: arg0, b: arg1, cnt: 1, dtor }}; + const real = (...args) => {{ + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + try {{ + return f(state.a, state.b, ...args); + }} finally {{ + if (--state.cnt === 0) {{ + wasm.{table}.get(state.dtor)(state.a, state.b); + state.a = 0; + CLOSURE_DTORS.unregister(state); + }} + }} + }}; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; + }} + ", + )); + } Ok(()) } @@ -2490,16 +2963,27 @@ __wbg_set_wasm(wasm);" return Ok(()); } let table = self.export_function_table()?; - self.global(&format!( - " - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') - ? {{ register: () => {{}}, unregister: () => {{}} }} - : new FinalizationRegistry(state => {{ - wasm.{table}.get(state.dtor)(state.a, state.b) - }}); - " - )); - + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_library.push_str( + " + $CLOSURE_DTORS: `(typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasmExports['__indirect_function_table'].get(state.dtor)(state.a, state.b) + })`,\n + ", + ); + } else { + self.global(&format!( + " + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? {{ register: () => {{}}, unregister: () => {{}} }} + : new FinalizationRegistry(state => {{ + wasm.{table}.get(state.dtor)(state.a, state.b) + }}); + " + )); + } Ok(()) } fn global(&mut self, s: &str) { @@ -2514,6 +2998,59 @@ __wbg_set_wasm(wasm);" self.globals.push('\n'); } + fn emscripten_library(&mut self, s: &str) { + let s = s.trim(); + + // Ensure a blank line between adjacent items, and ensure everything is + // terminated with a newline. + while !self.emscripten_library.ends_with("\n\n\n") + && !self.emscripten_library.ends_with("*/\n") + { + self.emscripten_library.push('\n'); + } + self.emscripten_library.push_str(s); + self.emscripten_library.push('\n'); + } + + fn write_js_function(&mut self, body: &str, func_name: &str, args: &str, deps: &[String]) { + if matches!(self.config.mode, OutputMode::Emscripten) { + let re = Regex::new(r"(?:wasmExports|wasm)\.([a-zA-Z0-9_$]*)").unwrap(); + let formatted_body = if let Some(_) = re.captures(body) { + re.replace_all(body, "wasmExports[\'$1\']").to_string() + } else { + body.to_string() + }; + + self.emscripten_library(&format!( + "${}: function{} {{ + {} + }}, + ", + func_name, + args, + formatted_body.trim() + )); + if !deps.is_empty() { + self.emscripten_library.push_str(&format!( + "${}_deps: [{}], + ", + func_name, + deps.join(",") + )); + } + } else { + self.global(&format!( + "function {}{} {{ + {} + }} + ", + func_name, + args, + body.trim() + )); + } + } + fn require_class_wrap(&mut self, name: &str) { require_class(&mut self.exported_classes, name).wrap_needed = true; } @@ -2699,24 +3236,29 @@ __wbg_set_wasm(wasm);" &mut self, table: TableId, alloc: FunctionId, + import_deps: &mut HashSet, ) -> Result { let view = self.memview_table("addToExternrefTable", table); assert!(self.config.externref); + import_deps.insert(format!("'${}'", view).to_string()); if !self.should_write_global(view.to_string()) { return Ok(view); } let alloc = self.export_name_of(alloc); let table = self.export_name_of(table); - self.global(&format!( - " - function {}(obj) {{ + self.write_js_function( + &format!( + " const idx = wasm.{}(); wasm.{}.set(idx, obj); return idx; - }} ", - view, alloc, table, - )); + alloc, table, + ), + &format!("{}", view), + "(obj)", + &[], + ); Ok(view) } @@ -2865,7 +3407,7 @@ __wbg_set_wasm(wasm);" ContextAdapterKind::Export(e) => format!("`{}`", e.debug_name), ContextAdapterKind::Adapter => format!("adapter {}", id.0), }; - + let mut import_deps: HashSet = Default::default(); // Process the `binding` and generate a bunch of JS/TypeScript/etc. let binding::JsFunction { ts_sig, @@ -2889,6 +3431,7 @@ __wbg_set_wasm(wasm);" &debug_name, ret_ty_override, ret_desc, + &mut import_deps, ) .with_context(|| "failed to generates bindings for ".to_string() + &debug_name)?; @@ -2919,7 +3462,9 @@ __wbg_set_wasm(wasm);" AuxExportKind::Function(name) => { if let Some(ts_sig) = ts_sig { self.typescript.push_str(&ts_docs); - self.typescript.push_str("export function "); + if !matches!(self.config.mode, OutputMode::Emscripten) { + self.typescript.push_str("export function "); + } self.typescript.push_str(name); self.typescript.push_str(ts_sig); self.typescript.push_str(";\n"); @@ -3002,7 +3547,7 @@ __wbg_set_wasm(wasm);" } } ContextAdapterKind::Import(core) => { - let code = if catch { + let mut code = if catch { format!( "function() {{ return handleError(function {}, arguments) }}", code @@ -3016,16 +3561,40 @@ __wbg_set_wasm(wasm);" format!("function{}", code) }; + if matches!(self.config.mode, OutputMode::Emscripten) && !import_deps.is_empty() { + let mut import_deps_vec = import_deps + .iter() + .map(|s| s.as_str()) + .collect::>(); + // sort to generate deterministic output. + import_deps_vec.sort(); + let deps: String = format!( + "{}__deps: [{}]\n", + self.module.imports.get(core).name, + import_deps_vec.join(",") + ); + code = format!("{},\n{}\n", code, deps); + } + self.wasm_import_definitions.insert(core, code); } ContextAdapterKind::Adapter => { assert!(!catch); assert!(!log_error); - self.globals.push_str("function "); - self.globals.push_str(&self.adapter_name(id)); - self.globals.push_str(&code); - self.globals.push_str("\n\n"); + if matches!(self.config.mode, OutputMode::Emscripten) { + self.emscripten_library.push('$'); + self.emscripten_library.push_str(&self.adapter_name(id)); + self.emscripten_library.push_str(": function"); + self.emscripten_library + .push_str(&code.replace("wasm.", "_")); + self.emscripten_library.push_str(",\n\n"); + } else { + self.globals.push_str("function "); + self.globals.push_str(&self.adapter_name(id)); + self.globals.push_str(&code); + self.globals.push_str("\n\n"); + } } } Ok(()) @@ -3249,6 +3818,7 @@ __wbg_set_wasm(wasm);" args: &[String], variadic: bool, prelude: &mut String, + import_deps: &mut HashSet, ) -> Result { let variadic_args = |js_arguments: &[String]| { Ok(if !variadic { @@ -3265,6 +3835,18 @@ __wbg_set_wasm(wasm);" } }) }; + + // Emscripten needs the function name extracted from the full function call here + // to construct dependency lists for each import. + if matches!(self.config.mode, OutputMode::Emscripten) { + let re = Regex::new(r"([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(").unwrap(); + for arg in args { + if let Some(result) = re.captures(arg) { + import_deps.insert(format!("'${}'", &result[1])); + } + } + } + match import { AuxImport::Value(val) => match kind { AdapterJsImportKind::Constructor => { @@ -3376,10 +3958,12 @@ __wbg_set_wasm(wasm);" assert_eq!(args.len(), 3); let call = self.adapter_name(*adapter); - + import_deps.insert(format!("'${}'", call)); if *mutable { self.expose_make_mut_closure()?; - + if matches!(self.config.mode, OutputMode::Emscripten) { + import_deps.insert("'$makeMutClosure'".to_string()); + } Ok(format!( "makeMutClosure({arg0}, {arg1}, {dtor}, {call})", arg0 = &args[0], @@ -3389,7 +3973,9 @@ __wbg_set_wasm(wasm);" )) } else { self.expose_make_closure()?; - + if matches!(self.config.mode, OutputMode::Emscripten) { + import_deps.insert("'$makeClosure'".to_string()); + } Ok(format!( "makeClosure({arg0}, {arg1}, {dtor}, {call})", arg0 = &args[0], @@ -3510,7 +4096,7 @@ __wbg_set_wasm(wasm);" AuxImport::Intrinsic(intrinsic) => { assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); - self.invoke_intrinsic(intrinsic, args, prelude) + self.invoke_intrinsic(intrinsic, args, prelude, import_deps) } AuxImport::LinkTo(path, content) => { @@ -3522,7 +4108,8 @@ __wbg_set_wasm(wasm);" OutputMode::Web | OutputMode::Bundler { .. } | OutputMode::Deno - | OutputMode::Node { module: true } => "import.meta.url", + | OutputMode::Node { module: true } + | OutputMode::Emscripten => "import.meta.url", OutputMode::Node { module: false } => { "require('url').pathToFileURL(__filename)" } @@ -3575,6 +4162,7 @@ __wbg_set_wasm(wasm);" intrinsic: &Intrinsic, args: &[String], prelude: &mut String, + import_deps: &mut HashSet, ) -> Result { let expr = match intrinsic { Intrinsic::JsvalEq => { @@ -3899,7 +4487,10 @@ __wbg_set_wasm(wasm);" ); } drop(memories); - format!("wasm.{}", self.export_name_of(memory)) + match self.config.mode { + OutputMode::Emscripten { .. } => "HEAPU8".to_string(), + _ => format!("wasm.{}", self.export_name_of(memory)), + } } Intrinsic::FunctionTable => { @@ -3911,6 +4502,7 @@ __wbg_set_wasm(wasm);" Intrinsic::DebugString => { assert_eq!(args.len(), 1); self.expose_debug_string(); + import_deps.insert("'$debugString'".to_string()); format!("debugString({})", args[0]) } @@ -3987,20 +4579,25 @@ __wbg_set_wasm(wasm);" .aux .externref_table .ok_or_else(|| anyhow!("must enable externref to use externref intrinsic"))?; + let mut base = "\n".to_string(); let name = self.export_name_of(table); + + if matches!(self.config.mode, OutputMode::Emscripten) { + base.push_str(&format!("const table = wasmExports['{name}'];\n")); + } else { + base.push_str(&format!("const table = wasm.{name};\n")); + } + // Grow the table to insert our initial values, and then also // set the 0th slot to `undefined` since that's what we've // historically used for our ABI which is that the index of 0 // returns `undefined` for types like `None` going out. - let mut base = format!( - " - const table = wasm.{}; - const offset = table.grow({}); + base.push_str(&format!( + " const offset = table.grow({}); table.set(0, undefined); ", - name, INITIAL_HEAP_VALUES.len(), - ); + )); for (i, value) in INITIAL_HEAP_VALUES.iter().enumerate() { base.push_str(&format!("table.set(offset + {}, {});\n", i, value)); } @@ -4185,9 +4782,8 @@ __wbg_set_wasm(wasm);" return; } - self.global( + self.write_js_function( " - function debugString(val) { // primitive types const type = typeof val; if (type == 'number' || type == 'boolean' || val == null) { @@ -4250,8 +4846,10 @@ __wbg_set_wasm(wasm);" } // TODO we could test for more things here, like `Set`s and `Map`s. return className; - } ", + "debugString", + "(val)", + &[], ); } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index a07f48306a1..8ddffc5713a 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -66,6 +66,7 @@ enum OutputMode { NoModules { global: String }, Node { module: bool }, Deno, + Emscripten, } enum Input { @@ -226,6 +227,13 @@ impl Bindgen { Ok(self) } + pub fn emscripten(&mut self, emscripten: bool) -> Result<&mut Bindgen, Error> { + if emscripten { + self.switch_mode(OutputMode::Emscripten, "--target emscripten")?; + } + Ok(self) + } + pub fn debug(&mut self, debug: bool) -> &mut Bindgen { self.debug = debug; self @@ -717,6 +725,9 @@ export * from \"./{js_name}\"; )?; } write(out_dir.join(&js_name), reset_indentation(&gen.js))?; + } else if matches!(self.generated.mode, OutputMode::Emscripten) { + let emscripten_js_path = out_dir.join("library_bindgen.js"); + write(&emscripten_js_path, reset_indentation(&gen.js))?; } else { write(&js_path, reset_indentation(&gen.js))?; } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 2e9b8a28019..dab9db83e00 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -41,7 +41,7 @@ assert_cmd = "2" diff = "0.1" predicates = "3" rayon = "1.0" -wasmparser = "0.214" +wasmparser = "0.227" wasmprinter = "0.214" [[test]] diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/emscripten_test.js b/crates/cli/src/bin/wasm-bindgen-test-runner/emscripten_test.js new file mode 100644 index 00000000000..56427051f41 --- /dev/null +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/emscripten_test.js @@ -0,0 +1,55 @@ +(function() {{ + var elem = document.querySelector('#output'); + window.extraLibraryFuncs = []; + window.addToLibrary = function(LibraryWbg) { + window.wasmExports = {__wbindgen_start:() => {}}; + window.cachedTextEncoder = {encodeInto:() => {}}; + window.Module = {}; + + try { + LibraryWbg.$initBindgen(); + } catch (e) { + elem.innerText = 'test setup failed: ' + e; + } + + function testExtraLibraryFuncs () { + ['$initBindgen', '$addOnInit', '$CLOSURE_DTORS', '$getStringFromWasm0'].forEach((value) => { + if (!extraLibraryFuncs.includes(value)) { + return { status: false, e: `test result: ${value} not found`}; + } + }); + return {status: true, e: 'test result: ok'}; + } + + function testLibraryWbg () { + if (typeof Module.hello !== 'function') { + return {status: false, e:'test result: hello() is not found'}; + } + if (typeof Module.Interval !== 'function') { + return {status: false, e:'test result: Interval is not found'}; + } + + const keys = Object.keys(LibraryWbg); + const testNames = ['clearInterval', 'setInterval', 'log']; + + for (const name of testNames) { + const regex = new RegExp(`^__wbg_${name}`); + const res = keys.find(key => regex.test(key)); + if (!res) { + return {status: false, e:`test result: ${name} not found`}; + } + } + return {status: true, e:'test result: ok'}; + } + + const tests = [testExtraLibraryFuncs(), testLibraryWbg()]; + for (const res of tests) { + if (!res.status) { + elem.innerText = res.e; + return; + } + } + elem.innerText = 'test result: ok'; + + }; +}}()); \ No newline at end of file diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/index-emscripten.html b/crates/cli/src/bin/wasm-bindgen-test-runner/index-emscripten.html new file mode 100644 index 00000000000..a931b750a7a --- /dev/null +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/index-emscripten.html @@ -0,0 +1,14 @@ + + + + + + +
Loading scripts...
+

+    

+    

+    

+    
+  
+
diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs
index a4b8c58de2b..60a9618e157 100644
--- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs
+++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs
@@ -111,16 +111,27 @@ fn main() -> anyhow::Result<()> {
 
     let shell = shell::Shell::new();
 
-    let file_name = cli
+    let mut file_name = cli
         .file
         .file_name()
         .map(Path::new)
         .context("file to test is not a valid file, can't extract file name")?;
 
+    let mut file_name_buf = cli.file.clone();
+
+    // Repoint the file to be read from "name.js" to "name.wasm" in the case of emscripten.
+    // Rustc generates a .js and a .wasm file when targeting emscripten. It lists the .js
+    // file as the primary executor which is inconsitent with what is expected here.
+    if file_name.extension().unwrap() == "js" {
+        file_name_buf.pop();
+        file_name_buf.push(file_name.file_stem().unwrap());
+        file_name_buf.set_extension("wasm");
+        file_name = Path::new(&file_name_buf);
+    }
     // Collect all tests that the test harness is supposed to run. We assume
     // that any exported function with the prefix `__wbg_test` is a test we need
     // to execute.
-    let wasm = fs::read(&cli.file).context("failed to read Wasm file")?;
+    let wasm = fs::read(&file_name_buf).context("failed to read Wasm file")?;
     let mut wasm =
         walrus::Module::from_buffer(&wasm).context("failed to deserialize Wasm module")?;
     let mut tests = Tests::new();
@@ -203,6 +214,7 @@ fn main() -> anyhow::Result<()> {
         Some(section) if section.data.contains(&0x03) => TestMode::SharedWorker { no_modules },
         Some(section) if section.data.contains(&0x04) => TestMode::ServiceWorker { no_modules },
         Some(section) if section.data.contains(&0x05) => TestMode::Node { no_modules },
+        Some(section) if section.data.contains(&0x06) => TestMode::Emscripten {},
         Some(_) => bail!("invalid __wasm_bingen_test_unstable value"),
         None => {
             let mut modes = Vec::new();
@@ -296,6 +308,7 @@ fn main() -> anyhow::Result<()> {
                 b.web(true)?
             }
         }
+        TestMode::Emscripten {} => b.emscripten(true)?,
     };
 
     if std::env::var("WASM_BINDGEN_SPLIT_LINKED_MODULES").is_ok() {
@@ -316,6 +329,18 @@ fn main() -> anyhow::Result<()> {
         TestMode::Node { no_modules } => {
             node::execute(module, tmpdir.path(), cli, tests, !no_modules, coverage)?
         }
+        TestMode::Emscripten => {
+            let srv = server::spawn_emscripten(
+                &"127.0.0.1:0".parse().unwrap(),
+                tmpdir.path(),
+                std::env::var("WASM_BINDGEN_TEST_NO_ORIGIN_ISOLATION").is_err(),
+            )
+            .context("failed to spawn server")?;
+            let addr = srv.server_addr();
+            println!("Tests are now available at http://{}", addr);
+            thread::spawn(|| srv.run());
+            headless::run(&addr, &shell, driver_timeout, browser_timeout)?;
+        }
         TestMode::Deno => deno::execute(module, tmpdir.path(), cli, tests)?,
         TestMode::Browser { .. }
         | TestMode::DedicatedWorker { .. }
@@ -372,6 +397,7 @@ enum TestMode {
     DedicatedWorker { no_modules: bool },
     SharedWorker { no_modules: bool },
     ServiceWorker { no_modules: bool },
+    Emscripten,
 }
 
 impl TestMode {
@@ -384,7 +410,7 @@ impl TestMode {
 
     fn no_modules(self) -> bool {
         match self {
-            Self::Deno => true,
+            Self::Deno | Self::Emscripten => true,
             Self::Browser { no_modules }
             | Self::Node { no_modules }
             | Self::DedicatedWorker { no_modules }
@@ -401,6 +427,7 @@ impl TestMode {
             TestMode::DedicatedWorker { .. } => "WASM_BINDGEN_USE_DEDICATED_WORKER",
             TestMode::SharedWorker { .. } => "WASM_BINDGEN_USE_SHARED_WORKER",
             TestMode::ServiceWorker { .. } => "WASM_BINDGEN_USE_SERVICE_WORKER",
+            TestMode::Emscripten { .. } => "WASM_BINDGEN_USE_EMSCRIPTEN",
         }
     }
 }
diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs
index f5b088a17b6..24c62417c15 100644
--- a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs
+++ b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs
@@ -353,37 +353,74 @@ pub(crate) fn spawn(
         response
     })
     .map_err(|e| anyhow!("{}", e))?;
-    return Ok(srv);
+    Ok(srv)
+}
+
+pub(crate) fn spawn_emscripten(
+    addr: &SocketAddr,
+    tmpdir: &Path,
+    isolate_origin: bool,
+) -> Result Response + Send + Sync>, Error> {
+    let js_path = tmpdir.join("run.js");
+    fs::write(js_path, include_str!("emscripten_test.js")).context("failed to write JS file")?;
+    let tmpdir = tmpdir.to_path_buf();
+    let srv = Server::new(addr, move |request| {
+        if request.url() == "/" {
+            let s = include_str!("index-emscripten.html");
+            let s = s.replace(
+                "",
+                "\n     ",
+            );
+
+            let response = Response::from_data("text/html", s);
 
-    fn try_asset(request: &Request, dir: &Path) -> Response {
-        let response = rouille::match_assets(request, dir);
-        if response.is_success() {
             return response;
         }
 
-        // When a browser is doing ES imports it's using the directives we
-        // write in the code that *don't* have file extensions (aka we say `from
-        // 'foo'` instead of `from 'foo.js'`. Fixup those paths here to see if a
-        // `js` file exists.
-        if let Some(part) = request.url().split('/').last() {
-            if !part.contains('.') {
-                let new_request = Request::fake_http(
-                    request.method(),
-                    format!("{}.js", request.url()),
-                    request
-                        .headers()
-                        .map(|(a, b)| (a.to_string(), b.to_string()))
-                        .collect(),
-                    Vec::new(),
-                );
-                let response = rouille::match_assets(&new_request, dir);
-                if response.is_success() {
-                    return response;
-                }
-            }
+        let mut response = try_asset(request, &tmpdir);
+        if !response.is_success() {
+            response = try_asset(request, ".".as_ref());
+        }
+        // Make sure browsers don't cache anything (Chrome appeared to with this
+        // header?)
+        response.headers.retain(|(k, _)| k != "Cache-Control");
+        if isolate_origin {
+            set_isolate_origin_headers(&mut response)
         }
         response
+    })
+    .map_err(|e| anyhow!("{}", e))?;
+    Ok(srv)
+}
+
+fn try_asset(request: &Request, dir: &Path) -> Response {
+    let response = rouille::match_assets(request, dir);
+    if response.is_success() {
+        return response;
+    }
+
+    // When a browser is doing ES imports it's using the directives we
+    // write in the code that *don't* have file extensions (aka we say `from
+    // 'foo'` instead of `from 'foo.js'`. Fixup those paths here to see if a
+    // `js` file exists.
+    if let Some(part) = request.url().split('/').last() {
+        if !part.contains('.') {
+            let new_request = Request::fake_http(
+                request.method(),
+                format!("{}.js", request.url()),
+                request
+                    .headers()
+                    .map(|(a, b)| (a.to_string(), b.to_string()))
+                    .collect(),
+                Vec::new(),
+            );
+            let response = rouille::match_assets(&new_request, dir);
+            if response.is_success() {
+                return response;
+            }
+        }
     }
+    response
 }
 
 fn handle_coverage_dump(profraw_path: &Path, request: &Request) -> anyhow::Result<()> {
diff --git a/crates/cli/src/bin/wasm-bindgen.rs b/crates/cli/src/bin/wasm-bindgen.rs
index b705e8a09dd..c9c600dc139 100644
--- a/crates/cli/src/bin/wasm-bindgen.rs
+++ b/crates/cli/src/bin/wasm-bindgen.rs
@@ -110,6 +110,7 @@ fn rmain(args: &Args) -> Result<(), Error> {
             "nodejs" => b.nodejs(true)?,
             "deno" => b.deno(true)?,
             "experimental-nodejs-module" => b.nodejs_module(true)?,
+            "emscripten" => b.emscripten(true)?,
             s => bail!("invalid encode-into mode: `{}`", s),
         };
     }
diff --git a/crates/cli/tests/reference/echo.js b/crates/cli/tests/reference/echo.js
index c786581f554..9b439df97e7 100644
--- a/crates/cli/tests/reference/echo.js
+++ b/crates/cli/tests/reference/echo.js
@@ -98,7 +98,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
 });
 
 function passStringToWasm0(arg, malloc, realloc) {
-
     if (realloc === undefined) {
         const buf = cachedTextEncoder.encode(arg);
         const ptr = malloc(buf.length, 1) >>> 0;
@@ -146,7 +145,7 @@ function getDataViewMemory0() {
 }
 
 function isLikeNone(x) {
-    return x === undefined || x === null;
+    return x == null;
 }
 
 const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder;
diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js
index 10d698dc7e5..edc9b7e134a 100644
--- a/crates/cli/tests/reference/enums.js
+++ b/crates/cli/tests/reference/enums.js
@@ -33,7 +33,7 @@ export function enum_echo(color) {
 }
 
 function isLikeNone(x) {
-    return x === undefined || x === null;
+    return x == null;
 }
 /**
  * @param {Color | null} [color]
diff --git a/crates/cli/tests/reference/getter-setter.js b/crates/cli/tests/reference/getter-setter.js
index a9df5536a5b..497e3bc3b96 100644
--- a/crates/cli/tests/reference/getter-setter.js
+++ b/crates/cli/tests/reference/getter-setter.js
@@ -25,7 +25,7 @@ function getStringFromWasm0(ptr, len) {
 }
 
 function isLikeNone(x) {
-    return x === undefined || x === null;
+    return x == null;
 }
 
 let WASM_VECTOR_LEN = 0;
@@ -48,7 +48,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
 });
 
 function passStringToWasm0(arg, malloc, realloc) {
-
     if (realloc === undefined) {
         const buf = cachedTextEncoder.encode(arg);
         const ptr = malloc(buf.length, 1) >>> 0;
diff --git a/crates/cli/tests/reference/int128.js b/crates/cli/tests/reference/int128.js
index 32de778621c..7bcc40d1e58 100644
--- a/crates/cli/tests/reference/int128.js
+++ b/crates/cli/tests/reference/int128.js
@@ -42,7 +42,7 @@ export function echo_u128(a) {
 }
 
 function isLikeNone(x) {
-    return x === undefined || x === null;
+    return x == null;
 }
 /**
  * @param {bigint | null} [a]
diff --git a/crates/cli/tests/reference/optional-args.js b/crates/cli/tests/reference/optional-args.js
index 43ead5d86c1..d907ecc3bf3 100644
--- a/crates/cli/tests/reference/optional-args.js
+++ b/crates/cli/tests/reference/optional-args.js
@@ -5,7 +5,7 @@ export function __wbg_set_wasm(val) {
 
 
 function isLikeNone(x) {
-    return x === undefined || x === null;
+    return x == null;
 }
 /**
  * @param {number | null} [a]
diff --git a/crates/cli/tests/reference/static.js b/crates/cli/tests/reference/static.js
index 34aa7975e7a..d96485a94cf 100644
--- a/crates/cli/tests/reference/static.js
+++ b/crates/cli/tests/reference/static.js
@@ -5,7 +5,7 @@ export function __wbg_set_wasm(val) {
 
 
 function isLikeNone(x) {
-    return x === undefined || x === null;
+    return x == null;
 }
 
 function addToExternrefTable0(obj) {
diff --git a/crates/cli/tests/reference/string-arg.js b/crates/cli/tests/reference/string-arg.js
index 9d52328a50f..41a902fcee6 100644
--- a/crates/cli/tests/reference/string-arg.js
+++ b/crates/cli/tests/reference/string-arg.js
@@ -44,7 +44,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
 });
 
 function passStringToWasm0(arg, malloc, realloc) {
-
     if (realloc === undefined) {
         const buf = cachedTextEncoder.encode(arg);
         const ptr = malloc(buf.length, 1) >>> 0;
diff --git a/crates/cli/tests/reference/wasm-export-types.js b/crates/cli/tests/reference/wasm-export-types.js
index de290e90502..4a034f8c293 100644
--- a/crates/cli/tests/reference/wasm-export-types.js
+++ b/crates/cli/tests/reference/wasm-export-types.js
@@ -36,7 +36,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
 });
 
 function passStringToWasm0(arg, malloc, realloc) {
-
     if (realloc === undefined) {
         const buf = cachedTextEncoder.encode(arg);
         const ptr = malloc(buf.length, 1) >>> 0;
diff --git a/crates/cli/tests/reference/web-sys.js b/crates/cli/tests/reference/web-sys.js
index 37300cfdcaa..eb714775b3d 100644
--- a/crates/cli/tests/reference/web-sys.js
+++ b/crates/cli/tests/reference/web-sys.js
@@ -124,7 +124,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
 });
 
 function passStringToWasm0(arg, malloc, realloc) {
-
     if (realloc === undefined) {
         const buf = cachedTextEncoder.encode(arg);
         const ptr = malloc(buf.length, 1) >>> 0;
diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml
index caf381c899b..7b7f54cc8f1 100644
--- a/crates/futures/Cargo.toml
+++ b/crates/futures/Cargo.toml
@@ -5,7 +5,7 @@ documentation = "https://docs.rs/wasm-bindgen-futures"
 edition = "2021"
 homepage = "https://rustwasm.github.io/wasm-bindgen/"
 include = ["/LICENSE-*", "/src"]
-license = "MIT OR Apache-2.0"
+license = "(MIT OR Apache-2.0) AND MPL-2.0"
 name = "wasm-bindgen-futures"
 readme = "./README.md"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures"
diff --git a/crates/futures/LICENSE-MPL-2.0 b/crates/futures/LICENSE-MPL-2.0
new file mode 100644
index 00000000000..ee6256cdb62
--- /dev/null
+++ b/crates/futures/LICENSE-MPL-2.0
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/crates/test-macro/src/lib.rs b/crates/test-macro/src/lib.rs
index c9e27eb6f63..be58a20d603 100644
--- a/crates/test-macro/src/lib.rs
+++ b/crates/test-macro/src/lib.rs
@@ -98,7 +98,7 @@ pub fn wasm_bindgen_test(
             const _: () = {
                 #wasm_bindgen_path::__rt::wasm_bindgen::__wbindgen_coverage! {
                 #[export_name = ::core::concat!("__wbgt_", #ignore_name, "_", ::core::module_path!(), "::", ::core::stringify!(#ident))]
-                #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
+                #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))]
                 extern "C" fn __wbgt_test(cx: &#wasm_bindgen_path::__rt::Context) {
                     let test_name = ::core::concat!(::core::module_path!(), "::", ::core::stringify!(#ident));
                     #test_body
@@ -110,7 +110,7 @@ pub fn wasm_bindgen_test(
 
     if let Some(path) = attributes.unsupported {
         tokens.extend(
-            quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), #path)] },
+            quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))), #path)] },
         );
 
         if let Some(should_panic) = should_panic {
@@ -121,7 +121,7 @@ pub fn wasm_bindgen_test(
             };
 
             tokens.extend(
-                quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), #should_panic)] }
+                quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))), #should_panic)] }
             )
         }
 
@@ -133,7 +133,7 @@ pub fn wasm_bindgen_test(
             };
 
             tokens.extend(
-                quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))), #ignore)] }
+                quote! { #[cfg_attr(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))), #ignore)] }
             )
         }
     }
diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs
index 36ee18248c9..f8de117fc2e 100644
--- a/crates/test/src/lib.rs
+++ b/crates/test/src/lib.rs
@@ -106,6 +106,14 @@ macro_rules! wasm_bindgen_test_configure {
             $crate::wasm_bindgen_test_configure!($($others)*);
         };
     );
+    (run_in_emscripten $($others:tt)*) => (
+        const _: () = {
+            #[link_section = "__wasm_bindgen_test_unstable"]
+            #[cfg(target_arch = "wasm32")]
+            pub static __WBG_TEST_run_in_emscripten: [u8; 1] = [0x06];
+            $crate::wasm_bindgen_test_configure!($($others)*);
+        };
+    );
     () => ()
 }
 
diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml
index f32e53d59e2..701a2a16705 100644
--- a/crates/threads-xform/Cargo.toml
+++ b/crates/threads-xform/Cargo.toml
@@ -20,7 +20,7 @@ wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.
 
 [dev-dependencies]
 rayon = "1.0"
-wasmparser = "0.214"
+wasmparser = "0.227"
 wasmprinter = "0.214"
 wat = "1.0"
 
diff --git a/crates/wasm-conventions/Cargo.toml b/crates/wasm-conventions/Cargo.toml
index 3554087cbde..3bbf49f35b3 100644
--- a/crates/wasm-conventions/Cargo.toml
+++ b/crates/wasm-conventions/Cargo.toml
@@ -17,7 +17,7 @@ walrus = "0.23"
 # Matching the version `walrus` depends on.
 anyhow = "1.0"
 log = "0.4"
-wasmparser = "0.214"
+wasmparser = "0.227"
 
 [lints]
 workspace = true
diff --git a/crates/wasm-conventions/src/lib.rs b/crates/wasm-conventions/src/lib.rs
index beb0fe9da09..b7e8022bdb7 100755
--- a/crates/wasm-conventions/src/lib.rs
+++ b/crates/wasm-conventions/src/lib.rs
@@ -13,7 +13,7 @@ use walrus::{
     ir::Value, ConstExpr, ElementId, ElementItems, FunctionBuilder, FunctionId, FunctionKind,
     GlobalId, GlobalKind, MemoryId, Module, RawCustomSection, ValType,
 };
-use wasmparser::{BinaryReader, WasmFeatures};
+use wasmparser::BinaryReader;
 
 /// Get a Wasm module's canonical linear memory.
 pub fn get_memory(module: &Module) -> Result {
@@ -192,7 +192,7 @@ pub fn target_feature(module: &Module, feature: &str) -> Result {
             .as_any()
             .downcast_ref()
             .context("failed to read section")?;
-        let mut reader = BinaryReader::new(§ion.data, 0, WasmFeatures::default());
+        let mut reader = BinaryReader::new(§ion.data, 0);
         // The first integer contains the target feature count.
         let count = reader.read_var_u32()?;
 
@@ -237,7 +237,7 @@ pub fn insert_target_feature(module: &mut Module, new_feature: &str) -> Result<(
             .as_any_mut()
             .downcast_mut()
             .context("failed to read section")?;
-        let mut reader = BinaryReader::new(§ion.data, 0, WasmFeatures::default());
+        let mut reader = BinaryReader::new(§ion.data, 0);
         // The first integer contains the target feature count.
         let count = reader.read_var_u32()?;
 
diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml
index c3a73eaa363..24c73d8deaf 100644
--- a/crates/web-sys/Cargo.toml
+++ b/crates/web-sys/Cargo.toml
@@ -7,7 +7,7 @@ documentation = "https://rustwasm.github.io/wasm-bindgen/api/web_sys/"
 edition = "2021"
 homepage = "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html"
 include = ["/LICENSE-*", "/src"]
-license = "MIT OR Apache-2.0"
+license = "(MIT OR Apache-2.0) AND MPL-2.0 AND BSD-3-Clause"
 name = "web-sys"
 readme = "./README.md"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys"
diff --git a/crates/web-sys/LICENSE-BSD-3-Clause b/crates/web-sys/LICENSE-BSD-3-Clause
new file mode 100644
index 00000000000..db7d3cd120f
--- /dev/null
+++ b/crates/web-sys/LICENSE-BSD-3-Clause
@@ -0,0 +1,11 @@
+Copyright © WHATWG (Apple, Google, Mozilla, Microsoft).
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/crates/web-sys/LICENSE-MPL-2.0 b/crates/web-sys/LICENSE-MPL-2.0
new file mode 100644
index 00000000000..ee6256cdb62
--- /dev/null
+++ b/crates/web-sys/LICENSE-MPL-2.0
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/src/convert/impls.rs b/src/convert/impls.rs
index 44bd846f683..5cecbf69ca2 100644
--- a/src/convert/impls.rs
+++ b/src/convert/impls.rs
@@ -44,7 +44,7 @@ impl WasmAbi for i128 {
 
     #[inline]
     fn join(low: u64, high: u64, _: (), _: ()) -> Self {
-        ((high as u128) << 64 | low as u128) as i128
+        (((high as u128) << 64) | low as u128) as i128
     }
 }
 impl WasmAbi for u128 {
@@ -62,7 +62,7 @@ impl WasmAbi for u128 {
 
     #[inline]
     fn join(low: u64, high: u64, _: (), _: ()) -> Self {
-        (high as u128) << 64 | low as u128
+        ((high as u128) << 64) | low as u128
     }
 }
 
diff --git a/src/externref.rs b/src/externref.rs
index e575c12a537..79290f9a23d 100644
--- a/src/externref.rs
+++ b/src/externref.rs
@@ -106,7 +106,7 @@ fn internal_error(msg: &str) -> ! {
             std::process::abort();
         } else if #[cfg(all(
             target_arch = "wasm32",
-            any(target_os = "unknown", target_os = "none")
+            any(target_os = "unknown", target_os = "none", target_os = "emscripten")
         ))] {
             core::arch::wasm32::unreachable();
         } else {
diff --git a/src/lib.rs b/src/lib.rs
index 0c81941bc39..cd6743ffcd3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -75,14 +75,14 @@ use crate::convert::{FromWasmAbi, TryFromJsValue, WasmRet, WasmSlice};
 
 macro_rules! externs {
     ($(#[$attr:meta])* extern "C" { $(fn $name:ident($($args:tt)*) -> $ret:ty;)* }) => (
-        #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
+        #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten")))]
         $(#[$attr])*
         extern "C" {
             $(fn $name($($args)*) -> $ret;)*
         }
 
         $(
-            #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
+            #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none", target_os = "emscripten"))))]
             #[allow(unused_variables)]
             unsafe extern fn $name($($args)*) -> $ret {
                 panic!("function not implemented on non-wasm32 targets")
@@ -1408,7 +1408,10 @@ pub trait UnwrapThrowExt: Sized {
     #[cfg_attr(
         any(
             debug_assertions,
-            not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))
+            not(all(
+                target_arch = "wasm32",
+                any(target_os = "unknown", target_os = "none", target_os = "emscripten")
+            ))
         ),
         track_caller
     )]
@@ -1417,7 +1420,11 @@ pub trait UnwrapThrowExt: Sized {
             debug_assertions,
             all(
                 target_arch = "wasm32",
-                any(target_os = "unknown", target_os = "none")
+                any(
+                    target_os = "unknown",
+                    target_os = "none",
+                    target_os = "emscripten"
+                )
             )
         )) {
             let loc = core::panic::Location::caller();
@@ -1440,7 +1447,10 @@ pub trait UnwrapThrowExt: Sized {
     #[cfg_attr(
         any(
             debug_assertions,
-            not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))
+            not(all(
+                target_arch = "wasm32",
+                any(target_os = "unknown", target_os = "none", target_os = "emscripten")
+            ))
         ),
         track_caller
     )]
@@ -1453,7 +1463,11 @@ impl UnwrapThrowExt for Option {
 
         if cfg!(all(
             target_arch = "wasm32",
-            any(target_os = "unknown", target_os = "none")
+            any(
+                target_os = "unknown",
+                target_os = "none",
+                target_os = "emscripten"
+            )
         )) {
             if let Some(val) = self {
                 val
@@ -1474,7 +1488,11 @@ impl UnwrapThrowExt for Option {
     fn expect_throw(self, message: &str) -> T {
         if cfg!(all(
             target_arch = "wasm32",
-            any(target_os = "unknown", target_os = "none")
+            any(
+                target_os = "unknown",
+                target_os = "none",
+                target_os = "emscripten"
+            )
         )) {
             if let Some(val) = self {
                 val
@@ -1507,7 +1525,11 @@ where
 
         if cfg!(all(
             target_arch = "wasm32",
-            any(target_os = "unknown", target_os = "none")
+            any(
+                target_os = "unknown",
+                target_os = "none",
+                target_os = "emscripten"
+            )
         )) {
             match self {
                 Ok(val) => val,
@@ -1537,7 +1559,11 @@ where
     fn expect_throw(self, message: &str) -> T {
         if cfg!(all(
             target_arch = "wasm32",
-            any(target_os = "unknown", target_os = "none")
+            any(
+                target_os = "unknown",
+                target_os = "none",
+                target_os = "emscripten"
+            )
         )) {
             match self {
                 Ok(val) => val,
diff --git a/tests/headless/main.rs b/tests/headless/main.rs
index b5f6f12a0c7..79199af3db2 100644
--- a/tests/headless/main.rs
+++ b/tests/headless/main.rs
@@ -1,4 +1,4 @@
-#![cfg(target_arch = "wasm32")]
+#![cfg(all(target_arch = "wasm32", target_os = "unknown"))]
 
 extern crate wasm_bindgen;
 extern crate wasm_bindgen_test;
diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs
index b5640177fc1..325af948b2e 100644
--- a/tests/wasm/main.rs
+++ b/tests/wasm/main.rs
@@ -1,4 +1,4 @@
-#![cfg(target_arch = "wasm32")]
+#![cfg(all(target_arch = "wasm32", target_os = "unknown"))]
 #![allow(renamed_and_removed_lints)] // clippy::drop_ref will be renamed to drop_ref
 #![allow(clippy::drop_ref, clippy::drop_non_drop)]
 
diff --git a/tests/wasm32-emscripten/main.rs b/tests/wasm32-emscripten/main.rs
new file mode 100644
index 00000000000..8cc8059baeb
--- /dev/null
+++ b/tests/wasm32-emscripten/main.rs
@@ -0,0 +1,57 @@
+#![cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
+
+extern crate wasm_bindgen;
+extern crate wasm_bindgen_test;
+
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_test::*;
+
+wasm_bindgen_test_configure!(run_in_emscripten);
+
+#[wasm_bindgen]
+extern "C" {
+    fn setInterval(closure: &Closure, millis: u32) -> f64;
+    fn clearInterval(token: f64);
+
+    #[wasm_bindgen(js_namespace = console)]
+    fn log(s: &str);
+}
+
+#[wasm_bindgen]
+pub struct Interval {
+    closure: Closure,
+    token: f64,
+}
+
+impl Interval {
+    pub fn new(millis: u32, f: F) -> Interval
+    where
+        F: FnMut(),
+    {
+        // Construct a new closure.
+        let closure = Closure::new(f);
+
+        // Pass the closure to JS, to run every n milliseconds.
+        let token = setInterval(&closure, millis);
+
+        Interval { closure, token }
+    }
+}
+
+// When the Interval is destroyed, clear its `setInterval` timer.
+impl Drop for Interval {
+    fn drop(&mut self) {
+        clearInterval(self.token);
+    }
+}
+
+// Keep logging "hello" every second until the resulting `Interval` is dropped.
+#[wasm_bindgen]
+pub fn hello() -> Interval {
+    Interval::new(1_000, || log("hello"))
+}
+
+#[wasm_bindgen_test]
+fn hello_test() {
+    hello();
+}