Path: blob/main/crates/environ/src/fact/trampoline.rs
1692 views
//! Low-level compilation of an fused adapter function.1//!2//! This module is tasked with the top-level `compile` function which creates a3//! single WebAssembly function which will perform the steps of the fused4//! adapter for an `AdapterData` provided. This is the "meat" of compilation5//! where the validation of the canonical ABI or similar all happens to6//! translate arguments from one module to another.7//!8//! ## Traps and their ordering9//!10//! Currently this compiler is pretty "loose" about the ordering of precisely11//! what trap happens where. The main reason for this is that to core wasm all12//! traps are the same and for fused adapters if a trap happens no intermediate13//! side effects are visible (as designed by the canonical ABI itself). For this14//! it's important to note that some of the precise choices of control flow here15//! can be somewhat arbitrary, an intentional decision.1617use crate::component::{18CanonicalAbiInfo, ComponentTypesBuilder, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, FixedEncoding as FE,19FlatType, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, PREPARE_ASYNC_NO_RESULT,20PREPARE_ASYNC_WITH_RESULT, START_FLAG_ASYNC_CALLEE, StringEncoding, Transcode,21TypeComponentLocalErrorContextTableIndex, TypeEnumIndex, TypeFlagsIndex, TypeFutureTableIndex,22TypeListIndex, TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex, TypeResultIndex,23TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, VariantInfo,24};25use crate::fact::signature::Signature;26use crate::fact::transcode::Transcoder;27use crate::fact::traps::Trap;28use crate::fact::{29AdapterData, Body, Function, FunctionId, Helper, HelperLocation, HelperType,30LinearMemoryOptions, Module, Options,31};32use crate::prelude::*;33use crate::{FuncIndex, GlobalIndex};34use std::collections::HashMap;35use std::mem;36use std::ops::Range;37use wasm_encoder::{BlockType, Encode, Instruction, Instruction::*, MemArg, ValType};38use wasmtime_component_util::{DiscriminantSize, FlagsSize};3940use super::DataModel;4142const MAX_STRING_BYTE_LENGTH: u32 = 1 << 31;43const UTF16_TAG: u32 = 1 << 31;4445/// This value is arbitrarily chosen and should be fine to change at any time,46/// it just seemed like a halfway reasonable starting point.47const INITIAL_FUEL: usize = 1_000;4849struct Compiler<'a, 'b> {50types: &'a ComponentTypesBuilder,51module: &'b mut Module<'a>,52result: FunctionId,5354/// The encoded WebAssembly function body so far, not including locals.55code: Vec<u8>,5657/// Total number of locals generated so far.58nlocals: u32,5960/// Locals partitioned by type which are not currently in use.61free_locals: HashMap<ValType, Vec<u32>>,6263/// Metadata about all `unreachable` trap instructions in this function and64/// what the trap represents. The offset within `self.code` is recorded as65/// well.66traps: Vec<(usize, Trap)>,6768/// A heuristic which is intended to limit the size of a generated function69/// to a certain maximum to avoid generating arbitrarily large functions.70///71/// This fuel counter is decremented each time `translate` is called and72/// when fuel is entirely consumed further translations, if necessary, will73/// be done through calls to other functions in the module. This is intended74/// to be a heuristic to split up the main function into theoretically75/// reusable portions.76fuel: usize,7778/// Indicates whether an "enter call" should be emitted in the generated79/// function with a call to `Resource{Enter,Exit}Call` at the beginning and80/// end of the function for tracking of information related to borrowed81/// resources.82emit_resource_call: bool,83}8485pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) {86fn compiler<'a, 'b>(87module: &'b mut Module<'a>,88adapter: &AdapterData,89) -> (Compiler<'a, 'b>, Signature, Signature) {90let lower_sig = module.types.signature(&adapter.lower);91let lift_sig = module.types.signature(&adapter.lift);92let ty = module93.core_types94.function(&lower_sig.params, &lower_sig.results);95let result = module96.funcs97.push(Function::new(Some(adapter.name.clone()), ty));9899// If this type signature contains any borrowed resources then invocations100// of enter/exit call for resource-related metadata tracking must be used.101// It shouldn't matter whether the lower/lift signature is used here as both102// should return the same answer.103let emit_resource_call = module.types.contains_borrow_resource(&adapter.lower);104assert_eq!(105emit_resource_call,106module.types.contains_borrow_resource(&adapter.lift)107);108109(110Compiler::new(111module,112result,113lower_sig.params.len() as u32,114emit_resource_call,115),116lower_sig,117lift_sig,118)119}120121// This closure compiles a function to be exported to the host which host to122// lift the parameters from the caller and lower them to the callee.123//124// This allows the host to delay copying the parameters until the callee125// signals readiness by clearing its backpressure flag.126let async_start_adapter = |module: &mut Module| {127let sig = module128.types129.async_start_signature(&adapter.lower, &adapter.lift);130let ty = module.core_types.function(&sig.params, &sig.results);131let result = module.funcs.push(Function::new(132Some(format!("[async-start]{}", adapter.name)),133ty,134));135136Compiler::new(module, result, sig.params.len() as u32, false)137.compile_async_start_adapter(adapter, &sig);138139result140};141142// This closure compiles a function to be exported by the adapter module and143// called by the host to lift the results from the callee and lower them to144// the caller.145//146// Given that async-lifted exports return their results via the147// `task.return` intrinsic, the host will need to copy the results from148// callee to caller when that intrinsic is called rather than when the149// callee task fully completes (which may happen much later).150let async_return_adapter = |module: &mut Module| {151let sig = module152.types153.async_return_signature(&adapter.lower, &adapter.lift);154let ty = module.core_types.function(&sig.params, &sig.results);155let result = module.funcs.push(Function::new(156Some(format!("[async-return]{}", adapter.name)),157ty,158));159160Compiler::new(module, result, sig.params.len() as u32, false)161.compile_async_return_adapter(adapter, &sig);162163result164};165166match (adapter.lower.options.async_, adapter.lift.options.async_) {167(false, false) => {168// We can adapt sync->sync case with only minimal use of intrinsics,169// e.g. resource enter and exit calls as needed.170let (compiler, lower_sig, lift_sig) = compiler(module, adapter);171compiler.compile_sync_to_sync_adapter(adapter, &lower_sig, &lift_sig)172}173(true, true) => {174// In the async->async case, we must compile a couple of helper functions:175//176// - `async-start`: copies the parameters from the caller to the callee177// - `async-return`: copies the result from the callee to the caller178//179// Unlike synchronous calls, the above operations are asynchronous180// and subject to backpressure. If the callee is not yet ready to181// handle a new call, the `async-start` function will not be called182// immediately. Instead, control will return to the caller,183// allowing it to do other work while waiting for this call to make184// progress. Once the callee indicates it is ready, `async-start`185// will be called, and sometime later (possibly after various task186// switch events), when the callee has produced a result, it will187// call `async-return` via the `task.return` intrinsic, at which188// point a `STATUS_RETURNED` event will be delivered to the caller.189let start = async_start_adapter(module);190let return_ = async_return_adapter(module);191let (compiler, lower_sig, lift_sig) = compiler(module, adapter);192compiler.compile_async_to_async_adapter(193adapter,194start,195return_,196i32::try_from(lift_sig.params.len()).unwrap(),197&lower_sig,198);199}200(false, true) => {201// Like the async->async case above, for the sync->async case we202// also need `async-start` and `async-return` helper functions to203// allow the callee to asynchronously "pull" the parameters and204// "push" the results when it is ready.205//206// However, since the caller is using the synchronous ABI, the207// parameters may have been passed via the stack rather than linear208// memory. In that case, we pass them to the host to store in a209// task-local location temporarily in the case of backpressure.210// Similarly, the host will also temporarily store the results that211// the callee provides to `async-return` until it is ready to resume212// the caller.213let start = async_start_adapter(module);214let return_ = async_return_adapter(module);215let (compiler, lower_sig, lift_sig) = compiler(module, adapter);216compiler.compile_sync_to_async_adapter(217adapter,218start,219return_,220i32::try_from(lift_sig.params.len()).unwrap(),221&lower_sig,222);223}224(true, false) => {225// As with the async->async and sync->async cases above, for the226// async->sync case we use `async-start` and `async-return` helper227// functions. Here, those functions allow the host to enforce228// backpressure in the case where the callee instance already has229// another synchronous call in progress, in which case we can't230// start a new one until the current one (and any others already231// waiting in line behind it) has completed.232//233// In the case of backpressure, we'll return control to the caller234// immediately so it can do other work. Later, once the callee is235// ready, the host will call the `async-start` function to retrieve236// the parameters and pass them to the callee. At that point, the237// callee may block on a host call, at which point the host will238// suspend the fiber it is running on and allow the caller (or any239// other ready instance) to run concurrently with the blocked240// callee. Once the callee finally returns, the host will call the241// `async-return` function to write the result to the caller's242// linear memory and deliver a `STATUS_RETURNED` event to the243// caller.244let lift_sig = module.types.signature(&adapter.lift);245let start = async_start_adapter(module);246let return_ = async_return_adapter(module);247let (compiler, lower_sig, ..) = compiler(module, adapter);248compiler.compile_async_to_sync_adapter(249adapter,250start,251return_,252i32::try_from(lift_sig.params.len()).unwrap(),253i32::try_from(lift_sig.results.len()).unwrap(),254&lower_sig,255);256}257}258}259260/// Compiles a helper function as specified by the `Helper` configuration.261///262/// This function is invoked when the translation process runs out of fuel for263/// some prior function which enqueues a helper to get translated later. This264/// translation function will perform one type translation as specified by265/// `Helper` which can either be in the stack or memory for each side.266pub(super) fn compile_helper(module: &mut Module<'_>, result: FunctionId, helper: Helper) {267let mut nlocals = 0;268let src_flat;269let src = match helper.src.loc {270// If the source is on the stack then it's specified in the parameters271// to the function, so this creates the flattened representation and272// then lists those as the locals with appropriate types for the source273// values.274HelperLocation::Stack => {275src_flat = module276.types277.flatten_types(&helper.src.opts, usize::MAX, [helper.src.ty])278.unwrap()279.iter()280.enumerate()281.map(|(i, ty)| (i as u32, *ty))282.collect::<Vec<_>>();283nlocals += src_flat.len() as u32;284Source::Stack(Stack {285locals: &src_flat,286opts: &helper.src.opts,287})288}289// If the source is in memory then that's just propagated here as the290// first local is the pointer to the source.291HelperLocation::Memory => {292nlocals += 1;293Source::Memory(Memory {294opts: &helper.src.opts,295addr: TempLocal::new(0, helper.src.opts.data_model.unwrap_memory().ptr()),296offset: 0,297})298}299HelperLocation::StructField | HelperLocation::ArrayElement => todo!("CM+GC"),300};301let dst_flat;302let dst = match helper.dst.loc {303// This is the same as the stack-based source although `Destination` is304// configured slightly differently.305HelperLocation::Stack => {306dst_flat = module307.types308.flatten_types(&helper.dst.opts, usize::MAX, [helper.dst.ty])309.unwrap();310Destination::Stack(&dst_flat, &helper.dst.opts)311}312// This is the same as a memory-based source but note that the address313// of the destination is passed as the final parameter to the function.314HelperLocation::Memory => {315nlocals += 1;316Destination::Memory(Memory {317opts: &helper.dst.opts,318addr: TempLocal::new(319nlocals - 1,320helper.dst.opts.data_model.unwrap_memory().ptr(),321),322offset: 0,323})324}325HelperLocation::StructField | HelperLocation::ArrayElement => todo!("CM+GC"),326};327let mut compiler = Compiler {328types: module.types,329module,330code: Vec::new(),331nlocals,332free_locals: HashMap::new(),333traps: Vec::new(),334result,335fuel: INITIAL_FUEL,336// This is a helper function and only the top-level function is337// responsible for emitting these intrinsic calls.338emit_resource_call: false,339};340compiler.translate(&helper.src.ty, &src, &helper.dst.ty, &dst);341compiler.finish();342}343344/// Possible ways that a interface value is represented in the core wasm345/// canonical ABI.346enum Source<'a> {347/// This value is stored on the "stack" in wasm locals.348///349/// This could mean that it's inline from the parameters to the function or350/// that after a function call the results were stored in locals and the351/// locals are the inline results.352Stack(Stack<'a>),353354/// This value is stored in linear memory described by the `Memory`355/// structure.356Memory(Memory<'a>),357358/// This value is stored in a GC struct field described by the `GcStruct`359/// structure.360#[allow(dead_code, reason = "CM+GC is still WIP")]361Struct(GcStruct<'a>),362363/// This value is stored in a GC array element described by the `GcArray`364/// structure.365#[allow(dead_code, reason = "CM+GC is still WIP")]366Array(GcArray<'a>),367}368369/// Same as `Source` but for where values are translated into.370enum Destination<'a> {371/// This value is destined for the WebAssembly stack which means that372/// results are simply pushed as we go along.373///374/// The types listed are the types that are expected to be on the stack at375/// the end of translation.376Stack(&'a [ValType], &'a Options),377378/// This value is to be placed in linear memory described by `Memory`.379Memory(Memory<'a>),380381/// This value is to be placed in a GC struct field described by the382/// `GcStruct` structure.383#[allow(dead_code, reason = "CM+GC is still WIP")]384Struct(GcStruct<'a>),385386/// This value is to be placed in a GC array element described by the387/// `GcArray` structure.388#[allow(dead_code, reason = "CM+GC is still WIP")]389Array(GcArray<'a>),390}391392struct Stack<'a> {393/// The locals that comprise a particular value.394///395/// The length of this list represents the flattened list of types that make396/// up the component value. Each list has the index of the local being397/// accessed as well as the type of the local itself.398locals: &'a [(u32, ValType)],399/// The lifting/lowering options for where this stack of values comes from400opts: &'a Options,401}402403/// Representation of where a value is going to be stored in linear memory.404struct Memory<'a> {405/// The lifting/lowering options with memory configuration406opts: &'a Options,407/// The index of the local that contains the base address of where the408/// storage is happening.409addr: TempLocal,410/// A "static" offset that will be baked into wasm instructions for where411/// memory loads/stores happen.412offset: u32,413}414415impl<'a> Memory<'a> {416fn mem_opts(&self) -> &'a LinearMemoryOptions {417self.opts.data_model.unwrap_memory()418}419}420421/// Representation of where a value is coming from or going to in a GC struct.422struct GcStruct<'a> {423opts: &'a Options,424// TODO: more fields to come in the future.425}426427/// Representation of where a value is coming from or going to in a GC array.428struct GcArray<'a> {429opts: &'a Options,430// TODO: more fields to come in the future.431}432433impl<'a, 'b> Compiler<'a, 'b> {434fn new(435module: &'b mut Module<'a>,436result: FunctionId,437nlocals: u32,438emit_resource_call: bool,439) -> Self {440Self {441types: module.types,442module,443result,444code: Vec::new(),445nlocals,446free_locals: HashMap::new(),447traps: Vec::new(),448fuel: INITIAL_FUEL,449emit_resource_call,450}451}452453/// Compile an adapter function supporting an async-lowered import to an454/// async-lifted export.455///456/// This uses a pair of `async-prepare` and `async-start` built-in functions457/// to set up and start a subtask, respectively. `async-prepare` accepts458/// `start` and `return_` functions which copy the parameters and results,459/// respectively; the host will call the former when the callee has cleared460/// its backpressure flag and the latter when the callee has called461/// `task.return`.462fn compile_async_to_async_adapter(463mut self,464adapter: &AdapterData,465start: FunctionId,466return_: FunctionId,467param_count: i32,468lower_sig: &Signature,469) {470let start_call =471self.module472.import_async_start_call(&adapter.name, adapter.lift.options.callback, None);473474self.call_prepare(adapter, start, return_, lower_sig, false);475476// TODO: As an optimization, consider checking the backpressure flag on477// the callee instance and, if it's unset _and_ the callee uses a478// callback, translate the params and call the callee function directly479// here (and make sure `start_call` knows _not_ to call it in that case).480481// We export this function so we can pass a funcref to the host.482//483// TODO: Use a declarative element segment instead of exporting this.484self.module.exports.push((485adapter.callee.as_u32(),486format!("[adapter-callee]{}", adapter.name),487));488489self.instruction(RefFunc(adapter.callee.as_u32()));490self.instruction(I32Const(param_count));491// The result count for an async callee is either one (if there's a492// callback) or zero (if there's no callback). We conservatively use493// one here to ensure the host provides room for the result, if any.494self.instruction(I32Const(1));495self.instruction(I32Const(START_FLAG_ASYNC_CALLEE));496self.instruction(Call(start_call.as_u32()));497498self.finish()499}500501/// Invokes the `prepare_call` builtin with the provided parameters for this502/// adapter.503///504/// This is part of a async lower and/or async lift adapter. This is not505/// used for a sync->sync function call. This is done to create the task on506/// the host side of the runtime and such. This will notably invoke a507/// Cranelift builtin which will spill all wasm-level parameters to the508/// stack to handle variadic signatures.509///510/// Note that the `prepare_sync` parameter here configures the511/// `result_count_or_max_if_async` parameter to indicate whether this is a512/// sync or async prepare.513fn call_prepare(514&mut self,515adapter: &AdapterData,516start: FunctionId,517return_: FunctionId,518lower_sig: &Signature,519prepare_sync: bool,520) {521let prepare = self.module.import_prepare_call(522&adapter.name,523&lower_sig.params,524match adapter.lift.options.data_model {525DataModel::Gc {} => todo!("CM+GC"),526DataModel::LinearMemory(LinearMemoryOptions { memory, .. }) => memory,527},528);529530self.flush_code();531self.module.funcs[self.result]532.body533.push(Body::RefFunc(start));534self.module.funcs[self.result]535.body536.push(Body::RefFunc(return_));537self.instruction(I32Const(538i32::try_from(adapter.lower.instance.as_u32()).unwrap(),539));540self.instruction(I32Const(541i32::try_from(adapter.lift.instance.as_u32()).unwrap(),542));543self.instruction(I32Const(544i32::try_from(self.types[adapter.lift.ty].results.as_u32()).unwrap(),545));546self.instruction(I32Const(i32::from(547adapter.lift.options.string_encoding as u8,548)));549550// flag this as a preparation for either an async call or sync call,551// depending on `prepare_sync`552let result_types = &self.types[self.types[adapter.lower.ty].results].types;553if prepare_sync {554self.instruction(I32Const(555i32::try_from(556self.types557.flatten_types(558&adapter.lower.options,559usize::MAX,560result_types.iter().copied(),561)562.map(|v| v.len())563.unwrap_or(usize::try_from(i32::MAX).unwrap()),564)565.unwrap(),566));567} else {568if result_types.len() > 0 {569self.instruction(I32Const(PREPARE_ASYNC_WITH_RESULT.cast_signed()));570} else {571self.instruction(I32Const(PREPARE_ASYNC_NO_RESULT.cast_signed()));572}573}574575// forward all our own arguments on to the host stub576for index in 0..lower_sig.params.len() {577self.instruction(LocalGet(u32::try_from(index).unwrap()));578}579self.instruction(Call(prepare.as_u32()));580}581582/// Compile an adapter function supporting a sync-lowered import to an583/// async-lifted export.584///585/// This uses a pair of `sync-prepare` and `sync-start` built-in functions586/// to set up and start a subtask, respectively. `sync-prepare` accepts587/// `start` and `return_` functions which copy the parameters and results,588/// respectively; the host will call the former when the callee has cleared589/// its backpressure flag and the latter when the callee has called590/// `task.return`.591fn compile_sync_to_async_adapter(592mut self,593adapter: &AdapterData,594start: FunctionId,595return_: FunctionId,596lift_param_count: i32,597lower_sig: &Signature,598) {599let start_call = self.module.import_sync_start_call(600&adapter.name,601adapter.lift.options.callback,602&lower_sig.results,603);604605self.call_prepare(adapter, start, return_, lower_sig, true);606607// TODO: As an optimization, consider checking the backpressure flag on608// the callee instance and, if it's unset _and_ the callee uses a609// callback, translate the params and call the callee function directly610// here (and make sure `start_call` knows _not_ to call it in that case).611612// We export this function so we can pass a funcref to the host.613//614// TODO: Use a declarative element segment instead of exporting this.615self.module.exports.push((616adapter.callee.as_u32(),617format!("[adapter-callee]{}", adapter.name),618));619620self.instruction(RefFunc(adapter.callee.as_u32()));621self.instruction(I32Const(lift_param_count));622self.instruction(Call(start_call.as_u32()));623624self.finish()625}626627/// Compile an adapter function supporting an async-lowered import to a628/// sync-lifted export.629///630/// This uses a pair of `async-prepare` and `async-start` built-in functions631/// to set up and start a subtask, respectively. `async-prepare` accepts632/// `start` and `return_` functions which copy the parameters and results,633/// respectively; the host will call the former when the callee has cleared634/// its backpressure flag and the latter when the callee has returned its635/// result(s).636fn compile_async_to_sync_adapter(637mut self,638adapter: &AdapterData,639start: FunctionId,640return_: FunctionId,641param_count: i32,642result_count: i32,643lower_sig: &Signature,644) {645let start_call =646self.module647.import_async_start_call(&adapter.name, None, adapter.lift.post_return);648649self.call_prepare(adapter, start, return_, lower_sig, false);650651// We export this function so we can pass a funcref to the host.652//653// TODO: Use a declarative element segment instead of exporting this.654self.module.exports.push((655adapter.callee.as_u32(),656format!("[adapter-callee]{}", adapter.name),657));658659self.instruction(RefFunc(adapter.callee.as_u32()));660self.instruction(I32Const(param_count));661self.instruction(I32Const(result_count));662self.instruction(I32Const(0));663self.instruction(Call(start_call.as_u32()));664665self.finish()666}667668/// Compiles a function to be exported to the host which host to lift the669/// parameters from the caller and lower them to the callee.670///671/// This allows the host to delay copying the parameters until the callee672/// signals readiness by clearing its backpressure flag.673fn compile_async_start_adapter(mut self, adapter: &AdapterData, sig: &Signature) {674let param_locals = sig675.params676.iter()677.enumerate()678.map(|(i, ty)| (i as u32, *ty))679.collect::<Vec<_>>();680681self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, false);682self.translate_params(adapter, ¶m_locals);683self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, true);684685self.finish();686}687688/// Compiles a function to be exported by the adapter module and called by689/// the host to lift the results from the callee and lower them to the690/// caller.691///692/// Given that async-lifted exports return their results via the693/// `task.return` intrinsic, the host will need to copy the results from694/// callee to caller when that intrinsic is called rather than when the695/// callee task fully completes (which may happen much later).696fn compile_async_return_adapter(mut self, adapter: &AdapterData, sig: &Signature) {697let param_locals = sig698.params699.iter()700.enumerate()701.map(|(i, ty)| (i as u32, *ty))702.collect::<Vec<_>>();703704self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, false);705// Note that we pass `param_locals` as _both_ the `param_locals` and706// `result_locals` parameters to `translate_results`. That's because707// the _parameters_ to `task.return` are actually the _results_ that the708// caller is waiting for.709//710// Additionally, the host will append a return711// pointer to the end of that list before calling this adapter's712// `async-return` function if the results exceed `MAX_FLAT_RESULTS` or713// the import is lowered async, in which case `translate_results` will714// use that pointer to store the results.715self.translate_results(adapter, ¶m_locals, ¶m_locals);716self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, true);717718self.finish()719}720721/// Compile an adapter function supporting a sync-lowered import to a722/// sync-lifted export.723///724/// Unlike calls involving async-lowered imports or async-lifted exports,725/// this adapter need not involve host built-ins except possibly for726/// resource bookkeeping.727fn compile_sync_to_sync_adapter(728mut self,729adapter: &AdapterData,730lower_sig: &Signature,731lift_sig: &Signature,732) {733// Check the instance flags required for this trampoline.734//735// This inserts the initial check required by `canon_lower` that the736// caller instance can be left and additionally checks the737// flags on the callee if necessary whether it can be entered.738self.trap_if_not_flag(adapter.lower.flags, FLAG_MAY_LEAVE, Trap::CannotLeave);739if adapter.called_as_export {740self.trap_if_not_flag(adapter.lift.flags, FLAG_MAY_ENTER, Trap::CannotEnter);741self.set_flag(adapter.lift.flags, FLAG_MAY_ENTER, false);742} else if self.module.debug {743self.assert_not_flag(744adapter.lift.flags,745FLAG_MAY_ENTER,746"may_enter should be unset",747);748}749750if self.emit_resource_call {751let enter = self.module.import_resource_enter_call();752self.instruction(Call(enter.as_u32()));753}754755// Perform the translation of arguments. Note that `FLAG_MAY_LEAVE` is756// cleared around this invocation for the callee as per the757// `canon_lift` definition in the spec. Additionally note that the758// precise ordering of traps here is not required since internal state759// is not visible to either instance and a trap will "lock down" both760// instances to no longer be visible. This means that we're free to761// reorder lifts/lowers and flags and such as is necessary and762// convenient here.763//764// TODO: if translation doesn't actually call any functions in either765// instance then there's no need to set/clear the flag here and that can766// be optimized away.767self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, false);768let param_locals = lower_sig769.params770.iter()771.enumerate()772.map(|(i, ty)| (i as u32, *ty))773.collect::<Vec<_>>();774self.translate_params(adapter, ¶m_locals);775self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, true);776777// With all the arguments on the stack the actual target function is778// now invoked. The core wasm results of the function are then placed779// into locals for result translation afterwards.780self.instruction(Call(adapter.callee.as_u32()));781let mut result_locals = Vec::with_capacity(lift_sig.results.len());782let mut temps = Vec::new();783for ty in lift_sig.results.iter().rev() {784let local = self.local_set_new_tmp(*ty);785result_locals.push((local.idx, *ty));786temps.push(local);787}788result_locals.reverse();789790// Like above during the translation of results the caller cannot be791// left (as we might invoke things like `realloc`). Again the precise792// order of everything doesn't matter since intermediate states cannot793// be witnessed, hence the setting of flags here to encapsulate both794// liftings and lowerings.795//796// TODO: like above the management of the `MAY_LEAVE` flag can probably797// be elided here for "simple" results.798self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, false);799self.translate_results(adapter, ¶m_locals, &result_locals);800self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, true);801802// And finally post-return state is handled here once all results/etc803// are all translated.804if let Some(func) = adapter.lift.post_return {805for (result, _) in result_locals.iter() {806self.instruction(LocalGet(*result));807}808self.instruction(Call(func.as_u32()));809}810if adapter.called_as_export {811self.set_flag(adapter.lift.flags, FLAG_MAY_ENTER, true);812}813814for tmp in temps {815self.free_temp_local(tmp);816}817818if self.emit_resource_call {819let exit = self.module.import_resource_exit_call();820self.instruction(Call(exit.as_u32()));821}822823self.finish()824}825826fn translate_params(&mut self, adapter: &AdapterData, param_locals: &[(u32, ValType)]) {827let src_tys = self.types[adapter.lower.ty].params;828let src_tys = self.types[src_tys]829.types830.iter()831.copied()832.collect::<Vec<_>>();833let dst_tys = self.types[adapter.lift.ty].params;834let dst_tys = self.types[dst_tys]835.types836.iter()837.copied()838.collect::<Vec<_>>();839let lift_opts = &adapter.lift.options;840let lower_opts = &adapter.lower.options;841842// TODO: handle subtyping843assert_eq!(src_tys.len(), dst_tys.len());844845// Async lowered functions have a smaller limit on flat parameters, but846// their destination, a lifted function, does not have a different limit847// than sync functions.848let max_flat_params = if adapter.lower.options.async_ {849MAX_FLAT_ASYNC_PARAMS850} else {851MAX_FLAT_PARAMS852};853let src_flat =854self.types855.flatten_types(lower_opts, max_flat_params, src_tys.iter().copied());856let dst_flat =857self.types858.flatten_types(lift_opts, MAX_FLAT_PARAMS, dst_tys.iter().copied());859860let src = if let Some(flat) = &src_flat {861Source::Stack(Stack {862locals: ¶m_locals[..flat.len()],863opts: lower_opts,864})865} else {866// If there are too many parameters then that means the parameters867// are actually a tuple stored in linear memory addressed by the868// first parameter local.869let lower_mem_opts = lower_opts.data_model.unwrap_memory();870let (addr, ty) = param_locals[0];871assert_eq!(ty, lower_mem_opts.ptr());872let align = src_tys873.iter()874.map(|t| self.types.align(lower_mem_opts, t))875.max()876.unwrap_or(1);877Source::Memory(self.memory_operand(lower_opts, TempLocal::new(addr, ty), align))878};879880let dst = if let Some(flat) = &dst_flat {881Destination::Stack(flat, lift_opts)882} else {883let abi = CanonicalAbiInfo::record(dst_tys.iter().map(|t| self.types.canonical_abi(t)));884match lift_opts.data_model {885DataModel::Gc {} => todo!("CM+GC"),886DataModel::LinearMemory(LinearMemoryOptions { memory64, .. }) => {887let (size, align) = if memory64 {888(abi.size64, abi.align64)889} else {890(abi.size32, abi.align32)891};892893// If there are too many parameters then space is allocated in the894// destination module for the parameters via its `realloc` function.895let size = MallocSize::Const(size);896Destination::Memory(self.malloc(lift_opts, size, align))897}898}899};900901let srcs = src902.record_field_srcs(self.types, src_tys.iter().copied())903.zip(src_tys.iter());904let dsts = dst905.record_field_dsts(self.types, dst_tys.iter().copied())906.zip(dst_tys.iter());907for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) {908self.translate(&src_ty, &src, &dst_ty, &dst);909}910911// If the destination was linear memory instead of the stack then the912// actual parameter that we're passing is the address of the values913// stored, so ensure that's happening in the wasm body here.914if let Destination::Memory(mem) = dst {915self.instruction(LocalGet(mem.addr.idx));916self.free_temp_local(mem.addr);917}918}919920fn translate_results(921&mut self,922adapter: &AdapterData,923param_locals: &[(u32, ValType)],924result_locals: &[(u32, ValType)],925) {926let src_tys = self.types[adapter.lift.ty].results;927let src_tys = self.types[src_tys]928.types929.iter()930.copied()931.collect::<Vec<_>>();932let dst_tys = self.types[adapter.lower.ty].results;933let dst_tys = self.types[dst_tys]934.types935.iter()936.copied()937.collect::<Vec<_>>();938let lift_opts = &adapter.lift.options;939let lower_opts = &adapter.lower.options;940941let src_flat = self942.types943.flatten_lifting_types(lift_opts, src_tys.iter().copied());944let dst_flat = self945.types946.flatten_lowering_types(lower_opts, dst_tys.iter().copied());947948let src = if src_flat.is_some() {949Source::Stack(Stack {950locals: result_locals,951opts: lift_opts,952})953} else {954// The original results to read from in this case come from the955// return value of the function itself. The imported function will956// return a linear memory address at which the values can be read957// from.958let lift_mem_opts = lift_opts.data_model.unwrap_memory();959let align = src_tys960.iter()961.map(|t| self.types.align(lift_mem_opts, t))962.max()963.unwrap_or(1);964assert_eq!(965result_locals.len(),966if lower_opts.async_ || lift_opts.async_ {9672968} else {9691970}971);972let (addr, ty) = result_locals[0];973assert_eq!(ty, lift_opts.data_model.unwrap_memory().ptr());974Source::Memory(self.memory_operand(lift_opts, TempLocal::new(addr, ty), align))975};976977let dst = if let Some(flat) = &dst_flat {978Destination::Stack(flat, lower_opts)979} else {980// This is slightly different than `translate_params` where the981// return pointer was provided by the caller of this function982// meaning the last parameter local is a pointer into linear memory.983let lower_mem_opts = lower_opts.data_model.unwrap_memory();984let align = dst_tys985.iter()986.map(|t| self.types.align(lower_mem_opts, t))987.max()988.unwrap_or(1);989let (addr, ty) = *param_locals.last().expect("no retptr");990assert_eq!(ty, lower_opts.data_model.unwrap_memory().ptr());991Destination::Memory(self.memory_operand(lower_opts, TempLocal::new(addr, ty), align))992};993994let srcs = src995.record_field_srcs(self.types, src_tys.iter().copied())996.zip(src_tys.iter());997let dsts = dst998.record_field_dsts(self.types, dst_tys.iter().copied())999.zip(dst_tys.iter());1000for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) {1001self.translate(&src_ty, &src, &dst_ty, &dst);1002}1003}10041005fn translate(1006&mut self,1007src_ty: &InterfaceType,1008src: &Source<'_>,1009dst_ty: &InterfaceType,1010dst: &Destination,1011) {1012if let Source::Memory(mem) = src {1013self.assert_aligned(src_ty, mem);1014}1015if let Destination::Memory(mem) = dst {1016self.assert_aligned(dst_ty, mem);1017}10181019// Calculate a cost heuristic for what the translation of this specific1020// layer of the type is going to incur. The purpose of this cost is that1021// we'll deduct it from `self.fuel` and if no fuel is remaining then1022// translation is outlined into a separate function rather than being1023// translated into this function.1024//1025// The general goal is to avoid creating an exponentially sized function1026// for a linearly sized input (the type section). By outlining helper1027// functions there will ideally be a constant set of helper functions1028// per type (to accommodate in-memory or on-stack transfers as well as1029// src/dst options) which means that each function is at most a certain1030// size and we have a linear number of functions which should guarantee1031// an overall linear size of the output.1032//1033// To implement this the current heuristic is that each layer of1034// translating a type has a cost associated with it and this cost is1035// accounted for in `self.fuel`. Some conversions are considered free as1036// they generate basically as much code as the `call` to the translation1037// function while other are considered proportionally expensive to the1038// size of the type. The hope is that some upper layers are of a type's1039// translation are all inlined into one function but bottom layers end1040// up getting outlined to separate functions. Theoretically, again this1041// is built on hopes and dreams, the outlining can be shared amongst1042// tightly-intertwined type hierarchies which will reduce the size of1043// the output module due to the helpers being used.1044//1045// This heuristic of how to split functions has changed a few times in1046// the past and this isn't necessarily guaranteed to be the final1047// iteration.1048let cost = match src_ty {1049// These types are all quite simple to load/store and equate to1050// basically the same cost of the `call` instruction to call an1051// out-of-line translation function, so give them 0 cost.1052InterfaceType::Bool1053| InterfaceType::U81054| InterfaceType::S81055| InterfaceType::U161056| InterfaceType::S161057| InterfaceType::U321058| InterfaceType::S321059| InterfaceType::U641060| InterfaceType::S641061| InterfaceType::Float321062| InterfaceType::Float64 => 0,10631064// This has a small amount of validation associated with it, so1065// give it a cost of 1.1066InterfaceType::Char => 1,10671068// This has a fair bit of code behind it depending on the1069// strings/encodings in play, so arbitrarily assign it this cost.1070InterfaceType::String => 40,10711072// Iteration of a loop is along the lines of the cost of a string1073// so give it the same cost1074InterfaceType::List(_) => 40,10751076InterfaceType::Flags(i) => {1077let count = self.module.types[*i].names.len();1078match FlagsSize::from_count(count) {1079FlagsSize::Size0 => 0,1080FlagsSize::Size1 | FlagsSize::Size2 => 1,1081FlagsSize::Size4Plus(n) => n.into(),1082}1083}10841085InterfaceType::Record(i) => self.types[*i].fields.len(),1086InterfaceType::Tuple(i) => self.types[*i].types.len(),1087InterfaceType::Variant(i) => self.types[*i].cases.len(),1088InterfaceType::Enum(i) => self.types[*i].names.len(),10891090// 2 cases to consider for each of these variants.1091InterfaceType::Option(_) | InterfaceType::Result(_) => 2,10921093// TODO(#6696) - something nonzero, is 1 right?1094InterfaceType::Own(_)1095| InterfaceType::Borrow(_)1096| InterfaceType::Future(_)1097| InterfaceType::Stream(_)1098| InterfaceType::ErrorContext(_) => 1,1099};11001101match self.fuel.checked_sub(cost) {1102// This function has enough fuel to perform the layer of translation1103// necessary for this type, so the fuel is updated in-place and1104// translation continues. Note that the recursion here is bounded by1105// the static recursion limit for all interface types as imposed1106// during the translation phase.1107Some(n) => {1108self.fuel = n;1109match src_ty {1110InterfaceType::Bool => self.translate_bool(src, dst_ty, dst),1111InterfaceType::U8 => self.translate_u8(src, dst_ty, dst),1112InterfaceType::S8 => self.translate_s8(src, dst_ty, dst),1113InterfaceType::U16 => self.translate_u16(src, dst_ty, dst),1114InterfaceType::S16 => self.translate_s16(src, dst_ty, dst),1115InterfaceType::U32 => self.translate_u32(src, dst_ty, dst),1116InterfaceType::S32 => self.translate_s32(src, dst_ty, dst),1117InterfaceType::U64 => self.translate_u64(src, dst_ty, dst),1118InterfaceType::S64 => self.translate_s64(src, dst_ty, dst),1119InterfaceType::Float32 => self.translate_f32(src, dst_ty, dst),1120InterfaceType::Float64 => self.translate_f64(src, dst_ty, dst),1121InterfaceType::Char => self.translate_char(src, dst_ty, dst),1122InterfaceType::String => self.translate_string(src, dst_ty, dst),1123InterfaceType::List(t) => self.translate_list(*t, src, dst_ty, dst),1124InterfaceType::Record(t) => self.translate_record(*t, src, dst_ty, dst),1125InterfaceType::Flags(f) => self.translate_flags(*f, src, dst_ty, dst),1126InterfaceType::Tuple(t) => self.translate_tuple(*t, src, dst_ty, dst),1127InterfaceType::Variant(v) => self.translate_variant(*v, src, dst_ty, dst),1128InterfaceType::Enum(t) => self.translate_enum(*t, src, dst_ty, dst),1129InterfaceType::Option(t) => self.translate_option(*t, src, dst_ty, dst),1130InterfaceType::Result(t) => self.translate_result(*t, src, dst_ty, dst),1131InterfaceType::Own(t) => self.translate_own(*t, src, dst_ty, dst),1132InterfaceType::Borrow(t) => self.translate_borrow(*t, src, dst_ty, dst),1133InterfaceType::Future(t) => self.translate_future(*t, src, dst_ty, dst),1134InterfaceType::Stream(t) => self.translate_stream(*t, src, dst_ty, dst),1135InterfaceType::ErrorContext(t) => {1136self.translate_error_context(*t, src, dst_ty, dst)1137}1138}1139}11401141// This function does not have enough fuel left to perform this1142// layer of translation so the translation is deferred to a helper1143// function. The actual translation here is then done by marshalling1144// the src/dst into the function we're calling and then processing1145// the results.1146None => {1147let src_loc = match src {1148// If the source is on the stack then `stack_get` is used to1149// convert everything to the appropriate flat representation1150// for the source type.1151Source::Stack(stack) => {1152for (i, ty) in stack1153.opts1154.flat_types(src_ty, self.types)1155.unwrap()1156.iter()1157.enumerate()1158{1159let stack = stack.slice(i..i + 1);1160self.stack_get(&stack, (*ty).into());1161}1162HelperLocation::Stack1163}1164// If the source is in memory then the pointer is passed1165// through, but note that the offset must be factored in1166// here since the translation function will start from1167// offset 0.1168Source::Memory(mem) => {1169self.push_mem_addr(mem);1170HelperLocation::Memory1171}1172Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1173};1174let dst_loc = match dst {1175Destination::Stack(..) => HelperLocation::Stack,1176Destination::Memory(mem) => {1177self.push_mem_addr(mem);1178HelperLocation::Memory1179}1180Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1181};1182// Generate a `FunctionId` corresponding to the `Helper`1183// configuration that is necessary here. This will ideally be a1184// "cache hit" and use a preexisting helper which represents1185// outlining what would otherwise be duplicate code within a1186// function to one function.1187let helper = self.module.translate_helper(Helper {1188src: HelperType {1189ty: *src_ty,1190opts: *src.opts(),1191loc: src_loc,1192},1193dst: HelperType {1194ty: *dst_ty,1195opts: *dst.opts(),1196loc: dst_loc,1197},1198});1199// Emit a `call` instruction which will get "relocated" to a1200// function index once translation has completely finished.1201self.flush_code();1202self.module.funcs[self.result].body.push(Body::Call(helper));12031204// If the destination of the translation was on the stack then1205// the types on the stack need to be optionally converted to1206// different types (e.g. if the result here is part of a variant1207// somewhere else).1208//1209// This translation happens inline here by popping the results1210// into new locals and then using those locals to do a1211// `stack_set`.1212if let Destination::Stack(tys, opts) = dst {1213let flat = self1214.types1215.flatten_types(opts, usize::MAX, [*dst_ty])1216.unwrap();1217assert_eq!(flat.len(), tys.len());1218let locals = flat1219.iter()1220.rev()1221.map(|ty| self.local_set_new_tmp(*ty))1222.collect::<Vec<_>>();1223for (ty, local) in tys.iter().zip(locals.into_iter().rev()) {1224self.instruction(LocalGet(local.idx));1225self.stack_set(std::slice::from_ref(ty), local.ty);1226self.free_temp_local(local);1227}1228}1229}1230}1231}12321233fn push_mem_addr(&mut self, mem: &Memory<'_>) {1234self.instruction(LocalGet(mem.addr.idx));1235if mem.offset != 0 {1236self.ptr_uconst(mem.mem_opts(), mem.offset);1237self.ptr_add(mem.mem_opts());1238}1239}12401241fn translate_bool(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1242// TODO: subtyping1243assert!(matches!(dst_ty, InterfaceType::Bool));1244self.push_dst_addr(dst);12451246// Booleans are canonicalized to 0 or 1 as they pass through the1247// component boundary, so use a `select` instruction to do so.1248self.instruction(I32Const(1));1249self.instruction(I32Const(0));1250match src {1251Source::Memory(mem) => self.i32_load8u(mem),1252Source::Stack(stack) => self.stack_get(stack, ValType::I32),1253Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1254}1255self.instruction(Select);12561257match dst {1258Destination::Memory(mem) => self.i32_store8(mem),1259Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1260Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1261}1262}12631264fn translate_u8(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1265// TODO: subtyping1266assert!(matches!(dst_ty, InterfaceType::U8));1267self.convert_u8_mask(src, dst, 0xff);1268}12691270fn convert_u8_mask(&mut self, src: &Source<'_>, dst: &Destination<'_>, mask: u8) {1271self.push_dst_addr(dst);1272let mut needs_mask = true;1273match src {1274Source::Memory(mem) => {1275self.i32_load8u(mem);1276needs_mask = mask != 0xff;1277}1278Source::Stack(stack) => {1279self.stack_get(stack, ValType::I32);1280}1281Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1282}1283if needs_mask {1284self.instruction(I32Const(i32::from(mask)));1285self.instruction(I32And);1286}1287match dst {1288Destination::Memory(mem) => self.i32_store8(mem),1289Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1290Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1291}1292}12931294fn translate_s8(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1295// TODO: subtyping1296assert!(matches!(dst_ty, InterfaceType::S8));1297self.push_dst_addr(dst);1298match src {1299Source::Memory(mem) => self.i32_load8s(mem),1300Source::Stack(stack) => {1301self.stack_get(stack, ValType::I32);1302self.instruction(I32Extend8S);1303}1304Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1305}1306match dst {1307Destination::Memory(mem) => self.i32_store8(mem),1308Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1309Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1310}1311}13121313fn translate_u16(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1314// TODO: subtyping1315assert!(matches!(dst_ty, InterfaceType::U16));1316self.convert_u16_mask(src, dst, 0xffff);1317}13181319fn convert_u16_mask(&mut self, src: &Source<'_>, dst: &Destination<'_>, mask: u16) {1320self.push_dst_addr(dst);1321let mut needs_mask = true;1322match src {1323Source::Memory(mem) => {1324self.i32_load16u(mem);1325needs_mask = mask != 0xffff;1326}1327Source::Stack(stack) => {1328self.stack_get(stack, ValType::I32);1329}1330Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1331}1332if needs_mask {1333self.instruction(I32Const(i32::from(mask)));1334self.instruction(I32And);1335}1336match dst {1337Destination::Memory(mem) => self.i32_store16(mem),1338Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1339Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1340}1341}13421343fn translate_s16(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1344// TODO: subtyping1345assert!(matches!(dst_ty, InterfaceType::S16));1346self.push_dst_addr(dst);1347match src {1348Source::Memory(mem) => self.i32_load16s(mem),1349Source::Stack(stack) => {1350self.stack_get(stack, ValType::I32);1351self.instruction(I32Extend16S);1352}1353Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1354}1355match dst {1356Destination::Memory(mem) => self.i32_store16(mem),1357Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1358Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1359}1360}13611362fn translate_u32(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1363// TODO: subtyping1364assert!(matches!(dst_ty, InterfaceType::U32));1365self.convert_u32_mask(src, dst, 0xffffffff)1366}13671368fn convert_u32_mask(&mut self, src: &Source<'_>, dst: &Destination<'_>, mask: u32) {1369self.push_dst_addr(dst);1370match src {1371Source::Memory(mem) => self.i32_load(mem),1372Source::Stack(stack) => self.stack_get(stack, ValType::I32),1373Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1374}1375if mask != 0xffffffff {1376self.instruction(I32Const(mask as i32));1377self.instruction(I32And);1378}1379match dst {1380Destination::Memory(mem) => self.i32_store(mem),1381Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1382Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1383}1384}13851386fn translate_s32(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1387// TODO: subtyping1388assert!(matches!(dst_ty, InterfaceType::S32));1389self.push_dst_addr(dst);1390match src {1391Source::Memory(mem) => self.i32_load(mem),1392Source::Stack(stack) => self.stack_get(stack, ValType::I32),1393Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1394}1395match dst {1396Destination::Memory(mem) => self.i32_store(mem),1397Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1398Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1399}1400}14011402fn translate_u64(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1403// TODO: subtyping1404assert!(matches!(dst_ty, InterfaceType::U64));1405self.push_dst_addr(dst);1406match src {1407Source::Memory(mem) => self.i64_load(mem),1408Source::Stack(stack) => self.stack_get(stack, ValType::I64),1409Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1410}1411match dst {1412Destination::Memory(mem) => self.i64_store(mem),1413Destination::Stack(stack, _) => self.stack_set(stack, ValType::I64),1414Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1415}1416}14171418fn translate_s64(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1419// TODO: subtyping1420assert!(matches!(dst_ty, InterfaceType::S64));1421self.push_dst_addr(dst);1422match src {1423Source::Memory(mem) => self.i64_load(mem),1424Source::Stack(stack) => self.stack_get(stack, ValType::I64),1425Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1426}1427match dst {1428Destination::Memory(mem) => self.i64_store(mem),1429Destination::Stack(stack, _) => self.stack_set(stack, ValType::I64),1430Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1431}1432}14331434fn translate_f32(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1435// TODO: subtyping1436assert!(matches!(dst_ty, InterfaceType::Float32));1437self.push_dst_addr(dst);1438match src {1439Source::Memory(mem) => self.f32_load(mem),1440Source::Stack(stack) => self.stack_get(stack, ValType::F32),1441Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1442}1443match dst {1444Destination::Memory(mem) => self.f32_store(mem),1445Destination::Stack(stack, _) => self.stack_set(stack, ValType::F32),1446Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1447}1448}14491450fn translate_f64(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1451// TODO: subtyping1452assert!(matches!(dst_ty, InterfaceType::Float64));1453self.push_dst_addr(dst);1454match src {1455Source::Memory(mem) => self.f64_load(mem),1456Source::Stack(stack) => self.stack_get(stack, ValType::F64),1457Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1458}1459match dst {1460Destination::Memory(mem) => self.f64_store(mem),1461Destination::Stack(stack, _) => self.stack_set(stack, ValType::F64),1462Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1463}1464}14651466fn translate_char(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1467assert!(matches!(dst_ty, InterfaceType::Char));1468match src {1469Source::Memory(mem) => self.i32_load(mem),1470Source::Stack(stack) => self.stack_get(stack, ValType::I32),1471Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1472}1473let local = self.local_set_new_tmp(ValType::I32);14741475// This sequence is copied from the output of LLVM for:1476//1477// pub extern "C" fn foo(x: u32) -> char {1478// char::try_from(x)1479// .unwrap_or_else(|_| std::arch::wasm32::unreachable())1480// }1481//1482// Apparently this does what's required by the canonical ABI:1483//1484// def i32_to_char(opts, i):1485// trap_if(i >= 0x110000)1486// trap_if(0xD800 <= i <= 0xDFFF)1487// return chr(i)1488//1489// ... but I don't know how it works other than "well I trust LLVM"1490self.instruction(Block(BlockType::Empty));1491self.instruction(Block(BlockType::Empty));1492self.instruction(LocalGet(local.idx));1493self.instruction(I32Const(0xd800));1494self.instruction(I32Xor);1495self.instruction(I32Const(-0x110000));1496self.instruction(I32Add);1497self.instruction(I32Const(-0x10f800));1498self.instruction(I32LtU);1499self.instruction(BrIf(0));1500self.instruction(LocalGet(local.idx));1501self.instruction(I32Const(0x110000));1502self.instruction(I32Ne);1503self.instruction(BrIf(1));1504self.instruction(End);1505self.trap(Trap::InvalidChar);1506self.instruction(End);15071508self.push_dst_addr(dst);1509self.instruction(LocalGet(local.idx));1510match dst {1511Destination::Memory(mem) => {1512self.i32_store(mem);1513}1514Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),1515Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1516}15171518self.free_temp_local(local);1519}15201521fn translate_string(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {1522assert!(matches!(dst_ty, InterfaceType::String));1523let src_opts = src.opts();1524let dst_opts = dst.opts();15251526let src_mem_opts = match &src_opts.data_model {1527DataModel::Gc {} => todo!("CM+GC"),1528DataModel::LinearMemory(opts) => opts,1529};1530let dst_mem_opts = match &dst_opts.data_model {1531DataModel::Gc {} => todo!("CM+GC"),1532DataModel::LinearMemory(opts) => opts,1533};15341535// Load the pointer/length of this string into temporary locals. These1536// will be referenced a good deal so this just makes it easier to deal1537// with them consistently below rather than trying to reload from memory1538// for example.1539match src {1540Source::Stack(s) => {1541assert_eq!(s.locals.len(), 2);1542self.stack_get(&s.slice(0..1), src_mem_opts.ptr());1543self.stack_get(&s.slice(1..2), src_mem_opts.ptr());1544}1545Source::Memory(mem) => {1546self.ptr_load(mem);1547self.ptr_load(&mem.bump(src_mem_opts.ptr_size().into()));1548}1549Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),1550}1551let src_len = self.local_set_new_tmp(src_mem_opts.ptr());1552let src_ptr = self.local_set_new_tmp(src_mem_opts.ptr());1553let src_str = WasmString {1554ptr: src_ptr,1555len: src_len,1556opts: src_opts,1557};15581559let dst_str = match src_opts.string_encoding {1560StringEncoding::Utf8 => match dst_opts.string_encoding {1561StringEncoding::Utf8 => self.string_copy(&src_str, FE::Utf8, dst_opts, FE::Utf8),1562StringEncoding::Utf16 => self.string_utf8_to_utf16(&src_str, dst_opts),1563StringEncoding::CompactUtf16 => {1564self.string_to_compact(&src_str, FE::Utf8, dst_opts)1565}1566},15671568StringEncoding::Utf16 => {1569self.verify_aligned(src_mem_opts, src_str.ptr.idx, 2);1570match dst_opts.string_encoding {1571StringEncoding::Utf8 => {1572self.string_deflate_to_utf8(&src_str, FE::Utf16, dst_opts)1573}1574StringEncoding::Utf16 => {1575self.string_copy(&src_str, FE::Utf16, dst_opts, FE::Utf16)1576}1577StringEncoding::CompactUtf16 => {1578self.string_to_compact(&src_str, FE::Utf16, dst_opts)1579}1580}1581}15821583StringEncoding::CompactUtf16 => {1584self.verify_aligned(src_mem_opts, src_str.ptr.idx, 2);15851586// Test the tag big to see if this is a utf16 or a latin1 string1587// at runtime...1588self.instruction(LocalGet(src_str.len.idx));1589self.ptr_uconst(src_mem_opts, UTF16_TAG);1590self.ptr_and(src_mem_opts);1591self.ptr_if(src_mem_opts, BlockType::Empty);15921593// In the utf16 block unset the upper bit from the length local1594// so further calculations have the right value. Afterwards the1595// string transcode proceeds assuming utf16.1596self.instruction(LocalGet(src_str.len.idx));1597self.ptr_uconst(src_mem_opts, UTF16_TAG);1598self.ptr_xor(src_mem_opts);1599self.instruction(LocalSet(src_str.len.idx));1600let s1 = match dst_opts.string_encoding {1601StringEncoding::Utf8 => {1602self.string_deflate_to_utf8(&src_str, FE::Utf16, dst_opts)1603}1604StringEncoding::Utf16 => {1605self.string_copy(&src_str, FE::Utf16, dst_opts, FE::Utf16)1606}1607StringEncoding::CompactUtf16 => {1608self.string_compact_utf16_to_compact(&src_str, dst_opts)1609}1610};16111612self.instruction(Else);16131614// In the latin1 block the `src_len` local is already the number1615// of code units, so the string transcoding is all that needs to1616// happen.1617let s2 = match dst_opts.string_encoding {1618StringEncoding::Utf16 => {1619self.string_copy(&src_str, FE::Latin1, dst_opts, FE::Utf16)1620}1621StringEncoding::Utf8 => {1622self.string_deflate_to_utf8(&src_str, FE::Latin1, dst_opts)1623}1624StringEncoding::CompactUtf16 => {1625self.string_copy(&src_str, FE::Latin1, dst_opts, FE::Latin1)1626}1627};1628// Set our `s2` generated locals to the `s2` generated locals1629// as the resulting pointer of this transcode.1630self.instruction(LocalGet(s2.ptr.idx));1631self.instruction(LocalSet(s1.ptr.idx));1632self.instruction(LocalGet(s2.len.idx));1633self.instruction(LocalSet(s1.len.idx));1634self.instruction(End);1635self.free_temp_local(s2.ptr);1636self.free_temp_local(s2.len);1637s11638}1639};16401641// Store the ptr/length in the desired destination1642match dst {1643Destination::Stack(s, _) => {1644self.instruction(LocalGet(dst_str.ptr.idx));1645self.stack_set(&s[..1], dst_mem_opts.ptr());1646self.instruction(LocalGet(dst_str.len.idx));1647self.stack_set(&s[1..], dst_mem_opts.ptr());1648}1649Destination::Memory(mem) => {1650self.instruction(LocalGet(mem.addr.idx));1651self.instruction(LocalGet(dst_str.ptr.idx));1652self.ptr_store(mem);1653self.instruction(LocalGet(mem.addr.idx));1654self.instruction(LocalGet(dst_str.len.idx));1655self.ptr_store(&mem.bump(dst_mem_opts.ptr_size().into()));1656}1657Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),1658}16591660self.free_temp_local(src_str.ptr);1661self.free_temp_local(src_str.len);1662self.free_temp_local(dst_str.ptr);1663self.free_temp_local(dst_str.len);1664}16651666// Corresponding function for `store_string_copy` in the spec.1667//1668// This performs a transcoding of the string with a one-pass copy from1669// the `src` encoding to the `dst` encoding. This is only possible for1670// fixed encodings where the first allocation is guaranteed to be an1671// appropriate fit so it's not suitable for all encodings.1672//1673// Imported host transcoding functions here take the src/dst pointers as1674// well as the number of code units in the source (which always matches1675// the number of code units in the destination). There is no return1676// value from the transcode function since the encoding should always1677// work on the first pass.1678fn string_copy<'c>(1679&mut self,1680src: &WasmString<'_>,1681src_enc: FE,1682dst_opts: &'c Options,1683dst_enc: FE,1684) -> WasmString<'c> {1685assert!(dst_enc.width() >= src_enc.width());1686self.validate_string_length(src, dst_enc);16871688let src_mem_opts = {1689match &src.opts.data_model {1690DataModel::Gc {} => todo!("CM+GC"),1691DataModel::LinearMemory(opts) => opts,1692}1693};1694let dst_mem_opts = {1695match &dst_opts.data_model {1696DataModel::Gc {} => todo!("CM+GC"),1697DataModel::LinearMemory(opts) => opts,1698}1699};17001701// Calculate the source byte length given the size of each code1702// unit. Note that this shouldn't overflow given1703// `validate_string_length` above.1704let mut src_byte_len_tmp = None;1705let src_byte_len = if src_enc.width() == 1 {1706src.len.idx1707} else {1708assert_eq!(src_enc.width(), 2);1709self.instruction(LocalGet(src.len.idx));1710self.ptr_uconst(src_mem_opts, 1);1711self.ptr_shl(src_mem_opts);1712let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());1713let ret = tmp.idx;1714src_byte_len_tmp = Some(tmp);1715ret1716};17171718// Convert the source code units length to the destination byte1719// length type.1720self.convert_src_len_to_dst(1721src.len.idx,1722src.opts.data_model.unwrap_memory().ptr(),1723dst_opts.data_model.unwrap_memory().ptr(),1724);1725let dst_len = self.local_tee_new_tmp(dst_opts.data_model.unwrap_memory().ptr());1726if dst_enc.width() > 1 {1727assert_eq!(dst_enc.width(), 2);1728self.ptr_uconst(dst_mem_opts, 1);1729self.ptr_shl(dst_mem_opts);1730}1731let dst_byte_len = self.local_set_new_tmp(dst_opts.data_model.unwrap_memory().ptr());17321733// Allocate space in the destination using the calculated byte1734// length.1735let dst = {1736let dst_mem = self.malloc(1737dst_opts,1738MallocSize::Local(dst_byte_len.idx),1739dst_enc.width().into(),1740);1741WasmString {1742ptr: dst_mem.addr,1743len: dst_len,1744opts: dst_opts,1745}1746};17471748// Validate that `src_len + src_ptr` and1749// `dst_mem.addr_local + dst_byte_len` are both in-bounds. This1750// is done by loading the last byte of the string and if that1751// doesn't trap then it's known valid.1752self.validate_string_inbounds(src, src_byte_len);1753self.validate_string_inbounds(&dst, dst_byte_len.idx);17541755// If the validations pass then the host `transcode` intrinsic1756// is invoked. This will either raise a trap or otherwise succeed1757// in which case we're done.1758let op = if src_enc == dst_enc {1759Transcode::Copy(src_enc)1760} else {1761assert_eq!(src_enc, FE::Latin1);1762assert_eq!(dst_enc, FE::Utf16);1763Transcode::Latin1ToUtf161764};1765let transcode = self.transcoder(src, &dst, op);1766self.instruction(LocalGet(src.ptr.idx));1767self.instruction(LocalGet(src.len.idx));1768self.instruction(LocalGet(dst.ptr.idx));1769self.instruction(Call(transcode.as_u32()));17701771self.free_temp_local(dst_byte_len);1772if let Some(tmp) = src_byte_len_tmp {1773self.free_temp_local(tmp);1774}17751776dst1777}1778// Corresponding function for `store_string_to_utf8` in the spec.1779//1780// This translation works by possibly performing a number of1781// reallocations. First a buffer of size input-code-units is used to try1782// to get the transcoding correct on the first try. If that fails the1783// maximum worst-case size is used and then that is resized down if it's1784// too large.1785//1786// The host transcoding function imported here will receive src ptr/len1787// and dst ptr/len and return how many code units were consumed on both1788// sides. The amount of code units consumed in the source dictates which1789// branches are taken in this conversion.1790fn string_deflate_to_utf8<'c>(1791&mut self,1792src: &WasmString<'_>,1793src_enc: FE,1794dst_opts: &'c Options,1795) -> WasmString<'c> {1796let src_mem_opts = match &src.opts.data_model {1797DataModel::Gc {} => todo!("CM+GC"),1798DataModel::LinearMemory(opts) => opts,1799};1800let dst_mem_opts = match &dst_opts.data_model {1801DataModel::Gc {} => todo!("CM+GC"),1802DataModel::LinearMemory(opts) => opts,1803};18041805self.validate_string_length(src, src_enc);18061807// Optimistically assume that the code unit length of the source is1808// all that's needed in the destination. Perform that allocation1809// here and proceed to transcoding below.1810self.convert_src_len_to_dst(1811src.len.idx,1812src.opts.data_model.unwrap_memory().ptr(),1813dst_opts.data_model.unwrap_memory().ptr(),1814);1815let dst_len = self.local_tee_new_tmp(dst_opts.data_model.unwrap_memory().ptr());1816let dst_byte_len = self.local_set_new_tmp(dst_opts.data_model.unwrap_memory().ptr());18171818let dst = {1819let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 1);1820WasmString {1821ptr: dst_mem.addr,1822len: dst_len,1823opts: dst_opts,1824}1825};18261827// Ensure buffers are all in-bounds1828let mut src_byte_len_tmp = None;1829let src_byte_len = match src_enc {1830FE::Latin1 => src.len.idx,1831FE::Utf16 => {1832self.instruction(LocalGet(src.len.idx));1833self.ptr_uconst(src_mem_opts, 1);1834self.ptr_shl(src_mem_opts);1835let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());1836let ret = tmp.idx;1837src_byte_len_tmp = Some(tmp);1838ret1839}1840FE::Utf8 => unreachable!(),1841};1842self.validate_string_inbounds(src, src_byte_len);1843self.validate_string_inbounds(&dst, dst_byte_len.idx);18441845// Perform the initial transcode1846let op = match src_enc {1847FE::Latin1 => Transcode::Latin1ToUtf8,1848FE::Utf16 => Transcode::Utf16ToUtf8,1849FE::Utf8 => unreachable!(),1850};1851let transcode = self.transcoder(src, &dst, op);1852self.instruction(LocalGet(src.ptr.idx));1853self.instruction(LocalGet(src.len.idx));1854self.instruction(LocalGet(dst.ptr.idx));1855self.instruction(LocalGet(dst_byte_len.idx));1856self.instruction(Call(transcode.as_u32()));1857self.instruction(LocalSet(dst.len.idx));1858let src_len_tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());18591860// Test if the source was entirely transcoded by comparing1861// `src_len_tmp`, the number of code units transcoded from the1862// source, with `src_len`, the original number of code units.1863self.instruction(LocalGet(src_len_tmp.idx));1864self.instruction(LocalGet(src.len.idx));1865self.ptr_ne(src_mem_opts);1866self.instruction(If(BlockType::Empty));18671868// Here a worst-case reallocation is performed to grow `dst_mem`.1869// In-line a check is also performed that the worst-case byte size1870// fits within the maximum size of strings.1871self.instruction(LocalGet(dst.ptr.idx)); // old_ptr1872self.instruction(LocalGet(dst_byte_len.idx)); // old_size1873self.ptr_uconst(dst_mem_opts, 1); // align1874let factor = match src_enc {1875FE::Latin1 => 2,1876FE::Utf16 => 3,1877_ => unreachable!(),1878};1879self.validate_string_length_u8(src, factor);1880self.convert_src_len_to_dst(1881src.len.idx,1882src.opts.data_model.unwrap_memory().ptr(),1883dst_opts.data_model.unwrap_memory().ptr(),1884);1885self.ptr_uconst(dst_mem_opts, factor.into());1886self.ptr_mul(dst_mem_opts);1887self.instruction(LocalTee(dst_byte_len.idx));1888self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));1889self.instruction(LocalSet(dst.ptr.idx));18901891// Verify that the destination is still in-bounds1892self.validate_string_inbounds(&dst, dst_byte_len.idx);18931894// Perform another round of transcoding that should be guaranteed1895// to succeed. Note that all the parameters here are offset by the1896// results of the first transcoding to only perform the remaining1897// transcode on the final units.1898self.instruction(LocalGet(src.ptr.idx));1899self.instruction(LocalGet(src_len_tmp.idx));1900if let FE::Utf16 = src_enc {1901self.ptr_uconst(src_mem_opts, 1);1902self.ptr_shl(src_mem_opts);1903}1904self.ptr_add(src_mem_opts);1905self.instruction(LocalGet(src.len.idx));1906self.instruction(LocalGet(src_len_tmp.idx));1907self.ptr_sub(src_mem_opts);1908self.instruction(LocalGet(dst.ptr.idx));1909self.instruction(LocalGet(dst.len.idx));1910self.ptr_add(dst_mem_opts);1911self.instruction(LocalGet(dst_byte_len.idx));1912self.instruction(LocalGet(dst.len.idx));1913self.ptr_sub(dst_mem_opts);1914self.instruction(Call(transcode.as_u32()));19151916// Add the second result, the amount of destination units encoded,1917// to `dst_len` so it's an accurate reflection of the final size of1918// the destination buffer.1919self.instruction(LocalGet(dst.len.idx));1920self.ptr_add(dst_mem_opts);1921self.instruction(LocalSet(dst.len.idx));19221923// In debug mode verify the first result consumed the entire string,1924// otherwise simply discard it.1925if self.module.debug {1926self.instruction(LocalGet(src.len.idx));1927self.instruction(LocalGet(src_len_tmp.idx));1928self.ptr_sub(src_mem_opts);1929self.ptr_ne(src_mem_opts);1930self.instruction(If(BlockType::Empty));1931self.trap(Trap::AssertFailed("should have finished encoding"));1932self.instruction(End);1933} else {1934self.instruction(Drop);1935}19361937// Perform a downsizing if the worst-case size was too large1938self.instruction(LocalGet(dst.len.idx));1939self.instruction(LocalGet(dst_byte_len.idx));1940self.ptr_ne(dst_mem_opts);1941self.instruction(If(BlockType::Empty));1942self.instruction(LocalGet(dst.ptr.idx)); // old_ptr1943self.instruction(LocalGet(dst_byte_len.idx)); // old_size1944self.ptr_uconst(dst_mem_opts, 1); // align1945self.instruction(LocalGet(dst.len.idx)); // new_size1946self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));1947self.instruction(LocalSet(dst.ptr.idx));1948self.instruction(End);19491950// If the first transcode was enough then assert that the returned1951// amount of destination items written equals the byte size.1952if self.module.debug {1953self.instruction(Else);19541955self.instruction(LocalGet(dst.len.idx));1956self.instruction(LocalGet(dst_byte_len.idx));1957self.ptr_ne(dst_mem_opts);1958self.instruction(If(BlockType::Empty));1959self.trap(Trap::AssertFailed("should have finished encoding"));1960self.instruction(End);1961}19621963self.instruction(End); // end of "first transcode not enough"19641965self.free_temp_local(src_len_tmp);1966self.free_temp_local(dst_byte_len);1967if let Some(tmp) = src_byte_len_tmp {1968self.free_temp_local(tmp);1969}19701971dst1972}19731974// Corresponds to the `store_utf8_to_utf16` function in the spec.1975//1976// When converting utf-8 to utf-16 a pessimistic allocation is1977// done which is twice the byte length of the utf-8 string.1978// The host then transcodes and returns how many code units were1979// actually used during the transcoding and if it's beneath the1980// pessimistic maximum then the buffer is reallocated down to1981// a smaller amount.1982//1983// The host-imported transcoding function takes the src/dst pointer as1984// well as the code unit size of both the source and destination. The1985// destination should always be big enough to hold the result of the1986// transcode and so the result of the host function is how many code1987// units were written to the destination.1988fn string_utf8_to_utf16<'c>(1989&mut self,1990src: &WasmString<'_>,1991dst_opts: &'c Options,1992) -> WasmString<'c> {1993let src_mem_opts = match &src.opts.data_model {1994DataModel::Gc {} => todo!("CM+GC"),1995DataModel::LinearMemory(opts) => opts,1996};1997let dst_mem_opts = match &dst_opts.data_model {1998DataModel::Gc {} => todo!("CM+GC"),1999DataModel::LinearMemory(opts) => opts,2000};20012002self.validate_string_length(src, FE::Utf16);2003self.convert_src_len_to_dst(2004src.len.idx,2005src_mem_opts.ptr(),2006dst_opts.data_model.unwrap_memory().ptr(),2007);2008let dst_len = self.local_tee_new_tmp(dst_opts.data_model.unwrap_memory().ptr());2009self.ptr_uconst(dst_mem_opts, 1);2010self.ptr_shl(dst_mem_opts);2011let dst_byte_len = self.local_set_new_tmp(dst_opts.data_model.unwrap_memory().ptr());2012let dst = {2013let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 2);2014WasmString {2015ptr: dst_mem.addr,2016len: dst_len,2017opts: dst_opts,2018}2019};20202021self.validate_string_inbounds(src, src.len.idx);2022self.validate_string_inbounds(&dst, dst_byte_len.idx);20232024let transcode = self.transcoder(src, &dst, Transcode::Utf8ToUtf16);2025self.instruction(LocalGet(src.ptr.idx));2026self.instruction(LocalGet(src.len.idx));2027self.instruction(LocalGet(dst.ptr.idx));2028self.instruction(Call(transcode.as_u32()));2029self.instruction(LocalSet(dst.len.idx));20302031// If the number of code units returned by transcode is not2032// equal to the original number of code units then2033// the buffer must be shrunk.2034//2035// Note that the byte length of the final allocation we2036// want is twice the code unit length returned by the2037// transcoding function.2038self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2039self.instruction(LocalGet(dst.len.idx));2040self.ptr_ne(dst_mem_opts);2041self.instruction(If(BlockType::Empty));2042self.instruction(LocalGet(dst.ptr.idx));2043self.instruction(LocalGet(dst_byte_len.idx));2044self.ptr_uconst(dst_mem_opts, 2);2045self.instruction(LocalGet(dst.len.idx));2046self.ptr_uconst(dst_mem_opts, 1);2047self.ptr_shl(dst_mem_opts);2048self.instruction(Call(match dst.opts.data_model {2049DataModel::Gc {} => todo!("CM+GC"),2050DataModel::LinearMemory(LinearMemoryOptions { realloc, .. }) => {2051realloc.unwrap().as_u32()2052}2053}));2054self.instruction(LocalSet(dst.ptr.idx));2055self.instruction(End); // end of shrink-to-fit20562057self.free_temp_local(dst_byte_len);20582059dst2060}20612062// Corresponds to `store_probably_utf16_to_latin1_or_utf16` in the spec.2063//2064// This will try to transcode the input utf16 string to utf16 in the2065// destination. If utf16 isn't needed though and latin1 could be used2066// then that's used instead and a reallocation to downsize occurs2067// afterwards.2068//2069// The host transcode function here will take the src/dst pointers as2070// well as src length. The destination byte length is twice the src code2071// unit length. The return value is the tagged length of the returned2072// string. If the upper bit is set then utf16 was used and the2073// conversion is done. If the upper bit is not set then latin1 was used2074// and a downsizing needs to happen.2075fn string_compact_utf16_to_compact<'c>(2076&mut self,2077src: &WasmString<'_>,2078dst_opts: &'c Options,2079) -> WasmString<'c> {2080let src_mem_opts = match &src.opts.data_model {2081DataModel::Gc {} => todo!("CM+GC"),2082DataModel::LinearMemory(opts) => opts,2083};2084let dst_mem_opts = match &dst_opts.data_model {2085DataModel::Gc {} => todo!("CM+GC"),2086DataModel::LinearMemory(opts) => opts,2087};20882089self.validate_string_length(src, FE::Utf16);2090self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2091let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());2092self.ptr_uconst(dst_mem_opts, 1);2093self.ptr_shl(dst_mem_opts);2094let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());2095let dst = {2096let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 2);2097WasmString {2098ptr: dst_mem.addr,2099len: dst_len,2100opts: dst_opts,2101}2102};21032104self.convert_src_len_to_dst(2105dst_byte_len.idx,2106dst.opts.data_model.unwrap_memory().ptr(),2107src_mem_opts.ptr(),2108);2109let src_byte_len = self.local_set_new_tmp(src_mem_opts.ptr());21102111self.validate_string_inbounds(src, src_byte_len.idx);2112self.validate_string_inbounds(&dst, dst_byte_len.idx);21132114let transcode = self.transcoder(src, &dst, Transcode::Utf16ToCompactProbablyUtf16);2115self.instruction(LocalGet(src.ptr.idx));2116self.instruction(LocalGet(src.len.idx));2117self.instruction(LocalGet(dst.ptr.idx));2118self.instruction(Call(transcode.as_u32()));2119self.instruction(LocalSet(dst.len.idx));21202121// Assert that the untagged code unit length is the same as the2122// source code unit length.2123if self.module.debug {2124self.instruction(LocalGet(dst.len.idx));2125self.ptr_uconst(dst_mem_opts, !UTF16_TAG);2126self.ptr_and(dst_mem_opts);2127self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2128self.ptr_ne(dst_mem_opts);2129self.instruction(If(BlockType::Empty));2130self.trap(Trap::AssertFailed("expected equal code units"));2131self.instruction(End);2132}21332134// If the UTF16_TAG is set then utf16 was used and the destination2135// should be appropriately sized. Bail out of the "is this string2136// empty" block and fall through otherwise to resizing.2137self.instruction(LocalGet(dst.len.idx));2138self.ptr_uconst(dst_mem_opts, UTF16_TAG);2139self.ptr_and(dst_mem_opts);2140self.ptr_br_if(dst_mem_opts, 0);21412142// Here `realloc` is used to downsize the string2143self.instruction(LocalGet(dst.ptr.idx)); // old_ptr2144self.instruction(LocalGet(dst_byte_len.idx)); // old_size2145self.ptr_uconst(dst_mem_opts, 2); // align2146self.instruction(LocalGet(dst.len.idx)); // new_size2147self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));2148self.instruction(LocalSet(dst.ptr.idx));21492150self.free_temp_local(dst_byte_len);2151self.free_temp_local(src_byte_len);21522153dst2154}21552156// Corresponds to `store_string_to_latin1_or_utf16` in the spec.2157//2158// This will attempt a first pass of transcoding to latin1 and on2159// failure a larger buffer is allocated for utf16 and then utf16 is2160// encoded in-place into the buffer. After either latin1 or utf16 the2161// buffer is then resized to fit the final string allocation.2162fn string_to_compact<'c>(2163&mut self,2164src: &WasmString<'_>,2165src_enc: FE,2166dst_opts: &'c Options,2167) -> WasmString<'c> {2168let src_mem_opts = match &src.opts.data_model {2169DataModel::Gc {} => todo!("CM+GC"),2170DataModel::LinearMemory(opts) => opts,2171};2172let dst_mem_opts = match &dst_opts.data_model {2173DataModel::Gc {} => todo!("CM+GC"),2174DataModel::LinearMemory(opts) => opts,2175};21762177self.validate_string_length(src, src_enc);2178self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2179let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());2180let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());2181let dst = {2182let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 2);2183WasmString {2184ptr: dst_mem.addr,2185len: dst_len,2186opts: dst_opts,2187}2188};21892190self.validate_string_inbounds(src, src.len.idx);2191self.validate_string_inbounds(&dst, dst_byte_len.idx);21922193// Perform the initial latin1 transcode. This returns the number of2194// source code units consumed and the number of destination code2195// units (bytes) written.2196let (latin1, utf16) = match src_enc {2197FE::Utf8 => (Transcode::Utf8ToLatin1, Transcode::Utf8ToCompactUtf16),2198FE::Utf16 => (Transcode::Utf16ToLatin1, Transcode::Utf16ToCompactUtf16),2199FE::Latin1 => unreachable!(),2200};2201let transcode_latin1 = self.transcoder(src, &dst, latin1);2202let transcode_utf16 = self.transcoder(src, &dst, utf16);2203self.instruction(LocalGet(src.ptr.idx));2204self.instruction(LocalGet(src.len.idx));2205self.instruction(LocalGet(dst.ptr.idx));2206self.instruction(Call(transcode_latin1.as_u32()));2207self.instruction(LocalSet(dst.len.idx));2208let src_len_tmp = self.local_set_new_tmp(src_mem_opts.ptr());22092210// If the source was entirely consumed then the transcode completed2211// and all that's necessary is to optionally shrink the buffer.2212self.instruction(LocalGet(src_len_tmp.idx));2213self.instruction(LocalGet(src.len.idx));2214self.ptr_eq(src_mem_opts);2215self.instruction(If(BlockType::Empty)); // if latin1-or-utf16 block22162217// Test if the original byte length of the allocation is the same as2218// the number of written bytes, and if not then shrink the buffer2219// with a call to `realloc`.2220self.instruction(LocalGet(dst_byte_len.idx));2221self.instruction(LocalGet(dst.len.idx));2222self.ptr_ne(dst_mem_opts);2223self.instruction(If(BlockType::Empty));2224self.instruction(LocalGet(dst.ptr.idx)); // old_ptr2225self.instruction(LocalGet(dst_byte_len.idx)); // old_size2226self.ptr_uconst(dst_mem_opts, 2); // align2227self.instruction(LocalGet(dst.len.idx)); // new_size2228self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));2229self.instruction(LocalSet(dst.ptr.idx));2230self.instruction(End);22312232// In this block the latin1 encoding failed. The host transcode2233// returned how many units were consumed from the source and how2234// many bytes were written to the destination. Here the buffer is2235// inflated and sized and the second utf16 intrinsic is invoked to2236// perform the final inflation.2237self.instruction(Else); // else latin1-or-utf16 block22382239// For utf8 validate that the inflated size is still within bounds.2240if src_enc.width() == 1 {2241self.validate_string_length_u8(src, 2);2242}22432244// Reallocate the buffer with twice the source code units in byte2245// size.2246self.instruction(LocalGet(dst.ptr.idx)); // old_ptr2247self.instruction(LocalGet(dst_byte_len.idx)); // old_size2248self.ptr_uconst(dst_mem_opts, 2); // align2249self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2250self.ptr_uconst(dst_mem_opts, 1);2251self.ptr_shl(dst_mem_opts);2252self.instruction(LocalTee(dst_byte_len.idx));2253self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));2254self.instruction(LocalSet(dst.ptr.idx));22552256// Call the host utf16 transcoding function. This will inflate the2257// prior latin1 bytes and then encode the rest of the source string2258// as utf16 into the remaining space in the destination buffer.2259self.instruction(LocalGet(src.ptr.idx));2260self.instruction(LocalGet(src_len_tmp.idx));2261if let FE::Utf16 = src_enc {2262self.ptr_uconst(src_mem_opts, 1);2263self.ptr_shl(src_mem_opts);2264}2265self.ptr_add(src_mem_opts);2266self.instruction(LocalGet(src.len.idx));2267self.instruction(LocalGet(src_len_tmp.idx));2268self.ptr_sub(src_mem_opts);2269self.instruction(LocalGet(dst.ptr.idx));2270self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2271self.instruction(LocalGet(dst.len.idx));2272self.instruction(Call(transcode_utf16.as_u32()));2273self.instruction(LocalSet(dst.len.idx));22742275// If the returned number of code units written to the destination2276// is not equal to the size of the allocation then the allocation is2277// resized down to the appropriate size.2278//2279// Note that the byte size desired is `2*dst_len` and the current2280// byte buffer size is `2*src_len` so the `2` factor isn't checked2281// here, just the lengths.2282self.instruction(LocalGet(dst.len.idx));2283self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2284self.ptr_ne(dst_mem_opts);2285self.instruction(If(BlockType::Empty));2286self.instruction(LocalGet(dst.ptr.idx)); // old_ptr2287self.instruction(LocalGet(dst_byte_len.idx)); // old_size2288self.ptr_uconst(dst_mem_opts, 2); // align2289self.instruction(LocalGet(dst.len.idx));2290self.ptr_uconst(dst_mem_opts, 1);2291self.ptr_shl(dst_mem_opts);2292self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));2293self.instruction(LocalSet(dst.ptr.idx));2294self.instruction(End);22952296// Tag the returned pointer as utf162297self.instruction(LocalGet(dst.len.idx));2298self.ptr_uconst(dst_mem_opts, UTF16_TAG);2299self.ptr_or(dst_mem_opts);2300self.instruction(LocalSet(dst.len.idx));23012302self.instruction(End); // end latin1-or-utf16 block23032304self.free_temp_local(src_len_tmp);2305self.free_temp_local(dst_byte_len);23062307dst2308}23092310fn validate_string_length(&mut self, src: &WasmString<'_>, dst: FE) {2311self.validate_string_length_u8(src, dst.width())2312}23132314fn validate_string_length_u8(&mut self, s: &WasmString<'_>, dst: u8) {2315let mem_opts = match &s.opts.data_model {2316DataModel::Gc {} => todo!("CM+GC"),2317DataModel::LinearMemory(opts) => opts,2318};23192320// Check to see if the source byte length is out of bounds in2321// which case a trap is generated.2322self.instruction(LocalGet(s.len.idx));2323let max = MAX_STRING_BYTE_LENGTH / u32::from(dst);2324self.ptr_uconst(mem_opts, max);2325self.ptr_ge_u(mem_opts);2326self.instruction(If(BlockType::Empty));2327self.trap(Trap::StringLengthTooBig);2328self.instruction(End);2329}23302331fn transcoder(2332&mut self,2333src: &WasmString<'_>,2334dst: &WasmString<'_>,2335op: Transcode,2336) -> FuncIndex {2337match (src.opts.data_model, dst.opts.data_model) {2338(DataModel::Gc {}, _) | (_, DataModel::Gc {}) => {2339todo!("CM+GC")2340}2341(2342DataModel::LinearMemory(LinearMemoryOptions {2343memory64: src64,2344memory: src_mem,2345realloc: _,2346}),2347DataModel::LinearMemory(LinearMemoryOptions {2348memory64: dst64,2349memory: dst_mem,2350realloc: _,2351}),2352) => self.module.import_transcoder(Transcoder {2353from_memory: src_mem.unwrap(),2354from_memory64: src64,2355to_memory: dst_mem.unwrap(),2356to_memory64: dst64,2357op,2358}),2359}2360}23612362fn validate_string_inbounds(&mut self, s: &WasmString<'_>, byte_len: u32) {2363match &s.opts.data_model {2364DataModel::Gc {} => todo!("CM+GC"),2365DataModel::LinearMemory(opts) => {2366self.validate_memory_inbounds(opts, s.ptr.idx, byte_len, Trap::StringLengthOverflow)2367}2368}2369}23702371fn validate_memory_inbounds(2372&mut self,2373opts: &LinearMemoryOptions,2374ptr_local: u32,2375byte_len_local: u32,2376trap: Trap,2377) {2378let extend_to_64 = |me: &mut Self| {2379if !opts.memory64 {2380me.instruction(I64ExtendI32U);2381}2382};23832384self.instruction(Block(BlockType::Empty));2385self.instruction(Block(BlockType::Empty));23862387// Calculate the full byte size of memory with `memory.size`. Note that2388// arithmetic here is done always in 64-bits to accommodate 4G memories.2389// Additionally it's assumed that 64-bit memories never fill up2390// entirely.2391self.instruction(MemorySize(opts.memory.unwrap().as_u32()));2392extend_to_64(self);2393self.instruction(I64Const(16));2394self.instruction(I64Shl);23952396// Calculate the end address of the string. This is done by adding the2397// base pointer to the byte length. For 32-bit memories there's no need2398// to check for overflow since everything is extended to 64-bit, but for2399// 64-bit memories overflow is checked.2400self.instruction(LocalGet(ptr_local));2401extend_to_64(self);2402self.instruction(LocalGet(byte_len_local));2403extend_to_64(self);2404self.instruction(I64Add);2405if opts.memory64 {2406let tmp = self.local_tee_new_tmp(ValType::I64);2407self.instruction(LocalGet(ptr_local));2408self.ptr_lt_u(opts);2409self.instruction(BrIf(0));2410self.instruction(LocalGet(tmp.idx));2411self.free_temp_local(tmp);2412}24132414// If the byte size of memory is greater than the final address of the2415// string then the string is invalid. Note that if it's precisely equal2416// then that's ok.2417self.instruction(I64GeU);2418self.instruction(BrIf(1));24192420self.instruction(End);2421self.trap(trap);2422self.instruction(End);2423}24242425fn translate_list(2426&mut self,2427src_ty: TypeListIndex,2428src: &Source<'_>,2429dst_ty: &InterfaceType,2430dst: &Destination,2431) {2432let src_mem_opts = match &src.opts().data_model {2433DataModel::Gc {} => todo!("CM+GC"),2434DataModel::LinearMemory(opts) => opts,2435};2436let dst_mem_opts = match &dst.opts().data_model {2437DataModel::Gc {} => todo!("CM+GC"),2438DataModel::LinearMemory(opts) => opts,2439};24402441let src_element_ty = &self.types[src_ty].element;2442let dst_element_ty = match dst_ty {2443InterfaceType::List(r) => &self.types[*r].element,2444_ => panic!("expected a list"),2445};2446let src_opts = src.opts();2447let dst_opts = dst.opts();2448let (src_size, src_align) = self.types.size_align(src_mem_opts, src_element_ty);2449let (dst_size, dst_align) = self.types.size_align(dst_mem_opts, dst_element_ty);24502451// Load the pointer/length of this list into temporary locals. These2452// will be referenced a good deal so this just makes it easier to deal2453// with them consistently below rather than trying to reload from memory2454// for example.2455match src {2456Source::Stack(s) => {2457assert_eq!(s.locals.len(), 2);2458self.stack_get(&s.slice(0..1), src_mem_opts.ptr());2459self.stack_get(&s.slice(1..2), src_mem_opts.ptr());2460}2461Source::Memory(mem) => {2462self.ptr_load(mem);2463self.ptr_load(&mem.bump(src_mem_opts.ptr_size().into()));2464}2465Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),2466}2467let src_len = self.local_set_new_tmp(src_mem_opts.ptr());2468let src_ptr = self.local_set_new_tmp(src_mem_opts.ptr());24692470// Create a `Memory` operand which will internally assert that the2471// `src_ptr` value is properly aligned.2472let src_mem = self.memory_operand(src_opts, src_ptr, src_align);24732474// Calculate the source/destination byte lengths into unique locals.2475let src_byte_len = self.calculate_list_byte_len(src_mem_opts, src_len.idx, src_size);2476let dst_byte_len = if src_size == dst_size {2477self.convert_src_len_to_dst(src_byte_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2478self.local_set_new_tmp(dst_mem_opts.ptr())2479} else if src_mem_opts.ptr() == dst_mem_opts.ptr() {2480self.calculate_list_byte_len(dst_mem_opts, src_len.idx, dst_size)2481} else {2482self.convert_src_len_to_dst(src_byte_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2483let tmp = self.local_set_new_tmp(dst_mem_opts.ptr());2484let ret = self.calculate_list_byte_len(dst_mem_opts, tmp.idx, dst_size);2485self.free_temp_local(tmp);2486ret2487};24882489// Here `realloc` is invoked (in a `malloc`-like fashion) to allocate2490// space for the list in the destination memory. This will also2491// internally insert checks that the returned pointer is aligned2492// correctly for the destination.2493let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), dst_align);24942495// With all the pointers and byte lengths verity that both the source2496// and the destination buffers are in-bounds.2497self.validate_memory_inbounds(2498src_mem_opts,2499src_mem.addr.idx,2500src_byte_len.idx,2501Trap::ListByteLengthOverflow,2502);2503self.validate_memory_inbounds(2504dst_mem_opts,2505dst_mem.addr.idx,2506dst_byte_len.idx,2507Trap::ListByteLengthOverflow,2508);25092510self.free_temp_local(src_byte_len);2511self.free_temp_local(dst_byte_len);25122513// This is the main body of the loop to actually translate list types.2514// Note that if both element sizes are 0 then this won't actually do2515// anything so the loop is removed entirely.2516if src_size > 0 || dst_size > 0 {2517// This block encompasses the entire loop and is use to exit before even2518// entering the loop if the list size is zero.2519self.instruction(Block(BlockType::Empty));25202521// Set the `remaining` local and only continue if it's > 02522self.instruction(LocalGet(src_len.idx));2523let remaining = self.local_tee_new_tmp(src_mem_opts.ptr());2524self.ptr_eqz(src_mem_opts);2525self.instruction(BrIf(0));25262527// Initialize the two destination pointers to their initial values2528self.instruction(LocalGet(src_mem.addr.idx));2529let cur_src_ptr = self.local_set_new_tmp(src_mem_opts.ptr());2530self.instruction(LocalGet(dst_mem.addr.idx));2531let cur_dst_ptr = self.local_set_new_tmp(dst_mem_opts.ptr());25322533self.instruction(Loop(BlockType::Empty));25342535// Translate the next element in the list2536let element_src = Source::Memory(Memory {2537opts: src_opts,2538offset: 0,2539addr: TempLocal::new(cur_src_ptr.idx, cur_src_ptr.ty),2540});2541let element_dst = Destination::Memory(Memory {2542opts: dst_opts,2543offset: 0,2544addr: TempLocal::new(cur_dst_ptr.idx, cur_dst_ptr.ty),2545});2546self.translate(src_element_ty, &element_src, dst_element_ty, &element_dst);25472548// Update the two loop pointers2549if src_size > 0 {2550self.instruction(LocalGet(cur_src_ptr.idx));2551self.ptr_uconst(src_mem_opts, src_size);2552self.ptr_add(src_mem_opts);2553self.instruction(LocalSet(cur_src_ptr.idx));2554}2555if dst_size > 0 {2556self.instruction(LocalGet(cur_dst_ptr.idx));2557self.ptr_uconst(dst_mem_opts, dst_size);2558self.ptr_add(dst_mem_opts);2559self.instruction(LocalSet(cur_dst_ptr.idx));2560}25612562// Update the remaining count, falling through to break out if it's zero2563// now.2564self.instruction(LocalGet(remaining.idx));2565self.ptr_iconst(src_mem_opts, -1);2566self.ptr_add(src_mem_opts);2567self.instruction(LocalTee(remaining.idx));2568self.ptr_br_if(src_mem_opts, 0);2569self.instruction(End); // end of loop2570self.instruction(End); // end of block25712572self.free_temp_local(cur_dst_ptr);2573self.free_temp_local(cur_src_ptr);2574self.free_temp_local(remaining);2575}25762577// Store the ptr/length in the desired destination2578match dst {2579Destination::Stack(s, _) => {2580self.instruction(LocalGet(dst_mem.addr.idx));2581self.stack_set(&s[..1], dst_mem_opts.ptr());2582self.convert_src_len_to_dst(src_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2583self.stack_set(&s[1..], dst_mem_opts.ptr());2584}2585Destination::Memory(mem) => {2586self.instruction(LocalGet(mem.addr.idx));2587self.instruction(LocalGet(dst_mem.addr.idx));2588self.ptr_store(mem);2589self.instruction(LocalGet(mem.addr.idx));2590self.convert_src_len_to_dst(src_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());2591self.ptr_store(&mem.bump(dst_mem_opts.ptr_size().into()));2592}2593Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),2594}25952596self.free_temp_local(src_len);2597self.free_temp_local(src_mem.addr);2598self.free_temp_local(dst_mem.addr);2599}26002601fn calculate_list_byte_len(2602&mut self,2603opts: &LinearMemoryOptions,2604len_local: u32,2605elt_size: u32,2606) -> TempLocal {2607// Zero-size types are easy to handle here because the byte size of the2608// destination is always zero.2609if elt_size == 0 {2610self.ptr_uconst(opts, 0);2611return self.local_set_new_tmp(opts.ptr());2612}26132614// For one-byte elements in the destination the check here can be a bit2615// more optimal than the general case below. In these situations if the2616// source pointer type is 32-bit then we're guaranteed to not overflow,2617// so the source length is simply casted to the destination's type.2618//2619// If the source is 64-bit then all that needs to be checked is to2620// ensure that it does not have the upper 32-bits set.2621if elt_size == 1 {2622if let ValType::I64 = opts.ptr() {2623self.instruction(LocalGet(len_local));2624self.instruction(I64Const(32));2625self.instruction(I64ShrU);2626self.instruction(I32WrapI64);2627self.instruction(If(BlockType::Empty));2628self.trap(Trap::ListByteLengthOverflow);2629self.instruction(End);2630}2631self.instruction(LocalGet(len_local));2632return self.local_set_new_tmp(opts.ptr());2633}26342635// The main check implemented by this function is to verify that2636// `src_len_local` does not exceed the 32-bit range. Byte sizes for2637// lists must always fit in 32-bits to get transferred to 32-bit2638// memories.2639self.instruction(Block(BlockType::Empty));2640self.instruction(Block(BlockType::Empty));2641self.instruction(LocalGet(len_local));2642match opts.ptr() {2643// The source's list length is guaranteed to be less than 32-bits2644// so simply extend it up to a 64-bit type for the multiplication2645// below.2646ValType::I32 => self.instruction(I64ExtendI32U),26472648// If the source is a 64-bit memory then if the item length doesn't2649// fit in 32-bits the byte length definitely won't, so generate a2650// branch to our overflow trap here if any of the upper 32-bits are set.2651ValType::I64 => {2652self.instruction(I64Const(32));2653self.instruction(I64ShrU);2654self.instruction(I32WrapI64);2655self.instruction(BrIf(0));2656self.instruction(LocalGet(len_local));2657}26582659_ => unreachable!(),2660}26612662// Next perform a 64-bit multiplication with the element byte size that2663// is itself guaranteed to fit in 32-bits. The result is then checked2664// to see if we overflowed the 32-bit space. The two input operands to2665// the multiplication are guaranteed to be 32-bits at most which means2666// that this multiplication shouldn't overflow.2667//2668// The result of the multiplication is saved into a local as well to2669// get the result afterwards.2670self.instruction(I64Const(elt_size.into()));2671self.instruction(I64Mul);2672let tmp = self.local_tee_new_tmp(ValType::I64);2673// Branch to success if the upper 32-bits are zero, otherwise2674// fall-through to the trap.2675self.instruction(I64Const(32));2676self.instruction(I64ShrU);2677self.instruction(I64Eqz);2678self.instruction(BrIf(1));2679self.instruction(End);2680self.trap(Trap::ListByteLengthOverflow);2681self.instruction(End);26822683// If a fresh local was used to store the result of the multiplication2684// then convert it down to 32-bits which should be guaranteed to not2685// lose information at this point.2686if opts.ptr() == ValType::I64 {2687tmp2688} else {2689self.instruction(LocalGet(tmp.idx));2690self.instruction(I32WrapI64);2691self.free_temp_local(tmp);2692self.local_set_new_tmp(ValType::I32)2693}2694}26952696fn convert_src_len_to_dst(2697&mut self,2698src_len_local: u32,2699src_ptr_ty: ValType,2700dst_ptr_ty: ValType,2701) {2702self.instruction(LocalGet(src_len_local));2703match (src_ptr_ty, dst_ptr_ty) {2704(ValType::I32, ValType::I64) => self.instruction(I64ExtendI32U),2705(ValType::I64, ValType::I32) => self.instruction(I32WrapI64),2706(src, dst) => assert_eq!(src, dst),2707}2708}27092710fn translate_record(2711&mut self,2712src_ty: TypeRecordIndex,2713src: &Source<'_>,2714dst_ty: &InterfaceType,2715dst: &Destination,2716) {2717let src_ty = &self.types[src_ty];2718let dst_ty = match dst_ty {2719InterfaceType::Record(r) => &self.types[*r],2720_ => panic!("expected a record"),2721};27222723// TODO: subtyping2724assert_eq!(src_ty.fields.len(), dst_ty.fields.len());27252726// First a map is made of the source fields to where they're coming2727// from (e.g. which offset or which locals). This map is keyed by the2728// fields' names2729let mut src_fields = HashMap::new();2730for (i, src) in src2731.record_field_srcs(self.types, src_ty.fields.iter().map(|f| f.ty))2732.enumerate()2733{2734let field = &src_ty.fields[i];2735src_fields.insert(&field.name, (src, &field.ty));2736}27372738// .. and next translation is performed in the order of the destination2739// fields in case the destination is the stack to ensure that the stack2740// has the fields all in the right order.2741//2742// Note that the lookup in `src_fields` is an infallible lookup which2743// will panic if the field isn't found.2744//2745// TODO: should that lookup be fallible with subtyping?2746for (i, dst) in dst2747.record_field_dsts(self.types, dst_ty.fields.iter().map(|f| f.ty))2748.enumerate()2749{2750let field = &dst_ty.fields[i];2751let (src, src_ty) = &src_fields[&field.name];2752self.translate(src_ty, src, &field.ty, &dst);2753}2754}27552756fn translate_flags(2757&mut self,2758src_ty: TypeFlagsIndex,2759src: &Source<'_>,2760dst_ty: &InterfaceType,2761dst: &Destination,2762) {2763let src_ty = &self.types[src_ty];2764let dst_ty = match dst_ty {2765InterfaceType::Flags(r) => &self.types[*r],2766_ => panic!("expected a record"),2767};27682769// TODO: subtyping2770//2771// Notably this implementation does not support reordering flags from2772// the source to the destination nor having more flags in the2773// destination. Currently this is a copy from source to destination2774// in-bulk. Otherwise reordering indices would have to have some sort of2775// fancy bit twiddling tricks or something like that.2776assert_eq!(src_ty.names, dst_ty.names);2777let cnt = src_ty.names.len();2778match FlagsSize::from_count(cnt) {2779FlagsSize::Size0 => {}2780FlagsSize::Size1 => {2781let mask = if cnt == 8 { 0xff } else { (1 << cnt) - 1 };2782self.convert_u8_mask(src, dst, mask);2783}2784FlagsSize::Size2 => {2785let mask = if cnt == 16 { 0xffff } else { (1 << cnt) - 1 };2786self.convert_u16_mask(src, dst, mask);2787}2788FlagsSize::Size4Plus(n) => {2789let srcs = src.record_field_srcs(self.types, (0..n).map(|_| InterfaceType::U32));2790let dsts = dst.record_field_dsts(self.types, (0..n).map(|_| InterfaceType::U32));2791let n = usize::from(n);2792for (i, (src, dst)) in srcs.zip(dsts).enumerate() {2793let mask = if i == n - 1 && (cnt % 32 != 0) {2794(1 << (cnt % 32)) - 12795} else {27960xffffffff2797};2798self.convert_u32_mask(&src, &dst, mask);2799}2800}2801}2802}28032804fn translate_tuple(2805&mut self,2806src_ty: TypeTupleIndex,2807src: &Source<'_>,2808dst_ty: &InterfaceType,2809dst: &Destination,2810) {2811let src_ty = &self.types[src_ty];2812let dst_ty = match dst_ty {2813InterfaceType::Tuple(t) => &self.types[*t],2814_ => panic!("expected a tuple"),2815};28162817// TODO: subtyping2818assert_eq!(src_ty.types.len(), dst_ty.types.len());28192820let srcs = src2821.record_field_srcs(self.types, src_ty.types.iter().copied())2822.zip(src_ty.types.iter());2823let dsts = dst2824.record_field_dsts(self.types, dst_ty.types.iter().copied())2825.zip(dst_ty.types.iter());2826for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) {2827self.translate(src_ty, &src, dst_ty, &dst);2828}2829}28302831fn translate_variant(2832&mut self,2833src_ty: TypeVariantIndex,2834src: &Source<'_>,2835dst_ty: &InterfaceType,2836dst: &Destination,2837) {2838let src_ty = &self.types[src_ty];2839let dst_ty = match dst_ty {2840InterfaceType::Variant(t) => &self.types[*t],2841_ => panic!("expected a variant"),2842};28432844let src_info = variant_info(self.types, src_ty.cases.iter().map(|(_, c)| c.as_ref()));2845let dst_info = variant_info(self.types, dst_ty.cases.iter().map(|(_, c)| c.as_ref()));28462847let iter = src_ty2848.cases2849.iter()2850.enumerate()2851.map(|(src_i, (src_case, src_case_ty))| {2852let dst_i = dst_ty2853.cases2854.iter()2855.position(|(c, _)| c == src_case)2856.unwrap();2857let dst_case_ty = &dst_ty.cases[dst_i];2858let src_i = u32::try_from(src_i).unwrap();2859let dst_i = u32::try_from(dst_i).unwrap();2860VariantCase {2861src_i,2862src_ty: src_case_ty.as_ref(),2863dst_i,2864dst_ty: dst_case_ty.as_ref(),2865}2866});2867self.convert_variant(src, &src_info, dst, &dst_info, iter);2868}28692870fn translate_enum(2871&mut self,2872src_ty: TypeEnumIndex,2873src: &Source<'_>,2874dst_ty: &InterfaceType,2875dst: &Destination,2876) {2877let src_ty = &self.types[src_ty];2878let dst_ty = match dst_ty {2879InterfaceType::Enum(t) => &self.types[*t],2880_ => panic!("expected an option"),2881};28822883debug_assert_eq!(src_ty.info.size, dst_ty.info.size);2884debug_assert_eq!(src_ty.names.len(), dst_ty.names.len());2885debug_assert!(2886src_ty2887.names2888.iter()2889.zip(dst_ty.names.iter())2890.all(|(a, b)| a == b)2891);28922893// Get the discriminant.2894match src {2895Source::Stack(s) => self.stack_get(&s.slice(0..1), ValType::I32),2896Source::Memory(mem) => match src_ty.info.size {2897DiscriminantSize::Size1 => self.i32_load8u(mem),2898DiscriminantSize::Size2 => self.i32_load16u(mem),2899DiscriminantSize::Size4 => self.i32_load(mem),2900},2901Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),2902}2903let tmp = self.local_tee_new_tmp(ValType::I32);29042905// Assert that the discriminant is valid.2906self.instruction(I32Const(i32::try_from(src_ty.names.len()).unwrap()));2907self.instruction(I32GtU);2908self.instruction(If(BlockType::Empty));2909self.trap(Trap::InvalidDiscriminant);2910self.instruction(End);29112912// Save the discriminant to the destination.2913match dst {2914Destination::Stack(stack, _) => {2915self.local_get_tmp(&tmp);2916self.stack_set(&stack[..1], ValType::I32)2917}2918Destination::Memory(mem) => {2919self.push_dst_addr(dst);2920self.local_get_tmp(&tmp);2921match dst_ty.info.size {2922DiscriminantSize::Size1 => self.i32_store8(mem),2923DiscriminantSize::Size2 => self.i32_store16(mem),2924DiscriminantSize::Size4 => self.i32_store(mem),2925}2926}2927Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),2928}2929self.free_temp_local(tmp);2930}29312932fn translate_option(2933&mut self,2934src_ty: TypeOptionIndex,2935src: &Source<'_>,2936dst_ty: &InterfaceType,2937dst: &Destination,2938) {2939let src_ty = &self.types[src_ty].ty;2940let dst_ty = match dst_ty {2941InterfaceType::Option(t) => &self.types[*t].ty,2942_ => panic!("expected an option"),2943};2944let src_ty = Some(src_ty);2945let dst_ty = Some(dst_ty);29462947let src_info = variant_info(self.types, [None, src_ty]);2948let dst_info = variant_info(self.types, [None, dst_ty]);29492950self.convert_variant(2951src,2952&src_info,2953dst,2954&dst_info,2955[2956VariantCase {2957src_i: 0,2958dst_i: 0,2959src_ty: None,2960dst_ty: None,2961},2962VariantCase {2963src_i: 1,2964dst_i: 1,2965src_ty,2966dst_ty,2967},2968]2969.into_iter(),2970);2971}29722973fn translate_result(2974&mut self,2975src_ty: TypeResultIndex,2976src: &Source<'_>,2977dst_ty: &InterfaceType,2978dst: &Destination,2979) {2980let src_ty = &self.types[src_ty];2981let dst_ty = match dst_ty {2982InterfaceType::Result(t) => &self.types[*t],2983_ => panic!("expected a result"),2984};29852986let src_info = variant_info(self.types, [src_ty.ok.as_ref(), src_ty.err.as_ref()]);2987let dst_info = variant_info(self.types, [dst_ty.ok.as_ref(), dst_ty.err.as_ref()]);29882989self.convert_variant(2990src,2991&src_info,2992dst,2993&dst_info,2994[2995VariantCase {2996src_i: 0,2997dst_i: 0,2998src_ty: src_ty.ok.as_ref(),2999dst_ty: dst_ty.ok.as_ref(),3000},3001VariantCase {3002src_i: 1,3003dst_i: 1,3004src_ty: src_ty.err.as_ref(),3005dst_ty: dst_ty.err.as_ref(),3006},3007]3008.into_iter(),3009);3010}30113012fn convert_variant<'c>(3013&mut self,3014src: &Source<'_>,3015src_info: &VariantInfo,3016dst: &Destination,3017dst_info: &VariantInfo,3018src_cases: impl ExactSizeIterator<Item = VariantCase<'c>>,3019) {3020// The outermost block is special since it has the result type of the3021// translation here. That will depend on the `dst`.3022let outer_block_ty = match dst {3023Destination::Stack(dst_flat, _) => match dst_flat.len() {30240 => BlockType::Empty,30251 => BlockType::Result(dst_flat[0]),3026_ => {3027let ty = self.module.core_types.function(&[], &dst_flat);3028BlockType::FunctionType(ty)3029}3030},3031Destination::Memory(_) => BlockType::Empty,3032Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),3033};3034self.instruction(Block(outer_block_ty));30353036// After the outermost block generate a new block for each of the3037// remaining cases.3038let src_cases_len = src_cases.len();3039for _ in 0..src_cases_len - 1 {3040self.instruction(Block(BlockType::Empty));3041}30423043// Generate a block for an invalid variant discriminant3044self.instruction(Block(BlockType::Empty));30453046// And generate one final block that we'll be jumping out of with the3047// `br_table`3048self.instruction(Block(BlockType::Empty));30493050// Load the discriminant3051match src {3052Source::Stack(s) => self.stack_get(&s.slice(0..1), ValType::I32),3053Source::Memory(mem) => match src_info.size {3054DiscriminantSize::Size1 => self.i32_load8u(mem),3055DiscriminantSize::Size2 => self.i32_load16u(mem),3056DiscriminantSize::Size4 => self.i32_load(mem),3057},3058Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),3059}30603061// Generate the `br_table` for the discriminant. Each case has an3062// offset of 1 to skip the trapping block.3063let mut targets = Vec::new();3064for i in 0..src_cases_len {3065targets.push((i + 1) as u32);3066}3067self.instruction(BrTable(targets[..].into(), 0));3068self.instruction(End); // end the `br_table` block30693070self.trap(Trap::InvalidDiscriminant);3071self.instruction(End); // end the "invalid discriminant" block30723073// Translate each case individually within its own block. Note that the3074// iteration order here places the first case in the innermost block3075// and the last case in the outermost block. This matches the order3076// of the jump targets in the `br_table` instruction.3077let src_cases_len = u32::try_from(src_cases_len).unwrap();3078for case in src_cases {3079let VariantCase {3080src_i,3081src_ty,3082dst_i,3083dst_ty,3084} = case;30853086// Translate the discriminant here, noting that `dst_i` may be3087// different than `src_i`.3088self.push_dst_addr(dst);3089self.instruction(I32Const(dst_i as i32));3090match dst {3091Destination::Stack(stack, _) => self.stack_set(&stack[..1], ValType::I32),3092Destination::Memory(mem) => match dst_info.size {3093DiscriminantSize::Size1 => self.i32_store8(mem),3094DiscriminantSize::Size2 => self.i32_store16(mem),3095DiscriminantSize::Size4 => self.i32_store(mem),3096},3097Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),3098}30993100let src_payload = src.payload_src(self.types, src_info, src_ty);3101let dst_payload = dst.payload_dst(self.types, dst_info, dst_ty);31023103// Translate the payload of this case using the various types from3104// the dst/src.3105match (src_ty, dst_ty) {3106(Some(src_ty), Some(dst_ty)) => {3107self.translate(src_ty, &src_payload, dst_ty, &dst_payload);3108}3109(None, None) => {}3110_ => unimplemented!(),3111}31123113// If the results of this translation were placed on the stack then3114// the stack values may need to be padded with more zeros due to3115// this particular case being possibly smaller than the entire3116// variant. That's handled here by pushing remaining zeros after3117// accounting for the discriminant pushed as well as the results of3118// this individual payload.3119if let Destination::Stack(payload_results, _) = dst_payload {3120if let Destination::Stack(dst_results, _) = dst {3121let remaining = &dst_results[1..][payload_results.len()..];3122for ty in remaining {3123match ty {3124ValType::I32 => self.instruction(I32Const(0)),3125ValType::I64 => self.instruction(I64Const(0)),3126ValType::F32 => self.instruction(F32Const(0.0.into())),3127ValType::F64 => self.instruction(F64Const(0.0.into())),3128_ => unreachable!(),3129}3130}3131}3132}31333134// Branch to the outermost block. Note that this isn't needed for3135// the outermost case since it simply falls through.3136if src_i != src_cases_len - 1 {3137self.instruction(Br(src_cases_len - src_i - 1));3138}3139self.instruction(End); // end this case's block3140}3141}31423143fn translate_future(3144&mut self,3145src_ty: TypeFutureTableIndex,3146src: &Source<'_>,3147dst_ty: &InterfaceType,3148dst: &Destination,3149) {3150let dst_ty = match dst_ty {3151InterfaceType::Future(t) => *t,3152_ => panic!("expected a `Future`"),3153};3154let transfer = self.module.import_future_transfer();3155self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);3156}31573158fn translate_stream(3159&mut self,3160src_ty: TypeStreamTableIndex,3161src: &Source<'_>,3162dst_ty: &InterfaceType,3163dst: &Destination,3164) {3165let dst_ty = match dst_ty {3166InterfaceType::Stream(t) => *t,3167_ => panic!("expected a `Stream`"),3168};3169let transfer = self.module.import_stream_transfer();3170self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);3171}31723173fn translate_error_context(3174&mut self,3175src_ty: TypeComponentLocalErrorContextTableIndex,3176src: &Source<'_>,3177dst_ty: &InterfaceType,3178dst: &Destination,3179) {3180let dst_ty = match dst_ty {3181InterfaceType::ErrorContext(t) => *t,3182_ => panic!("expected an `ErrorContext`"),3183};3184let transfer = self.module.import_error_context_transfer();3185self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);3186}31873188fn translate_own(3189&mut self,3190src_ty: TypeResourceTableIndex,3191src: &Source<'_>,3192dst_ty: &InterfaceType,3193dst: &Destination,3194) {3195let dst_ty = match dst_ty {3196InterfaceType::Own(t) => *t,3197_ => panic!("expected an `Own`"),3198};3199let transfer = self.module.import_resource_transfer_own();3200self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);3201}32023203fn translate_borrow(3204&mut self,3205src_ty: TypeResourceTableIndex,3206src: &Source<'_>,3207dst_ty: &InterfaceType,3208dst: &Destination,3209) {3210let dst_ty = match dst_ty {3211InterfaceType::Borrow(t) => *t,3212_ => panic!("expected an `Borrow`"),3213};32143215let transfer = self.module.import_resource_transfer_borrow();3216self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);3217}32183219/// Translates the index `src`, which resides in the table `src_ty`, into3220/// and index within `dst_ty` and is stored at `dst`.3221///3222/// Actual translation of the index happens in a wasmtime libcall, which a3223/// cranelift-generated trampoline to satisfy this import will call. The3224/// `transfer` function is an imported function which takes the src, src_ty,3225/// and dst_ty, and returns the dst index.3226fn translate_handle(3227&mut self,3228src_ty: u32,3229src: &Source<'_>,3230dst_ty: u32,3231dst: &Destination,3232transfer: FuncIndex,3233) {3234self.push_dst_addr(dst);3235match src {3236Source::Memory(mem) => self.i32_load(mem),3237Source::Stack(stack) => self.stack_get(stack, ValType::I32),3238Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),3239}3240self.instruction(I32Const(src_ty as i32));3241self.instruction(I32Const(dst_ty as i32));3242self.instruction(Call(transfer.as_u32()));3243match dst {3244Destination::Memory(mem) => self.i32_store(mem),3245Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),3246Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),3247}3248}32493250fn trap_if_not_flag(&mut self, flags_global: GlobalIndex, flag_to_test: i32, trap: Trap) {3251self.instruction(GlobalGet(flags_global.as_u32()));3252self.instruction(I32Const(flag_to_test));3253self.instruction(I32And);3254self.instruction(I32Eqz);3255self.instruction(If(BlockType::Empty));3256self.trap(trap);3257self.instruction(End);3258}32593260fn assert_not_flag(&mut self, flags_global: GlobalIndex, flag_to_test: i32, msg: &'static str) {3261self.instruction(GlobalGet(flags_global.as_u32()));3262self.instruction(I32Const(flag_to_test));3263self.instruction(I32And);3264self.instruction(If(BlockType::Empty));3265self.trap(Trap::AssertFailed(msg));3266self.instruction(End);3267}32683269fn set_flag(&mut self, flags_global: GlobalIndex, flag_to_set: i32, value: bool) {3270self.instruction(GlobalGet(flags_global.as_u32()));3271if value {3272self.instruction(I32Const(flag_to_set));3273self.instruction(I32Or);3274} else {3275self.instruction(I32Const(!flag_to_set));3276self.instruction(I32And);3277}3278self.instruction(GlobalSet(flags_global.as_u32()));3279}32803281fn verify_aligned(&mut self, opts: &LinearMemoryOptions, addr_local: u32, align: u32) {3282// If the alignment is 1 then everything is trivially aligned and the3283// check can be omitted.3284if align == 1 {3285return;3286}3287self.instruction(LocalGet(addr_local));3288assert!(align.is_power_of_two());3289self.ptr_uconst(opts, align - 1);3290self.ptr_and(opts);3291self.ptr_if(opts, BlockType::Empty);3292self.trap(Trap::UnalignedPointer);3293self.instruction(End);3294}32953296fn assert_aligned(&mut self, ty: &InterfaceType, mem: &Memory) {3297let mem_opts = mem.mem_opts();3298if !self.module.debug {3299return;3300}3301let align = self.types.align(mem_opts, ty);3302if align == 1 {3303return;3304}3305assert!(align.is_power_of_two());3306self.instruction(LocalGet(mem.addr.idx));3307self.ptr_uconst(mem_opts, mem.offset);3308self.ptr_add(mem_opts);3309self.ptr_uconst(mem_opts, align - 1);3310self.ptr_and(mem_opts);3311self.ptr_if(mem_opts, BlockType::Empty);3312self.trap(Trap::AssertFailed("pointer not aligned"));3313self.instruction(End);3314}33153316fn malloc<'c>(&mut self, opts: &'c Options, size: MallocSize, align: u32) -> Memory<'c> {3317match &opts.data_model {3318DataModel::Gc {} => todo!("CM+GC"),3319DataModel::LinearMemory(mem_opts) => {3320let realloc = mem_opts.realloc.unwrap();3321self.ptr_uconst(mem_opts, 0);3322self.ptr_uconst(mem_opts, 0);3323self.ptr_uconst(mem_opts, align);3324match size {3325MallocSize::Const(size) => self.ptr_uconst(mem_opts, size),3326MallocSize::Local(idx) => self.instruction(LocalGet(idx)),3327}3328self.instruction(Call(realloc.as_u32()));3329let addr = self.local_set_new_tmp(mem_opts.ptr());3330self.memory_operand(opts, addr, align)3331}3332}3333}33343335fn memory_operand<'c>(&mut self, opts: &'c Options, addr: TempLocal, align: u32) -> Memory<'c> {3336let ret = Memory {3337addr,3338offset: 0,3339opts,3340};3341self.verify_aligned(opts.data_model.unwrap_memory(), ret.addr.idx, align);3342ret3343}33443345/// Generates a new local in this function of the `ty` specified,3346/// initializing it with the top value on the current wasm stack.3347///3348/// The returned `TempLocal` must be freed after it is finished with3349/// `free_temp_local`.3350fn local_tee_new_tmp(&mut self, ty: ValType) -> TempLocal {3351self.gen_temp_local(ty, LocalTee)3352}33533354/// Same as `local_tee_new_tmp` but initializes the local with `LocalSet`3355/// instead of `LocalTee`.3356fn local_set_new_tmp(&mut self, ty: ValType) -> TempLocal {3357self.gen_temp_local(ty, LocalSet)3358}33593360fn local_get_tmp(&mut self, local: &TempLocal) {3361self.instruction(LocalGet(local.idx));3362}33633364fn gen_temp_local(&mut self, ty: ValType, insn: fn(u32) -> Instruction<'static>) -> TempLocal {3365// First check to see if any locals are available in this function which3366// were previously generated but are no longer in use.3367if let Some(idx) = self.free_locals.get_mut(&ty).and_then(|v| v.pop()) {3368self.instruction(insn(idx));3369return TempLocal {3370ty,3371idx,3372needs_free: true,3373};3374}33753376// Failing that generate a fresh new local.3377let locals = &mut self.module.funcs[self.result].locals;3378match locals.last_mut() {3379Some((cnt, prev_ty)) if ty == *prev_ty => *cnt += 1,3380_ => locals.push((1, ty)),3381}3382self.nlocals += 1;3383let idx = self.nlocals - 1;3384self.instruction(insn(idx));3385TempLocal {3386ty,3387idx,3388needs_free: true,3389}3390}33913392/// Used to release a `TempLocal` from a particular lexical scope to allow3393/// its possible reuse in later scopes.3394fn free_temp_local(&mut self, mut local: TempLocal) {3395assert!(local.needs_free);3396self.free_locals3397.entry(local.ty)3398.or_insert(Vec::new())3399.push(local.idx);3400local.needs_free = false;3401}34023403fn instruction(&mut self, instr: Instruction) {3404instr.encode(&mut self.code);3405}34063407fn trap(&mut self, trap: Trap) {3408self.traps.push((self.code.len(), trap));3409self.instruction(Unreachable);3410}34113412/// Flushes out the current `code` instructions (and `traps` if there are3413/// any) into the destination function.3414///3415/// This is a noop if no instructions have been encoded yet.3416fn flush_code(&mut self) {3417if self.code.is_empty() {3418return;3419}3420self.module.funcs[self.result].body.push(Body::Raw(3421mem::take(&mut self.code),3422mem::take(&mut self.traps),3423));3424}34253426fn finish(mut self) {3427// Append the final `end` instruction which all functions require, and3428// then empty out the temporary buffer in `Compiler`.3429self.instruction(End);3430self.flush_code();34313432// Flag the function as "done" which helps with an assert later on in3433// emission that everything was eventually finished.3434self.module.funcs[self.result].filled_in = true;3435}34363437/// Fetches the value contained with the local specified by `stack` and3438/// converts it to `dst_ty`.3439///3440/// This is only intended for use in primitive operations where `stack` is3441/// guaranteed to have only one local. The type of the local on the stack is3442/// then converted to `dst_ty` appropriately. Note that the types may be3443/// different due to the "flattening" of variant types.3444fn stack_get(&mut self, stack: &Stack<'_>, dst_ty: ValType) {3445assert_eq!(stack.locals.len(), 1);3446let (idx, src_ty) = stack.locals[0];3447self.instruction(LocalGet(idx));3448match (src_ty, dst_ty) {3449(ValType::I32, ValType::I32)3450| (ValType::I64, ValType::I64)3451| (ValType::F32, ValType::F32)3452| (ValType::F64, ValType::F64) => {}34533454(ValType::I32, ValType::F32) => self.instruction(F32ReinterpretI32),3455(ValType::I64, ValType::I32) => {3456self.assert_i64_upper_bits_not_set(idx);3457self.instruction(I32WrapI64);3458}3459(ValType::I64, ValType::F64) => self.instruction(F64ReinterpretI64),3460(ValType::I64, ValType::F32) => {3461self.assert_i64_upper_bits_not_set(idx);3462self.instruction(I32WrapI64);3463self.instruction(F32ReinterpretI32);3464}34653466// should not be possible given the `join` function for variants3467(ValType::I32, ValType::I64)3468| (ValType::I32, ValType::F64)3469| (ValType::F32, ValType::I32)3470| (ValType::F32, ValType::I64)3471| (ValType::F32, ValType::F64)3472| (ValType::F64, ValType::I32)3473| (ValType::F64, ValType::I64)3474| (ValType::F64, ValType::F32)34753476// not used in the component model3477| (ValType::Ref(_), _)3478| (_, ValType::Ref(_))3479| (ValType::V128, _)3480| (_, ValType::V128) => {3481panic!("cannot get {dst_ty:?} from {src_ty:?} local");3482}3483}3484}34853486fn assert_i64_upper_bits_not_set(&mut self, local: u32) {3487if !self.module.debug {3488return;3489}3490self.instruction(LocalGet(local));3491self.instruction(I64Const(32));3492self.instruction(I64ShrU);3493self.instruction(I32WrapI64);3494self.instruction(If(BlockType::Empty));3495self.trap(Trap::AssertFailed("upper bits are unexpectedly set"));3496self.instruction(End);3497}34983499/// Converts the top value on the WebAssembly stack which has type3500/// `src_ty` to `dst_tys[0]`.3501///3502/// This is only intended for conversion of primitives where the `dst_tys`3503/// list is known to be of length 1.3504fn stack_set(&mut self, dst_tys: &[ValType], src_ty: ValType) {3505assert_eq!(dst_tys.len(), 1);3506let dst_ty = dst_tys[0];3507match (src_ty, dst_ty) {3508(ValType::I32, ValType::I32)3509| (ValType::I64, ValType::I64)3510| (ValType::F32, ValType::F32)3511| (ValType::F64, ValType::F64) => {}35123513(ValType::F32, ValType::I32) => self.instruction(I32ReinterpretF32),3514(ValType::I32, ValType::I64) => self.instruction(I64ExtendI32U),3515(ValType::F64, ValType::I64) => self.instruction(I64ReinterpretF64),3516(ValType::F32, ValType::I64) => {3517self.instruction(I32ReinterpretF32);3518self.instruction(I64ExtendI32U);3519}35203521// should not be possible given the `join` function for variants3522(ValType::I64, ValType::I32)3523| (ValType::F64, ValType::I32)3524| (ValType::I32, ValType::F32)3525| (ValType::I64, ValType::F32)3526| (ValType::F64, ValType::F32)3527| (ValType::I32, ValType::F64)3528| (ValType::I64, ValType::F64)3529| (ValType::F32, ValType::F64)35303531// not used in the component model3532| (ValType::Ref(_), _)3533| (_, ValType::Ref(_))3534| (ValType::V128, _)3535| (_, ValType::V128) => {3536panic!("cannot get {dst_ty:?} from {src_ty:?} local");3537}3538}3539}35403541fn i32_load8u(&mut self, mem: &Memory) {3542self.instruction(LocalGet(mem.addr.idx));3543self.instruction(I32Load8U(mem.memarg(0)));3544}35453546fn i32_load8s(&mut self, mem: &Memory) {3547self.instruction(LocalGet(mem.addr.idx));3548self.instruction(I32Load8S(mem.memarg(0)));3549}35503551fn i32_load16u(&mut self, mem: &Memory) {3552self.instruction(LocalGet(mem.addr.idx));3553self.instruction(I32Load16U(mem.memarg(1)));3554}35553556fn i32_load16s(&mut self, mem: &Memory) {3557self.instruction(LocalGet(mem.addr.idx));3558self.instruction(I32Load16S(mem.memarg(1)));3559}35603561fn i32_load(&mut self, mem: &Memory) {3562self.instruction(LocalGet(mem.addr.idx));3563self.instruction(I32Load(mem.memarg(2)));3564}35653566fn i64_load(&mut self, mem: &Memory) {3567self.instruction(LocalGet(mem.addr.idx));3568self.instruction(I64Load(mem.memarg(3)));3569}35703571fn ptr_load(&mut self, mem: &Memory) {3572if mem.mem_opts().memory64 {3573self.i64_load(mem);3574} else {3575self.i32_load(mem);3576}3577}35783579fn ptr_add(&mut self, opts: &LinearMemoryOptions) {3580if opts.memory64 {3581self.instruction(I64Add);3582} else {3583self.instruction(I32Add);3584}3585}35863587fn ptr_sub(&mut self, opts: &LinearMemoryOptions) {3588if opts.memory64 {3589self.instruction(I64Sub);3590} else {3591self.instruction(I32Sub);3592}3593}35943595fn ptr_mul(&mut self, opts: &LinearMemoryOptions) {3596if opts.memory64 {3597self.instruction(I64Mul);3598} else {3599self.instruction(I32Mul);3600}3601}36023603fn ptr_ge_u(&mut self, opts: &LinearMemoryOptions) {3604if opts.memory64 {3605self.instruction(I64GeU);3606} else {3607self.instruction(I32GeU);3608}3609}36103611fn ptr_lt_u(&mut self, opts: &LinearMemoryOptions) {3612if opts.memory64 {3613self.instruction(I64LtU);3614} else {3615self.instruction(I32LtU);3616}3617}36183619fn ptr_shl(&mut self, opts: &LinearMemoryOptions) {3620if opts.memory64 {3621self.instruction(I64Shl);3622} else {3623self.instruction(I32Shl);3624}3625}36263627fn ptr_eqz(&mut self, opts: &LinearMemoryOptions) {3628if opts.memory64 {3629self.instruction(I64Eqz);3630} else {3631self.instruction(I32Eqz);3632}3633}36343635fn ptr_uconst(&mut self, opts: &LinearMemoryOptions, val: u32) {3636if opts.memory64 {3637self.instruction(I64Const(val.into()));3638} else {3639self.instruction(I32Const(val as i32));3640}3641}36423643fn ptr_iconst(&mut self, opts: &LinearMemoryOptions, val: i32) {3644if opts.memory64 {3645self.instruction(I64Const(val.into()));3646} else {3647self.instruction(I32Const(val));3648}3649}36503651fn ptr_eq(&mut self, opts: &LinearMemoryOptions) {3652if opts.memory64 {3653self.instruction(I64Eq);3654} else {3655self.instruction(I32Eq);3656}3657}36583659fn ptr_ne(&mut self, opts: &LinearMemoryOptions) {3660if opts.memory64 {3661self.instruction(I64Ne);3662} else {3663self.instruction(I32Ne);3664}3665}36663667fn ptr_and(&mut self, opts: &LinearMemoryOptions) {3668if opts.memory64 {3669self.instruction(I64And);3670} else {3671self.instruction(I32And);3672}3673}36743675fn ptr_or(&mut self, opts: &LinearMemoryOptions) {3676if opts.memory64 {3677self.instruction(I64Or);3678} else {3679self.instruction(I32Or);3680}3681}36823683fn ptr_xor(&mut self, opts: &LinearMemoryOptions) {3684if opts.memory64 {3685self.instruction(I64Xor);3686} else {3687self.instruction(I32Xor);3688}3689}36903691fn ptr_if(&mut self, opts: &LinearMemoryOptions, ty: BlockType) {3692if opts.memory64 {3693self.instruction(I64Const(0));3694self.instruction(I64Ne);3695}3696self.instruction(If(ty));3697}36983699fn ptr_br_if(&mut self, opts: &LinearMemoryOptions, depth: u32) {3700if opts.memory64 {3701self.instruction(I64Const(0));3702self.instruction(I64Ne);3703}3704self.instruction(BrIf(depth));3705}37063707fn f32_load(&mut self, mem: &Memory) {3708self.instruction(LocalGet(mem.addr.idx));3709self.instruction(F32Load(mem.memarg(2)));3710}37113712fn f64_load(&mut self, mem: &Memory) {3713self.instruction(LocalGet(mem.addr.idx));3714self.instruction(F64Load(mem.memarg(3)));3715}37163717fn push_dst_addr(&mut self, dst: &Destination) {3718if let Destination::Memory(mem) = dst {3719self.instruction(LocalGet(mem.addr.idx));3720}3721}37223723fn i32_store8(&mut self, mem: &Memory) {3724self.instruction(I32Store8(mem.memarg(0)));3725}37263727fn i32_store16(&mut self, mem: &Memory) {3728self.instruction(I32Store16(mem.memarg(1)));3729}37303731fn i32_store(&mut self, mem: &Memory) {3732self.instruction(I32Store(mem.memarg(2)));3733}37343735fn i64_store(&mut self, mem: &Memory) {3736self.instruction(I64Store(mem.memarg(3)));3737}37383739fn ptr_store(&mut self, mem: &Memory) {3740if mem.mem_opts().memory64 {3741self.i64_store(mem);3742} else {3743self.i32_store(mem);3744}3745}37463747fn f32_store(&mut self, mem: &Memory) {3748self.instruction(F32Store(mem.memarg(2)));3749}37503751fn f64_store(&mut self, mem: &Memory) {3752self.instruction(F64Store(mem.memarg(3)));3753}3754}37553756impl<'a> Source<'a> {3757/// Given this `Source` returns an iterator over the `Source` for each of3758/// the component `fields` specified.3759///3760/// This will automatically slice stack-based locals to the appropriate3761/// width for each component type and additionally calculate the appropriate3762/// offset for each memory-based type.3763fn record_field_srcs<'b>(3764&'b self,3765types: &'b ComponentTypesBuilder,3766fields: impl IntoIterator<Item = InterfaceType> + 'b,3767) -> impl Iterator<Item = Source<'a>> + 'b3768where3769'a: 'b,3770{3771let mut offset = 0;3772fields.into_iter().map(move |ty| match self {3773Source::Memory(mem) => {3774let mem = next_field_offset(&mut offset, types, &ty, mem);3775Source::Memory(mem)3776}3777Source::Stack(stack) => {3778let cnt = types.flat_types(&ty).unwrap().len() as u32;3779offset += cnt;3780Source::Stack(stack.slice((offset - cnt) as usize..offset as usize))3781}3782Source::Struct(_) => todo!(),3783Source::Array(_) => todo!(),3784})3785}37863787/// Returns the corresponding discriminant source and payload source f3788fn payload_src(3789&self,3790types: &ComponentTypesBuilder,3791info: &VariantInfo,3792case: Option<&InterfaceType>,3793) -> Source<'a> {3794match self {3795Source::Stack(s) => {3796let flat_len = match case {3797Some(case) => types.flat_types(case).unwrap().len(),3798None => 0,3799};3800Source::Stack(s.slice(1..s.locals.len()).slice(0..flat_len))3801}3802Source::Memory(mem) => {3803let mem = if mem.mem_opts().memory64 {3804mem.bump(info.payload_offset64)3805} else {3806mem.bump(info.payload_offset32)3807};3808Source::Memory(mem)3809}3810Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),3811}3812}38133814fn opts(&self) -> &'a Options {3815match self {3816Source::Stack(s) => s.opts,3817Source::Memory(mem) => mem.opts,3818Source::Struct(s) => s.opts,3819Source::Array(a) => a.opts,3820}3821}3822}38233824impl<'a> Destination<'a> {3825/// Same as `Source::record_field_srcs` but for destinations.3826fn record_field_dsts<'b, I>(3827&'b self,3828types: &'b ComponentTypesBuilder,3829fields: I,3830) -> impl Iterator<Item = Destination<'b>> + use<'b, I>3831where3832'a: 'b,3833I: IntoIterator<Item = InterfaceType> + 'b,3834{3835let mut offset = 0;3836fields.into_iter().map(move |ty| match self {3837Destination::Memory(mem) => {3838let mem = next_field_offset(&mut offset, types, &ty, mem);3839Destination::Memory(mem)3840}3841Destination::Stack(s, opts) => {3842let cnt = types.flat_types(&ty).unwrap().len() as u32;3843offset += cnt;3844Destination::Stack(&s[(offset - cnt) as usize..offset as usize], opts)3845}3846Destination::Struct(_) => todo!(),3847Destination::Array(_) => todo!(),3848})3849}38503851/// Returns the corresponding discriminant source and payload source f3852fn payload_dst(3853&self,3854types: &ComponentTypesBuilder,3855info: &VariantInfo,3856case: Option<&InterfaceType>,3857) -> Destination<'_> {3858match self {3859Destination::Stack(s, opts) => {3860let flat_len = match case {3861Some(case) => types.flat_types(case).unwrap().len(),3862None => 0,3863};3864Destination::Stack(&s[1..][..flat_len], opts)3865}3866Destination::Memory(mem) => {3867let mem = if mem.mem_opts().memory64 {3868mem.bump(info.payload_offset64)3869} else {3870mem.bump(info.payload_offset32)3871};3872Destination::Memory(mem)3873}3874Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),3875}3876}38773878fn opts(&self) -> &'a Options {3879match self {3880Destination::Stack(_, opts) => opts,3881Destination::Memory(mem) => mem.opts,3882Destination::Struct(s) => s.opts,3883Destination::Array(a) => a.opts,3884}3885}3886}38873888fn next_field_offset<'a>(3889offset: &mut u32,3890types: &ComponentTypesBuilder,3891field: &InterfaceType,3892mem: &Memory<'a>,3893) -> Memory<'a> {3894let abi = types.canonical_abi(field);3895let offset = if mem.mem_opts().memory64 {3896abi.next_field64(offset)3897} else {3898abi.next_field32(offset)3899};3900mem.bump(offset)3901}39023903impl<'a> Memory<'a> {3904fn memarg(&self, align: u32) -> MemArg {3905MemArg {3906offset: u64::from(self.offset),3907align,3908memory_index: self.mem_opts().memory.unwrap().as_u32(),3909}3910}39113912fn bump(&self, offset: u32) -> Memory<'a> {3913Memory {3914opts: self.opts,3915addr: TempLocal::new(self.addr.idx, self.addr.ty),3916offset: self.offset + offset,3917}3918}3919}39203921impl<'a> Stack<'a> {3922fn slice(&self, range: Range<usize>) -> Stack<'a> {3923Stack {3924locals: &self.locals[range],3925opts: self.opts,3926}3927}3928}39293930struct VariantCase<'a> {3931src_i: u32,3932src_ty: Option<&'a InterfaceType>,3933dst_i: u32,3934dst_ty: Option<&'a InterfaceType>,3935}39363937fn variant_info<'a, I>(types: &ComponentTypesBuilder, cases: I) -> VariantInfo3938where3939I: IntoIterator<Item = Option<&'a InterfaceType>>,3940I::IntoIter: ExactSizeIterator,3941{3942VariantInfo::new(3943cases3944.into_iter()3945.map(|ty| ty.map(|ty| types.canonical_abi(ty))),3946)3947.03948}39493950enum MallocSize {3951Const(u32),3952Local(u32),3953}39543955struct WasmString<'a> {3956ptr: TempLocal,3957len: TempLocal,3958opts: &'a Options,3959}39603961struct TempLocal {3962idx: u32,3963ty: ValType,3964needs_free: bool,3965}39663967impl TempLocal {3968fn new(idx: u32, ty: ValType) -> TempLocal {3969TempLocal {3970idx,3971ty,3972needs_free: false,3973}3974}3975}39763977impl std::ops::Drop for TempLocal {3978fn drop(&mut self) {3979if self.needs_free {3980panic!("temporary local not free'd");3981}3982}3983}39843985impl From<FlatType> for ValType {3986fn from(ty: FlatType) -> ValType {3987match ty {3988FlatType::I32 => ValType::I32,3989FlatType::I64 => ValType::I64,3990FlatType::F32 => ValType::F32,3991FlatType::F64 => ValType::F64,3992}3993}3994}399539963997