Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/environ/src/fact/trampoline.rs
1692 views
1
//! Low-level compilation of an fused adapter function.
2
//!
3
//! This module is tasked with the top-level `compile` function which creates a
4
//! single WebAssembly function which will perform the steps of the fused
5
//! adapter for an `AdapterData` provided. This is the "meat" of compilation
6
//! where the validation of the canonical ABI or similar all happens to
7
//! translate arguments from one module to another.
8
//!
9
//! ## Traps and their ordering
10
//!
11
//! Currently this compiler is pretty "loose" about the ordering of precisely
12
//! what trap happens where. The main reason for this is that to core wasm all
13
//! traps are the same and for fused adapters if a trap happens no intermediate
14
//! side effects are visible (as designed by the canonical ABI itself). For this
15
//! it's important to note that some of the precise choices of control flow here
16
//! can be somewhat arbitrary, an intentional decision.
17
18
use crate::component::{
19
CanonicalAbiInfo, ComponentTypesBuilder, FLAG_MAY_ENTER, FLAG_MAY_LEAVE, FixedEncoding as FE,
20
FlatType, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, PREPARE_ASYNC_NO_RESULT,
21
PREPARE_ASYNC_WITH_RESULT, START_FLAG_ASYNC_CALLEE, StringEncoding, Transcode,
22
TypeComponentLocalErrorContextTableIndex, TypeEnumIndex, TypeFlagsIndex, TypeFutureTableIndex,
23
TypeListIndex, TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex, TypeResultIndex,
24
TypeStreamTableIndex, TypeTupleIndex, TypeVariantIndex, VariantInfo,
25
};
26
use crate::fact::signature::Signature;
27
use crate::fact::transcode::Transcoder;
28
use crate::fact::traps::Trap;
29
use crate::fact::{
30
AdapterData, Body, Function, FunctionId, Helper, HelperLocation, HelperType,
31
LinearMemoryOptions, Module, Options,
32
};
33
use crate::prelude::*;
34
use crate::{FuncIndex, GlobalIndex};
35
use std::collections::HashMap;
36
use std::mem;
37
use std::ops::Range;
38
use wasm_encoder::{BlockType, Encode, Instruction, Instruction::*, MemArg, ValType};
39
use wasmtime_component_util::{DiscriminantSize, FlagsSize};
40
41
use super::DataModel;
42
43
const MAX_STRING_BYTE_LENGTH: u32 = 1 << 31;
44
const UTF16_TAG: u32 = 1 << 31;
45
46
/// This value is arbitrarily chosen and should be fine to change at any time,
47
/// it just seemed like a halfway reasonable starting point.
48
const INITIAL_FUEL: usize = 1_000;
49
50
struct Compiler<'a, 'b> {
51
types: &'a ComponentTypesBuilder,
52
module: &'b mut Module<'a>,
53
result: FunctionId,
54
55
/// The encoded WebAssembly function body so far, not including locals.
56
code: Vec<u8>,
57
58
/// Total number of locals generated so far.
59
nlocals: u32,
60
61
/// Locals partitioned by type which are not currently in use.
62
free_locals: HashMap<ValType, Vec<u32>>,
63
64
/// Metadata about all `unreachable` trap instructions in this function and
65
/// what the trap represents. The offset within `self.code` is recorded as
66
/// well.
67
traps: Vec<(usize, Trap)>,
68
69
/// A heuristic which is intended to limit the size of a generated function
70
/// to a certain maximum to avoid generating arbitrarily large functions.
71
///
72
/// This fuel counter is decremented each time `translate` is called and
73
/// when fuel is entirely consumed further translations, if necessary, will
74
/// be done through calls to other functions in the module. This is intended
75
/// to be a heuristic to split up the main function into theoretically
76
/// reusable portions.
77
fuel: usize,
78
79
/// Indicates whether an "enter call" should be emitted in the generated
80
/// function with a call to `Resource{Enter,Exit}Call` at the beginning and
81
/// end of the function for tracking of information related to borrowed
82
/// resources.
83
emit_resource_call: bool,
84
}
85
86
pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) {
87
fn compiler<'a, 'b>(
88
module: &'b mut Module<'a>,
89
adapter: &AdapterData,
90
) -> (Compiler<'a, 'b>, Signature, Signature) {
91
let lower_sig = module.types.signature(&adapter.lower);
92
let lift_sig = module.types.signature(&adapter.lift);
93
let ty = module
94
.core_types
95
.function(&lower_sig.params, &lower_sig.results);
96
let result = module
97
.funcs
98
.push(Function::new(Some(adapter.name.clone()), ty));
99
100
// If this type signature contains any borrowed resources then invocations
101
// of enter/exit call for resource-related metadata tracking must be used.
102
// It shouldn't matter whether the lower/lift signature is used here as both
103
// should return the same answer.
104
let emit_resource_call = module.types.contains_borrow_resource(&adapter.lower);
105
assert_eq!(
106
emit_resource_call,
107
module.types.contains_borrow_resource(&adapter.lift)
108
);
109
110
(
111
Compiler::new(
112
module,
113
result,
114
lower_sig.params.len() as u32,
115
emit_resource_call,
116
),
117
lower_sig,
118
lift_sig,
119
)
120
}
121
122
// This closure compiles a function to be exported to the host which host to
123
// lift the parameters from the caller and lower them to the callee.
124
//
125
// This allows the host to delay copying the parameters until the callee
126
// signals readiness by clearing its backpressure flag.
127
let async_start_adapter = |module: &mut Module| {
128
let sig = module
129
.types
130
.async_start_signature(&adapter.lower, &adapter.lift);
131
let ty = module.core_types.function(&sig.params, &sig.results);
132
let result = module.funcs.push(Function::new(
133
Some(format!("[async-start]{}", adapter.name)),
134
ty,
135
));
136
137
Compiler::new(module, result, sig.params.len() as u32, false)
138
.compile_async_start_adapter(adapter, &sig);
139
140
result
141
};
142
143
// This closure compiles a function to be exported by the adapter module and
144
// called by the host to lift the results from the callee and lower them to
145
// the caller.
146
//
147
// Given that async-lifted exports return their results via the
148
// `task.return` intrinsic, the host will need to copy the results from
149
// callee to caller when that intrinsic is called rather than when the
150
// callee task fully completes (which may happen much later).
151
let async_return_adapter = |module: &mut Module| {
152
let sig = module
153
.types
154
.async_return_signature(&adapter.lower, &adapter.lift);
155
let ty = module.core_types.function(&sig.params, &sig.results);
156
let result = module.funcs.push(Function::new(
157
Some(format!("[async-return]{}", adapter.name)),
158
ty,
159
));
160
161
Compiler::new(module, result, sig.params.len() as u32, false)
162
.compile_async_return_adapter(adapter, &sig);
163
164
result
165
};
166
167
match (adapter.lower.options.async_, adapter.lift.options.async_) {
168
(false, false) => {
169
// We can adapt sync->sync case with only minimal use of intrinsics,
170
// e.g. resource enter and exit calls as needed.
171
let (compiler, lower_sig, lift_sig) = compiler(module, adapter);
172
compiler.compile_sync_to_sync_adapter(adapter, &lower_sig, &lift_sig)
173
}
174
(true, true) => {
175
// In the async->async case, we must compile a couple of helper functions:
176
//
177
// - `async-start`: copies the parameters from the caller to the callee
178
// - `async-return`: copies the result from the callee to the caller
179
//
180
// Unlike synchronous calls, the above operations are asynchronous
181
// and subject to backpressure. If the callee is not yet ready to
182
// handle a new call, the `async-start` function will not be called
183
// immediately. Instead, control will return to the caller,
184
// allowing it to do other work while waiting for this call to make
185
// progress. Once the callee indicates it is ready, `async-start`
186
// will be called, and sometime later (possibly after various task
187
// switch events), when the callee has produced a result, it will
188
// call `async-return` via the `task.return` intrinsic, at which
189
// point a `STATUS_RETURNED` event will be delivered to the caller.
190
let start = async_start_adapter(module);
191
let return_ = async_return_adapter(module);
192
let (compiler, lower_sig, lift_sig) = compiler(module, adapter);
193
compiler.compile_async_to_async_adapter(
194
adapter,
195
start,
196
return_,
197
i32::try_from(lift_sig.params.len()).unwrap(),
198
&lower_sig,
199
);
200
}
201
(false, true) => {
202
// Like the async->async case above, for the sync->async case we
203
// also need `async-start` and `async-return` helper functions to
204
// allow the callee to asynchronously "pull" the parameters and
205
// "push" the results when it is ready.
206
//
207
// However, since the caller is using the synchronous ABI, the
208
// parameters may have been passed via the stack rather than linear
209
// memory. In that case, we pass them to the host to store in a
210
// task-local location temporarily in the case of backpressure.
211
// Similarly, the host will also temporarily store the results that
212
// the callee provides to `async-return` until it is ready to resume
213
// the caller.
214
let start = async_start_adapter(module);
215
let return_ = async_return_adapter(module);
216
let (compiler, lower_sig, lift_sig) = compiler(module, adapter);
217
compiler.compile_sync_to_async_adapter(
218
adapter,
219
start,
220
return_,
221
i32::try_from(lift_sig.params.len()).unwrap(),
222
&lower_sig,
223
);
224
}
225
(true, false) => {
226
// As with the async->async and sync->async cases above, for the
227
// async->sync case we use `async-start` and `async-return` helper
228
// functions. Here, those functions allow the host to enforce
229
// backpressure in the case where the callee instance already has
230
// another synchronous call in progress, in which case we can't
231
// start a new one until the current one (and any others already
232
// waiting in line behind it) has completed.
233
//
234
// In the case of backpressure, we'll return control to the caller
235
// immediately so it can do other work. Later, once the callee is
236
// ready, the host will call the `async-start` function to retrieve
237
// the parameters and pass them to the callee. At that point, the
238
// callee may block on a host call, at which point the host will
239
// suspend the fiber it is running on and allow the caller (or any
240
// other ready instance) to run concurrently with the blocked
241
// callee. Once the callee finally returns, the host will call the
242
// `async-return` function to write the result to the caller's
243
// linear memory and deliver a `STATUS_RETURNED` event to the
244
// caller.
245
let lift_sig = module.types.signature(&adapter.lift);
246
let start = async_start_adapter(module);
247
let return_ = async_return_adapter(module);
248
let (compiler, lower_sig, ..) = compiler(module, adapter);
249
compiler.compile_async_to_sync_adapter(
250
adapter,
251
start,
252
return_,
253
i32::try_from(lift_sig.params.len()).unwrap(),
254
i32::try_from(lift_sig.results.len()).unwrap(),
255
&lower_sig,
256
);
257
}
258
}
259
}
260
261
/// Compiles a helper function as specified by the `Helper` configuration.
262
///
263
/// This function is invoked when the translation process runs out of fuel for
264
/// some prior function which enqueues a helper to get translated later. This
265
/// translation function will perform one type translation as specified by
266
/// `Helper` which can either be in the stack or memory for each side.
267
pub(super) fn compile_helper(module: &mut Module<'_>, result: FunctionId, helper: Helper) {
268
let mut nlocals = 0;
269
let src_flat;
270
let src = match helper.src.loc {
271
// If the source is on the stack then it's specified in the parameters
272
// to the function, so this creates the flattened representation and
273
// then lists those as the locals with appropriate types for the source
274
// values.
275
HelperLocation::Stack => {
276
src_flat = module
277
.types
278
.flatten_types(&helper.src.opts, usize::MAX, [helper.src.ty])
279
.unwrap()
280
.iter()
281
.enumerate()
282
.map(|(i, ty)| (i as u32, *ty))
283
.collect::<Vec<_>>();
284
nlocals += src_flat.len() as u32;
285
Source::Stack(Stack {
286
locals: &src_flat,
287
opts: &helper.src.opts,
288
})
289
}
290
// If the source is in memory then that's just propagated here as the
291
// first local is the pointer to the source.
292
HelperLocation::Memory => {
293
nlocals += 1;
294
Source::Memory(Memory {
295
opts: &helper.src.opts,
296
addr: TempLocal::new(0, helper.src.opts.data_model.unwrap_memory().ptr()),
297
offset: 0,
298
})
299
}
300
HelperLocation::StructField | HelperLocation::ArrayElement => todo!("CM+GC"),
301
};
302
let dst_flat;
303
let dst = match helper.dst.loc {
304
// This is the same as the stack-based source although `Destination` is
305
// configured slightly differently.
306
HelperLocation::Stack => {
307
dst_flat = module
308
.types
309
.flatten_types(&helper.dst.opts, usize::MAX, [helper.dst.ty])
310
.unwrap();
311
Destination::Stack(&dst_flat, &helper.dst.opts)
312
}
313
// This is the same as a memory-based source but note that the address
314
// of the destination is passed as the final parameter to the function.
315
HelperLocation::Memory => {
316
nlocals += 1;
317
Destination::Memory(Memory {
318
opts: &helper.dst.opts,
319
addr: TempLocal::new(
320
nlocals - 1,
321
helper.dst.opts.data_model.unwrap_memory().ptr(),
322
),
323
offset: 0,
324
})
325
}
326
HelperLocation::StructField | HelperLocation::ArrayElement => todo!("CM+GC"),
327
};
328
let mut compiler = Compiler {
329
types: module.types,
330
module,
331
code: Vec::new(),
332
nlocals,
333
free_locals: HashMap::new(),
334
traps: Vec::new(),
335
result,
336
fuel: INITIAL_FUEL,
337
// This is a helper function and only the top-level function is
338
// responsible for emitting these intrinsic calls.
339
emit_resource_call: false,
340
};
341
compiler.translate(&helper.src.ty, &src, &helper.dst.ty, &dst);
342
compiler.finish();
343
}
344
345
/// Possible ways that a interface value is represented in the core wasm
346
/// canonical ABI.
347
enum Source<'a> {
348
/// This value is stored on the "stack" in wasm locals.
349
///
350
/// This could mean that it's inline from the parameters to the function or
351
/// that after a function call the results were stored in locals and the
352
/// locals are the inline results.
353
Stack(Stack<'a>),
354
355
/// This value is stored in linear memory described by the `Memory`
356
/// structure.
357
Memory(Memory<'a>),
358
359
/// This value is stored in a GC struct field described by the `GcStruct`
360
/// structure.
361
#[allow(dead_code, reason = "CM+GC is still WIP")]
362
Struct(GcStruct<'a>),
363
364
/// This value is stored in a GC array element described by the `GcArray`
365
/// structure.
366
#[allow(dead_code, reason = "CM+GC is still WIP")]
367
Array(GcArray<'a>),
368
}
369
370
/// Same as `Source` but for where values are translated into.
371
enum Destination<'a> {
372
/// This value is destined for the WebAssembly stack which means that
373
/// results are simply pushed as we go along.
374
///
375
/// The types listed are the types that are expected to be on the stack at
376
/// the end of translation.
377
Stack(&'a [ValType], &'a Options),
378
379
/// This value is to be placed in linear memory described by `Memory`.
380
Memory(Memory<'a>),
381
382
/// This value is to be placed in a GC struct field described by the
383
/// `GcStruct` structure.
384
#[allow(dead_code, reason = "CM+GC is still WIP")]
385
Struct(GcStruct<'a>),
386
387
/// This value is to be placed in a GC array element described by the
388
/// `GcArray` structure.
389
#[allow(dead_code, reason = "CM+GC is still WIP")]
390
Array(GcArray<'a>),
391
}
392
393
struct Stack<'a> {
394
/// The locals that comprise a particular value.
395
///
396
/// The length of this list represents the flattened list of types that make
397
/// up the component value. Each list has the index of the local being
398
/// accessed as well as the type of the local itself.
399
locals: &'a [(u32, ValType)],
400
/// The lifting/lowering options for where this stack of values comes from
401
opts: &'a Options,
402
}
403
404
/// Representation of where a value is going to be stored in linear memory.
405
struct Memory<'a> {
406
/// The lifting/lowering options with memory configuration
407
opts: &'a Options,
408
/// The index of the local that contains the base address of where the
409
/// storage is happening.
410
addr: TempLocal,
411
/// A "static" offset that will be baked into wasm instructions for where
412
/// memory loads/stores happen.
413
offset: u32,
414
}
415
416
impl<'a> Memory<'a> {
417
fn mem_opts(&self) -> &'a LinearMemoryOptions {
418
self.opts.data_model.unwrap_memory()
419
}
420
}
421
422
/// Representation of where a value is coming from or going to in a GC struct.
423
struct GcStruct<'a> {
424
opts: &'a Options,
425
// TODO: more fields to come in the future.
426
}
427
428
/// Representation of where a value is coming from or going to in a GC array.
429
struct GcArray<'a> {
430
opts: &'a Options,
431
// TODO: more fields to come in the future.
432
}
433
434
impl<'a, 'b> Compiler<'a, 'b> {
435
fn new(
436
module: &'b mut Module<'a>,
437
result: FunctionId,
438
nlocals: u32,
439
emit_resource_call: bool,
440
) -> Self {
441
Self {
442
types: module.types,
443
module,
444
result,
445
code: Vec::new(),
446
nlocals,
447
free_locals: HashMap::new(),
448
traps: Vec::new(),
449
fuel: INITIAL_FUEL,
450
emit_resource_call,
451
}
452
}
453
454
/// Compile an adapter function supporting an async-lowered import to an
455
/// async-lifted export.
456
///
457
/// This uses a pair of `async-prepare` and `async-start` built-in functions
458
/// to set up and start a subtask, respectively. `async-prepare` accepts
459
/// `start` and `return_` functions which copy the parameters and results,
460
/// respectively; the host will call the former when the callee has cleared
461
/// its backpressure flag and the latter when the callee has called
462
/// `task.return`.
463
fn compile_async_to_async_adapter(
464
mut self,
465
adapter: &AdapterData,
466
start: FunctionId,
467
return_: FunctionId,
468
param_count: i32,
469
lower_sig: &Signature,
470
) {
471
let start_call =
472
self.module
473
.import_async_start_call(&adapter.name, adapter.lift.options.callback, None);
474
475
self.call_prepare(adapter, start, return_, lower_sig, false);
476
477
// TODO: As an optimization, consider checking the backpressure flag on
478
// the callee instance and, if it's unset _and_ the callee uses a
479
// callback, translate the params and call the callee function directly
480
// here (and make sure `start_call` knows _not_ to call it in that case).
481
482
// We export this function so we can pass a funcref to the host.
483
//
484
// TODO: Use a declarative element segment instead of exporting this.
485
self.module.exports.push((
486
adapter.callee.as_u32(),
487
format!("[adapter-callee]{}", adapter.name),
488
));
489
490
self.instruction(RefFunc(adapter.callee.as_u32()));
491
self.instruction(I32Const(param_count));
492
// The result count for an async callee is either one (if there's a
493
// callback) or zero (if there's no callback). We conservatively use
494
// one here to ensure the host provides room for the result, if any.
495
self.instruction(I32Const(1));
496
self.instruction(I32Const(START_FLAG_ASYNC_CALLEE));
497
self.instruction(Call(start_call.as_u32()));
498
499
self.finish()
500
}
501
502
/// Invokes the `prepare_call` builtin with the provided parameters for this
503
/// adapter.
504
///
505
/// This is part of a async lower and/or async lift adapter. This is not
506
/// used for a sync->sync function call. This is done to create the task on
507
/// the host side of the runtime and such. This will notably invoke a
508
/// Cranelift builtin which will spill all wasm-level parameters to the
509
/// stack to handle variadic signatures.
510
///
511
/// Note that the `prepare_sync` parameter here configures the
512
/// `result_count_or_max_if_async` parameter to indicate whether this is a
513
/// sync or async prepare.
514
fn call_prepare(
515
&mut self,
516
adapter: &AdapterData,
517
start: FunctionId,
518
return_: FunctionId,
519
lower_sig: &Signature,
520
prepare_sync: bool,
521
) {
522
let prepare = self.module.import_prepare_call(
523
&adapter.name,
524
&lower_sig.params,
525
match adapter.lift.options.data_model {
526
DataModel::Gc {} => todo!("CM+GC"),
527
DataModel::LinearMemory(LinearMemoryOptions { memory, .. }) => memory,
528
},
529
);
530
531
self.flush_code();
532
self.module.funcs[self.result]
533
.body
534
.push(Body::RefFunc(start));
535
self.module.funcs[self.result]
536
.body
537
.push(Body::RefFunc(return_));
538
self.instruction(I32Const(
539
i32::try_from(adapter.lower.instance.as_u32()).unwrap(),
540
));
541
self.instruction(I32Const(
542
i32::try_from(adapter.lift.instance.as_u32()).unwrap(),
543
));
544
self.instruction(I32Const(
545
i32::try_from(self.types[adapter.lift.ty].results.as_u32()).unwrap(),
546
));
547
self.instruction(I32Const(i32::from(
548
adapter.lift.options.string_encoding as u8,
549
)));
550
551
// flag this as a preparation for either an async call or sync call,
552
// depending on `prepare_sync`
553
let result_types = &self.types[self.types[adapter.lower.ty].results].types;
554
if prepare_sync {
555
self.instruction(I32Const(
556
i32::try_from(
557
self.types
558
.flatten_types(
559
&adapter.lower.options,
560
usize::MAX,
561
result_types.iter().copied(),
562
)
563
.map(|v| v.len())
564
.unwrap_or(usize::try_from(i32::MAX).unwrap()),
565
)
566
.unwrap(),
567
));
568
} else {
569
if result_types.len() > 0 {
570
self.instruction(I32Const(PREPARE_ASYNC_WITH_RESULT.cast_signed()));
571
} else {
572
self.instruction(I32Const(PREPARE_ASYNC_NO_RESULT.cast_signed()));
573
}
574
}
575
576
// forward all our own arguments on to the host stub
577
for index in 0..lower_sig.params.len() {
578
self.instruction(LocalGet(u32::try_from(index).unwrap()));
579
}
580
self.instruction(Call(prepare.as_u32()));
581
}
582
583
/// Compile an adapter function supporting a sync-lowered import to an
584
/// async-lifted export.
585
///
586
/// This uses a pair of `sync-prepare` and `sync-start` built-in functions
587
/// to set up and start a subtask, respectively. `sync-prepare` accepts
588
/// `start` and `return_` functions which copy the parameters and results,
589
/// respectively; the host will call the former when the callee has cleared
590
/// its backpressure flag and the latter when the callee has called
591
/// `task.return`.
592
fn compile_sync_to_async_adapter(
593
mut self,
594
adapter: &AdapterData,
595
start: FunctionId,
596
return_: FunctionId,
597
lift_param_count: i32,
598
lower_sig: &Signature,
599
) {
600
let start_call = self.module.import_sync_start_call(
601
&adapter.name,
602
adapter.lift.options.callback,
603
&lower_sig.results,
604
);
605
606
self.call_prepare(adapter, start, return_, lower_sig, true);
607
608
// TODO: As an optimization, consider checking the backpressure flag on
609
// the callee instance and, if it's unset _and_ the callee uses a
610
// callback, translate the params and call the callee function directly
611
// here (and make sure `start_call` knows _not_ to call it in that case).
612
613
// We export this function so we can pass a funcref to the host.
614
//
615
// TODO: Use a declarative element segment instead of exporting this.
616
self.module.exports.push((
617
adapter.callee.as_u32(),
618
format!("[adapter-callee]{}", adapter.name),
619
));
620
621
self.instruction(RefFunc(adapter.callee.as_u32()));
622
self.instruction(I32Const(lift_param_count));
623
self.instruction(Call(start_call.as_u32()));
624
625
self.finish()
626
}
627
628
/// Compile an adapter function supporting an async-lowered import to a
629
/// sync-lifted export.
630
///
631
/// This uses a pair of `async-prepare` and `async-start` built-in functions
632
/// to set up and start a subtask, respectively. `async-prepare` accepts
633
/// `start` and `return_` functions which copy the parameters and results,
634
/// respectively; the host will call the former when the callee has cleared
635
/// its backpressure flag and the latter when the callee has returned its
636
/// result(s).
637
fn compile_async_to_sync_adapter(
638
mut self,
639
adapter: &AdapterData,
640
start: FunctionId,
641
return_: FunctionId,
642
param_count: i32,
643
result_count: i32,
644
lower_sig: &Signature,
645
) {
646
let start_call =
647
self.module
648
.import_async_start_call(&adapter.name, None, adapter.lift.post_return);
649
650
self.call_prepare(adapter, start, return_, lower_sig, false);
651
652
// We export this function so we can pass a funcref to the host.
653
//
654
// TODO: Use a declarative element segment instead of exporting this.
655
self.module.exports.push((
656
adapter.callee.as_u32(),
657
format!("[adapter-callee]{}", adapter.name),
658
));
659
660
self.instruction(RefFunc(adapter.callee.as_u32()));
661
self.instruction(I32Const(param_count));
662
self.instruction(I32Const(result_count));
663
self.instruction(I32Const(0));
664
self.instruction(Call(start_call.as_u32()));
665
666
self.finish()
667
}
668
669
/// Compiles a function to be exported to the host which host to lift the
670
/// parameters from the caller and lower them to the callee.
671
///
672
/// This allows the host to delay copying the parameters until the callee
673
/// signals readiness by clearing its backpressure flag.
674
fn compile_async_start_adapter(mut self, adapter: &AdapterData, sig: &Signature) {
675
let param_locals = sig
676
.params
677
.iter()
678
.enumerate()
679
.map(|(i, ty)| (i as u32, *ty))
680
.collect::<Vec<_>>();
681
682
self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, false);
683
self.translate_params(adapter, &param_locals);
684
self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, true);
685
686
self.finish();
687
}
688
689
/// Compiles a function to be exported by the adapter module and called by
690
/// the host to lift the results from the callee and lower them to the
691
/// caller.
692
///
693
/// Given that async-lifted exports return their results via the
694
/// `task.return` intrinsic, the host will need to copy the results from
695
/// callee to caller when that intrinsic is called rather than when the
696
/// callee task fully completes (which may happen much later).
697
fn compile_async_return_adapter(mut self, adapter: &AdapterData, sig: &Signature) {
698
let param_locals = sig
699
.params
700
.iter()
701
.enumerate()
702
.map(|(i, ty)| (i as u32, *ty))
703
.collect::<Vec<_>>();
704
705
self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, false);
706
// Note that we pass `param_locals` as _both_ the `param_locals` and
707
// `result_locals` parameters to `translate_results`. That's because
708
// the _parameters_ to `task.return` are actually the _results_ that the
709
// caller is waiting for.
710
//
711
// Additionally, the host will append a return
712
// pointer to the end of that list before calling this adapter's
713
// `async-return` function if the results exceed `MAX_FLAT_RESULTS` or
714
// the import is lowered async, in which case `translate_results` will
715
// use that pointer to store the results.
716
self.translate_results(adapter, &param_locals, &param_locals);
717
self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, true);
718
719
self.finish()
720
}
721
722
/// Compile an adapter function supporting a sync-lowered import to a
723
/// sync-lifted export.
724
///
725
/// Unlike calls involving async-lowered imports or async-lifted exports,
726
/// this adapter need not involve host built-ins except possibly for
727
/// resource bookkeeping.
728
fn compile_sync_to_sync_adapter(
729
mut self,
730
adapter: &AdapterData,
731
lower_sig: &Signature,
732
lift_sig: &Signature,
733
) {
734
// Check the instance flags required for this trampoline.
735
//
736
// This inserts the initial check required by `canon_lower` that the
737
// caller instance can be left and additionally checks the
738
// flags on the callee if necessary whether it can be entered.
739
self.trap_if_not_flag(adapter.lower.flags, FLAG_MAY_LEAVE, Trap::CannotLeave);
740
if adapter.called_as_export {
741
self.trap_if_not_flag(adapter.lift.flags, FLAG_MAY_ENTER, Trap::CannotEnter);
742
self.set_flag(adapter.lift.flags, FLAG_MAY_ENTER, false);
743
} else if self.module.debug {
744
self.assert_not_flag(
745
adapter.lift.flags,
746
FLAG_MAY_ENTER,
747
"may_enter should be unset",
748
);
749
}
750
751
if self.emit_resource_call {
752
let enter = self.module.import_resource_enter_call();
753
self.instruction(Call(enter.as_u32()));
754
}
755
756
// Perform the translation of arguments. Note that `FLAG_MAY_LEAVE` is
757
// cleared around this invocation for the callee as per the
758
// `canon_lift` definition in the spec. Additionally note that the
759
// precise ordering of traps here is not required since internal state
760
// is not visible to either instance and a trap will "lock down" both
761
// instances to no longer be visible. This means that we're free to
762
// reorder lifts/lowers and flags and such as is necessary and
763
// convenient here.
764
//
765
// TODO: if translation doesn't actually call any functions in either
766
// instance then there's no need to set/clear the flag here and that can
767
// be optimized away.
768
self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, false);
769
let param_locals = lower_sig
770
.params
771
.iter()
772
.enumerate()
773
.map(|(i, ty)| (i as u32, *ty))
774
.collect::<Vec<_>>();
775
self.translate_params(adapter, &param_locals);
776
self.set_flag(adapter.lift.flags, FLAG_MAY_LEAVE, true);
777
778
// With all the arguments on the stack the actual target function is
779
// now invoked. The core wasm results of the function are then placed
780
// into locals for result translation afterwards.
781
self.instruction(Call(adapter.callee.as_u32()));
782
let mut result_locals = Vec::with_capacity(lift_sig.results.len());
783
let mut temps = Vec::new();
784
for ty in lift_sig.results.iter().rev() {
785
let local = self.local_set_new_tmp(*ty);
786
result_locals.push((local.idx, *ty));
787
temps.push(local);
788
}
789
result_locals.reverse();
790
791
// Like above during the translation of results the caller cannot be
792
// left (as we might invoke things like `realloc`). Again the precise
793
// order of everything doesn't matter since intermediate states cannot
794
// be witnessed, hence the setting of flags here to encapsulate both
795
// liftings and lowerings.
796
//
797
// TODO: like above the management of the `MAY_LEAVE` flag can probably
798
// be elided here for "simple" results.
799
self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, false);
800
self.translate_results(adapter, &param_locals, &result_locals);
801
self.set_flag(adapter.lower.flags, FLAG_MAY_LEAVE, true);
802
803
// And finally post-return state is handled here once all results/etc
804
// are all translated.
805
if let Some(func) = adapter.lift.post_return {
806
for (result, _) in result_locals.iter() {
807
self.instruction(LocalGet(*result));
808
}
809
self.instruction(Call(func.as_u32()));
810
}
811
if adapter.called_as_export {
812
self.set_flag(adapter.lift.flags, FLAG_MAY_ENTER, true);
813
}
814
815
for tmp in temps {
816
self.free_temp_local(tmp);
817
}
818
819
if self.emit_resource_call {
820
let exit = self.module.import_resource_exit_call();
821
self.instruction(Call(exit.as_u32()));
822
}
823
824
self.finish()
825
}
826
827
fn translate_params(&mut self, adapter: &AdapterData, param_locals: &[(u32, ValType)]) {
828
let src_tys = self.types[adapter.lower.ty].params;
829
let src_tys = self.types[src_tys]
830
.types
831
.iter()
832
.copied()
833
.collect::<Vec<_>>();
834
let dst_tys = self.types[adapter.lift.ty].params;
835
let dst_tys = self.types[dst_tys]
836
.types
837
.iter()
838
.copied()
839
.collect::<Vec<_>>();
840
let lift_opts = &adapter.lift.options;
841
let lower_opts = &adapter.lower.options;
842
843
// TODO: handle subtyping
844
assert_eq!(src_tys.len(), dst_tys.len());
845
846
// Async lowered functions have a smaller limit on flat parameters, but
847
// their destination, a lifted function, does not have a different limit
848
// than sync functions.
849
let max_flat_params = if adapter.lower.options.async_ {
850
MAX_FLAT_ASYNC_PARAMS
851
} else {
852
MAX_FLAT_PARAMS
853
};
854
let src_flat =
855
self.types
856
.flatten_types(lower_opts, max_flat_params, src_tys.iter().copied());
857
let dst_flat =
858
self.types
859
.flatten_types(lift_opts, MAX_FLAT_PARAMS, dst_tys.iter().copied());
860
861
let src = if let Some(flat) = &src_flat {
862
Source::Stack(Stack {
863
locals: &param_locals[..flat.len()],
864
opts: lower_opts,
865
})
866
} else {
867
// If there are too many parameters then that means the parameters
868
// are actually a tuple stored in linear memory addressed by the
869
// first parameter local.
870
let lower_mem_opts = lower_opts.data_model.unwrap_memory();
871
let (addr, ty) = param_locals[0];
872
assert_eq!(ty, lower_mem_opts.ptr());
873
let align = src_tys
874
.iter()
875
.map(|t| self.types.align(lower_mem_opts, t))
876
.max()
877
.unwrap_or(1);
878
Source::Memory(self.memory_operand(lower_opts, TempLocal::new(addr, ty), align))
879
};
880
881
let dst = if let Some(flat) = &dst_flat {
882
Destination::Stack(flat, lift_opts)
883
} else {
884
let abi = CanonicalAbiInfo::record(dst_tys.iter().map(|t| self.types.canonical_abi(t)));
885
match lift_opts.data_model {
886
DataModel::Gc {} => todo!("CM+GC"),
887
DataModel::LinearMemory(LinearMemoryOptions { memory64, .. }) => {
888
let (size, align) = if memory64 {
889
(abi.size64, abi.align64)
890
} else {
891
(abi.size32, abi.align32)
892
};
893
894
// If there are too many parameters then space is allocated in the
895
// destination module for the parameters via its `realloc` function.
896
let size = MallocSize::Const(size);
897
Destination::Memory(self.malloc(lift_opts, size, align))
898
}
899
}
900
};
901
902
let srcs = src
903
.record_field_srcs(self.types, src_tys.iter().copied())
904
.zip(src_tys.iter());
905
let dsts = dst
906
.record_field_dsts(self.types, dst_tys.iter().copied())
907
.zip(dst_tys.iter());
908
for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) {
909
self.translate(&src_ty, &src, &dst_ty, &dst);
910
}
911
912
// If the destination was linear memory instead of the stack then the
913
// actual parameter that we're passing is the address of the values
914
// stored, so ensure that's happening in the wasm body here.
915
if let Destination::Memory(mem) = dst {
916
self.instruction(LocalGet(mem.addr.idx));
917
self.free_temp_local(mem.addr);
918
}
919
}
920
921
fn translate_results(
922
&mut self,
923
adapter: &AdapterData,
924
param_locals: &[(u32, ValType)],
925
result_locals: &[(u32, ValType)],
926
) {
927
let src_tys = self.types[adapter.lift.ty].results;
928
let src_tys = self.types[src_tys]
929
.types
930
.iter()
931
.copied()
932
.collect::<Vec<_>>();
933
let dst_tys = self.types[adapter.lower.ty].results;
934
let dst_tys = self.types[dst_tys]
935
.types
936
.iter()
937
.copied()
938
.collect::<Vec<_>>();
939
let lift_opts = &adapter.lift.options;
940
let lower_opts = &adapter.lower.options;
941
942
let src_flat = self
943
.types
944
.flatten_lifting_types(lift_opts, src_tys.iter().copied());
945
let dst_flat = self
946
.types
947
.flatten_lowering_types(lower_opts, dst_tys.iter().copied());
948
949
let src = if src_flat.is_some() {
950
Source::Stack(Stack {
951
locals: result_locals,
952
opts: lift_opts,
953
})
954
} else {
955
// The original results to read from in this case come from the
956
// return value of the function itself. The imported function will
957
// return a linear memory address at which the values can be read
958
// from.
959
let lift_mem_opts = lift_opts.data_model.unwrap_memory();
960
let align = src_tys
961
.iter()
962
.map(|t| self.types.align(lift_mem_opts, t))
963
.max()
964
.unwrap_or(1);
965
assert_eq!(
966
result_locals.len(),
967
if lower_opts.async_ || lift_opts.async_ {
968
2
969
} else {
970
1
971
}
972
);
973
let (addr, ty) = result_locals[0];
974
assert_eq!(ty, lift_opts.data_model.unwrap_memory().ptr());
975
Source::Memory(self.memory_operand(lift_opts, TempLocal::new(addr, ty), align))
976
};
977
978
let dst = if let Some(flat) = &dst_flat {
979
Destination::Stack(flat, lower_opts)
980
} else {
981
// This is slightly different than `translate_params` where the
982
// return pointer was provided by the caller of this function
983
// meaning the last parameter local is a pointer into linear memory.
984
let lower_mem_opts = lower_opts.data_model.unwrap_memory();
985
let align = dst_tys
986
.iter()
987
.map(|t| self.types.align(lower_mem_opts, t))
988
.max()
989
.unwrap_or(1);
990
let (addr, ty) = *param_locals.last().expect("no retptr");
991
assert_eq!(ty, lower_opts.data_model.unwrap_memory().ptr());
992
Destination::Memory(self.memory_operand(lower_opts, TempLocal::new(addr, ty), align))
993
};
994
995
let srcs = src
996
.record_field_srcs(self.types, src_tys.iter().copied())
997
.zip(src_tys.iter());
998
let dsts = dst
999
.record_field_dsts(self.types, dst_tys.iter().copied())
1000
.zip(dst_tys.iter());
1001
for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) {
1002
self.translate(&src_ty, &src, &dst_ty, &dst);
1003
}
1004
}
1005
1006
fn translate(
1007
&mut self,
1008
src_ty: &InterfaceType,
1009
src: &Source<'_>,
1010
dst_ty: &InterfaceType,
1011
dst: &Destination,
1012
) {
1013
if let Source::Memory(mem) = src {
1014
self.assert_aligned(src_ty, mem);
1015
}
1016
if let Destination::Memory(mem) = dst {
1017
self.assert_aligned(dst_ty, mem);
1018
}
1019
1020
// Calculate a cost heuristic for what the translation of this specific
1021
// layer of the type is going to incur. The purpose of this cost is that
1022
// we'll deduct it from `self.fuel` and if no fuel is remaining then
1023
// translation is outlined into a separate function rather than being
1024
// translated into this function.
1025
//
1026
// The general goal is to avoid creating an exponentially sized function
1027
// for a linearly sized input (the type section). By outlining helper
1028
// functions there will ideally be a constant set of helper functions
1029
// per type (to accommodate in-memory or on-stack transfers as well as
1030
// src/dst options) which means that each function is at most a certain
1031
// size and we have a linear number of functions which should guarantee
1032
// an overall linear size of the output.
1033
//
1034
// To implement this the current heuristic is that each layer of
1035
// translating a type has a cost associated with it and this cost is
1036
// accounted for in `self.fuel`. Some conversions are considered free as
1037
// they generate basically as much code as the `call` to the translation
1038
// function while other are considered proportionally expensive to the
1039
// size of the type. The hope is that some upper layers are of a type's
1040
// translation are all inlined into one function but bottom layers end
1041
// up getting outlined to separate functions. Theoretically, again this
1042
// is built on hopes and dreams, the outlining can be shared amongst
1043
// tightly-intertwined type hierarchies which will reduce the size of
1044
// the output module due to the helpers being used.
1045
//
1046
// This heuristic of how to split functions has changed a few times in
1047
// the past and this isn't necessarily guaranteed to be the final
1048
// iteration.
1049
let cost = match src_ty {
1050
// These types are all quite simple to load/store and equate to
1051
// basically the same cost of the `call` instruction to call an
1052
// out-of-line translation function, so give them 0 cost.
1053
InterfaceType::Bool
1054
| InterfaceType::U8
1055
| InterfaceType::S8
1056
| InterfaceType::U16
1057
| InterfaceType::S16
1058
| InterfaceType::U32
1059
| InterfaceType::S32
1060
| InterfaceType::U64
1061
| InterfaceType::S64
1062
| InterfaceType::Float32
1063
| InterfaceType::Float64 => 0,
1064
1065
// This has a small amount of validation associated with it, so
1066
// give it a cost of 1.
1067
InterfaceType::Char => 1,
1068
1069
// This has a fair bit of code behind it depending on the
1070
// strings/encodings in play, so arbitrarily assign it this cost.
1071
InterfaceType::String => 40,
1072
1073
// Iteration of a loop is along the lines of the cost of a string
1074
// so give it the same cost
1075
InterfaceType::List(_) => 40,
1076
1077
InterfaceType::Flags(i) => {
1078
let count = self.module.types[*i].names.len();
1079
match FlagsSize::from_count(count) {
1080
FlagsSize::Size0 => 0,
1081
FlagsSize::Size1 | FlagsSize::Size2 => 1,
1082
FlagsSize::Size4Plus(n) => n.into(),
1083
}
1084
}
1085
1086
InterfaceType::Record(i) => self.types[*i].fields.len(),
1087
InterfaceType::Tuple(i) => self.types[*i].types.len(),
1088
InterfaceType::Variant(i) => self.types[*i].cases.len(),
1089
InterfaceType::Enum(i) => self.types[*i].names.len(),
1090
1091
// 2 cases to consider for each of these variants.
1092
InterfaceType::Option(_) | InterfaceType::Result(_) => 2,
1093
1094
// TODO(#6696) - something nonzero, is 1 right?
1095
InterfaceType::Own(_)
1096
| InterfaceType::Borrow(_)
1097
| InterfaceType::Future(_)
1098
| InterfaceType::Stream(_)
1099
| InterfaceType::ErrorContext(_) => 1,
1100
};
1101
1102
match self.fuel.checked_sub(cost) {
1103
// This function has enough fuel to perform the layer of translation
1104
// necessary for this type, so the fuel is updated in-place and
1105
// translation continues. Note that the recursion here is bounded by
1106
// the static recursion limit for all interface types as imposed
1107
// during the translation phase.
1108
Some(n) => {
1109
self.fuel = n;
1110
match src_ty {
1111
InterfaceType::Bool => self.translate_bool(src, dst_ty, dst),
1112
InterfaceType::U8 => self.translate_u8(src, dst_ty, dst),
1113
InterfaceType::S8 => self.translate_s8(src, dst_ty, dst),
1114
InterfaceType::U16 => self.translate_u16(src, dst_ty, dst),
1115
InterfaceType::S16 => self.translate_s16(src, dst_ty, dst),
1116
InterfaceType::U32 => self.translate_u32(src, dst_ty, dst),
1117
InterfaceType::S32 => self.translate_s32(src, dst_ty, dst),
1118
InterfaceType::U64 => self.translate_u64(src, dst_ty, dst),
1119
InterfaceType::S64 => self.translate_s64(src, dst_ty, dst),
1120
InterfaceType::Float32 => self.translate_f32(src, dst_ty, dst),
1121
InterfaceType::Float64 => self.translate_f64(src, dst_ty, dst),
1122
InterfaceType::Char => self.translate_char(src, dst_ty, dst),
1123
InterfaceType::String => self.translate_string(src, dst_ty, dst),
1124
InterfaceType::List(t) => self.translate_list(*t, src, dst_ty, dst),
1125
InterfaceType::Record(t) => self.translate_record(*t, src, dst_ty, dst),
1126
InterfaceType::Flags(f) => self.translate_flags(*f, src, dst_ty, dst),
1127
InterfaceType::Tuple(t) => self.translate_tuple(*t, src, dst_ty, dst),
1128
InterfaceType::Variant(v) => self.translate_variant(*v, src, dst_ty, dst),
1129
InterfaceType::Enum(t) => self.translate_enum(*t, src, dst_ty, dst),
1130
InterfaceType::Option(t) => self.translate_option(*t, src, dst_ty, dst),
1131
InterfaceType::Result(t) => self.translate_result(*t, src, dst_ty, dst),
1132
InterfaceType::Own(t) => self.translate_own(*t, src, dst_ty, dst),
1133
InterfaceType::Borrow(t) => self.translate_borrow(*t, src, dst_ty, dst),
1134
InterfaceType::Future(t) => self.translate_future(*t, src, dst_ty, dst),
1135
InterfaceType::Stream(t) => self.translate_stream(*t, src, dst_ty, dst),
1136
InterfaceType::ErrorContext(t) => {
1137
self.translate_error_context(*t, src, dst_ty, dst)
1138
}
1139
}
1140
}
1141
1142
// This function does not have enough fuel left to perform this
1143
// layer of translation so the translation is deferred to a helper
1144
// function. The actual translation here is then done by marshalling
1145
// the src/dst into the function we're calling and then processing
1146
// the results.
1147
None => {
1148
let src_loc = match src {
1149
// If the source is on the stack then `stack_get` is used to
1150
// convert everything to the appropriate flat representation
1151
// for the source type.
1152
Source::Stack(stack) => {
1153
for (i, ty) in stack
1154
.opts
1155
.flat_types(src_ty, self.types)
1156
.unwrap()
1157
.iter()
1158
.enumerate()
1159
{
1160
let stack = stack.slice(i..i + 1);
1161
self.stack_get(&stack, (*ty).into());
1162
}
1163
HelperLocation::Stack
1164
}
1165
// If the source is in memory then the pointer is passed
1166
// through, but note that the offset must be factored in
1167
// here since the translation function will start from
1168
// offset 0.
1169
Source::Memory(mem) => {
1170
self.push_mem_addr(mem);
1171
HelperLocation::Memory
1172
}
1173
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1174
};
1175
let dst_loc = match dst {
1176
Destination::Stack(..) => HelperLocation::Stack,
1177
Destination::Memory(mem) => {
1178
self.push_mem_addr(mem);
1179
HelperLocation::Memory
1180
}
1181
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1182
};
1183
// Generate a `FunctionId` corresponding to the `Helper`
1184
// configuration that is necessary here. This will ideally be a
1185
// "cache hit" and use a preexisting helper which represents
1186
// outlining what would otherwise be duplicate code within a
1187
// function to one function.
1188
let helper = self.module.translate_helper(Helper {
1189
src: HelperType {
1190
ty: *src_ty,
1191
opts: *src.opts(),
1192
loc: src_loc,
1193
},
1194
dst: HelperType {
1195
ty: *dst_ty,
1196
opts: *dst.opts(),
1197
loc: dst_loc,
1198
},
1199
});
1200
// Emit a `call` instruction which will get "relocated" to a
1201
// function index once translation has completely finished.
1202
self.flush_code();
1203
self.module.funcs[self.result].body.push(Body::Call(helper));
1204
1205
// If the destination of the translation was on the stack then
1206
// the types on the stack need to be optionally converted to
1207
// different types (e.g. if the result here is part of a variant
1208
// somewhere else).
1209
//
1210
// This translation happens inline here by popping the results
1211
// into new locals and then using those locals to do a
1212
// `stack_set`.
1213
if let Destination::Stack(tys, opts) = dst {
1214
let flat = self
1215
.types
1216
.flatten_types(opts, usize::MAX, [*dst_ty])
1217
.unwrap();
1218
assert_eq!(flat.len(), tys.len());
1219
let locals = flat
1220
.iter()
1221
.rev()
1222
.map(|ty| self.local_set_new_tmp(*ty))
1223
.collect::<Vec<_>>();
1224
for (ty, local) in tys.iter().zip(locals.into_iter().rev()) {
1225
self.instruction(LocalGet(local.idx));
1226
self.stack_set(std::slice::from_ref(ty), local.ty);
1227
self.free_temp_local(local);
1228
}
1229
}
1230
}
1231
}
1232
}
1233
1234
fn push_mem_addr(&mut self, mem: &Memory<'_>) {
1235
self.instruction(LocalGet(mem.addr.idx));
1236
if mem.offset != 0 {
1237
self.ptr_uconst(mem.mem_opts(), mem.offset);
1238
self.ptr_add(mem.mem_opts());
1239
}
1240
}
1241
1242
fn translate_bool(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1243
// TODO: subtyping
1244
assert!(matches!(dst_ty, InterfaceType::Bool));
1245
self.push_dst_addr(dst);
1246
1247
// Booleans are canonicalized to 0 or 1 as they pass through the
1248
// component boundary, so use a `select` instruction to do so.
1249
self.instruction(I32Const(1));
1250
self.instruction(I32Const(0));
1251
match src {
1252
Source::Memory(mem) => self.i32_load8u(mem),
1253
Source::Stack(stack) => self.stack_get(stack, ValType::I32),
1254
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1255
}
1256
self.instruction(Select);
1257
1258
match dst {
1259
Destination::Memory(mem) => self.i32_store8(mem),
1260
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1261
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1262
}
1263
}
1264
1265
fn translate_u8(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1266
// TODO: subtyping
1267
assert!(matches!(dst_ty, InterfaceType::U8));
1268
self.convert_u8_mask(src, dst, 0xff);
1269
}
1270
1271
fn convert_u8_mask(&mut self, src: &Source<'_>, dst: &Destination<'_>, mask: u8) {
1272
self.push_dst_addr(dst);
1273
let mut needs_mask = true;
1274
match src {
1275
Source::Memory(mem) => {
1276
self.i32_load8u(mem);
1277
needs_mask = mask != 0xff;
1278
}
1279
Source::Stack(stack) => {
1280
self.stack_get(stack, ValType::I32);
1281
}
1282
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1283
}
1284
if needs_mask {
1285
self.instruction(I32Const(i32::from(mask)));
1286
self.instruction(I32And);
1287
}
1288
match dst {
1289
Destination::Memory(mem) => self.i32_store8(mem),
1290
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1291
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1292
}
1293
}
1294
1295
fn translate_s8(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1296
// TODO: subtyping
1297
assert!(matches!(dst_ty, InterfaceType::S8));
1298
self.push_dst_addr(dst);
1299
match src {
1300
Source::Memory(mem) => self.i32_load8s(mem),
1301
Source::Stack(stack) => {
1302
self.stack_get(stack, ValType::I32);
1303
self.instruction(I32Extend8S);
1304
}
1305
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1306
}
1307
match dst {
1308
Destination::Memory(mem) => self.i32_store8(mem),
1309
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1310
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1311
}
1312
}
1313
1314
fn translate_u16(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1315
// TODO: subtyping
1316
assert!(matches!(dst_ty, InterfaceType::U16));
1317
self.convert_u16_mask(src, dst, 0xffff);
1318
}
1319
1320
fn convert_u16_mask(&mut self, src: &Source<'_>, dst: &Destination<'_>, mask: u16) {
1321
self.push_dst_addr(dst);
1322
let mut needs_mask = true;
1323
match src {
1324
Source::Memory(mem) => {
1325
self.i32_load16u(mem);
1326
needs_mask = mask != 0xffff;
1327
}
1328
Source::Stack(stack) => {
1329
self.stack_get(stack, ValType::I32);
1330
}
1331
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1332
}
1333
if needs_mask {
1334
self.instruction(I32Const(i32::from(mask)));
1335
self.instruction(I32And);
1336
}
1337
match dst {
1338
Destination::Memory(mem) => self.i32_store16(mem),
1339
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1340
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1341
}
1342
}
1343
1344
fn translate_s16(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1345
// TODO: subtyping
1346
assert!(matches!(dst_ty, InterfaceType::S16));
1347
self.push_dst_addr(dst);
1348
match src {
1349
Source::Memory(mem) => self.i32_load16s(mem),
1350
Source::Stack(stack) => {
1351
self.stack_get(stack, ValType::I32);
1352
self.instruction(I32Extend16S);
1353
}
1354
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1355
}
1356
match dst {
1357
Destination::Memory(mem) => self.i32_store16(mem),
1358
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1359
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1360
}
1361
}
1362
1363
fn translate_u32(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1364
// TODO: subtyping
1365
assert!(matches!(dst_ty, InterfaceType::U32));
1366
self.convert_u32_mask(src, dst, 0xffffffff)
1367
}
1368
1369
fn convert_u32_mask(&mut self, src: &Source<'_>, dst: &Destination<'_>, mask: u32) {
1370
self.push_dst_addr(dst);
1371
match src {
1372
Source::Memory(mem) => self.i32_load(mem),
1373
Source::Stack(stack) => self.stack_get(stack, ValType::I32),
1374
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1375
}
1376
if mask != 0xffffffff {
1377
self.instruction(I32Const(mask as i32));
1378
self.instruction(I32And);
1379
}
1380
match dst {
1381
Destination::Memory(mem) => self.i32_store(mem),
1382
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1383
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1384
}
1385
}
1386
1387
fn translate_s32(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1388
// TODO: subtyping
1389
assert!(matches!(dst_ty, InterfaceType::S32));
1390
self.push_dst_addr(dst);
1391
match src {
1392
Source::Memory(mem) => self.i32_load(mem),
1393
Source::Stack(stack) => self.stack_get(stack, ValType::I32),
1394
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1395
}
1396
match dst {
1397
Destination::Memory(mem) => self.i32_store(mem),
1398
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1399
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1400
}
1401
}
1402
1403
fn translate_u64(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1404
// TODO: subtyping
1405
assert!(matches!(dst_ty, InterfaceType::U64));
1406
self.push_dst_addr(dst);
1407
match src {
1408
Source::Memory(mem) => self.i64_load(mem),
1409
Source::Stack(stack) => self.stack_get(stack, ValType::I64),
1410
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1411
}
1412
match dst {
1413
Destination::Memory(mem) => self.i64_store(mem),
1414
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I64),
1415
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1416
}
1417
}
1418
1419
fn translate_s64(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1420
// TODO: subtyping
1421
assert!(matches!(dst_ty, InterfaceType::S64));
1422
self.push_dst_addr(dst);
1423
match src {
1424
Source::Memory(mem) => self.i64_load(mem),
1425
Source::Stack(stack) => self.stack_get(stack, ValType::I64),
1426
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1427
}
1428
match dst {
1429
Destination::Memory(mem) => self.i64_store(mem),
1430
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I64),
1431
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1432
}
1433
}
1434
1435
fn translate_f32(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1436
// TODO: subtyping
1437
assert!(matches!(dst_ty, InterfaceType::Float32));
1438
self.push_dst_addr(dst);
1439
match src {
1440
Source::Memory(mem) => self.f32_load(mem),
1441
Source::Stack(stack) => self.stack_get(stack, ValType::F32),
1442
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1443
}
1444
match dst {
1445
Destination::Memory(mem) => self.f32_store(mem),
1446
Destination::Stack(stack, _) => self.stack_set(stack, ValType::F32),
1447
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1448
}
1449
}
1450
1451
fn translate_f64(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1452
// TODO: subtyping
1453
assert!(matches!(dst_ty, InterfaceType::Float64));
1454
self.push_dst_addr(dst);
1455
match src {
1456
Source::Memory(mem) => self.f64_load(mem),
1457
Source::Stack(stack) => self.stack_get(stack, ValType::F64),
1458
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1459
}
1460
match dst {
1461
Destination::Memory(mem) => self.f64_store(mem),
1462
Destination::Stack(stack, _) => self.stack_set(stack, ValType::F64),
1463
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1464
}
1465
}
1466
1467
fn translate_char(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1468
assert!(matches!(dst_ty, InterfaceType::Char));
1469
match src {
1470
Source::Memory(mem) => self.i32_load(mem),
1471
Source::Stack(stack) => self.stack_get(stack, ValType::I32),
1472
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1473
}
1474
let local = self.local_set_new_tmp(ValType::I32);
1475
1476
// This sequence is copied from the output of LLVM for:
1477
//
1478
// pub extern "C" fn foo(x: u32) -> char {
1479
// char::try_from(x)
1480
// .unwrap_or_else(|_| std::arch::wasm32::unreachable())
1481
// }
1482
//
1483
// Apparently this does what's required by the canonical ABI:
1484
//
1485
// def i32_to_char(opts, i):
1486
// trap_if(i >= 0x110000)
1487
// trap_if(0xD800 <= i <= 0xDFFF)
1488
// return chr(i)
1489
//
1490
// ... but I don't know how it works other than "well I trust LLVM"
1491
self.instruction(Block(BlockType::Empty));
1492
self.instruction(Block(BlockType::Empty));
1493
self.instruction(LocalGet(local.idx));
1494
self.instruction(I32Const(0xd800));
1495
self.instruction(I32Xor);
1496
self.instruction(I32Const(-0x110000));
1497
self.instruction(I32Add);
1498
self.instruction(I32Const(-0x10f800));
1499
self.instruction(I32LtU);
1500
self.instruction(BrIf(0));
1501
self.instruction(LocalGet(local.idx));
1502
self.instruction(I32Const(0x110000));
1503
self.instruction(I32Ne);
1504
self.instruction(BrIf(1));
1505
self.instruction(End);
1506
self.trap(Trap::InvalidChar);
1507
self.instruction(End);
1508
1509
self.push_dst_addr(dst);
1510
self.instruction(LocalGet(local.idx));
1511
match dst {
1512
Destination::Memory(mem) => {
1513
self.i32_store(mem);
1514
}
1515
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
1516
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1517
}
1518
1519
self.free_temp_local(local);
1520
}
1521
1522
fn translate_string(&mut self, src: &Source<'_>, dst_ty: &InterfaceType, dst: &Destination) {
1523
assert!(matches!(dst_ty, InterfaceType::String));
1524
let src_opts = src.opts();
1525
let dst_opts = dst.opts();
1526
1527
let src_mem_opts = match &src_opts.data_model {
1528
DataModel::Gc {} => todo!("CM+GC"),
1529
DataModel::LinearMemory(opts) => opts,
1530
};
1531
let dst_mem_opts = match &dst_opts.data_model {
1532
DataModel::Gc {} => todo!("CM+GC"),
1533
DataModel::LinearMemory(opts) => opts,
1534
};
1535
1536
// Load the pointer/length of this string into temporary locals. These
1537
// will be referenced a good deal so this just makes it easier to deal
1538
// with them consistently below rather than trying to reload from memory
1539
// for example.
1540
match src {
1541
Source::Stack(s) => {
1542
assert_eq!(s.locals.len(), 2);
1543
self.stack_get(&s.slice(0..1), src_mem_opts.ptr());
1544
self.stack_get(&s.slice(1..2), src_mem_opts.ptr());
1545
}
1546
Source::Memory(mem) => {
1547
self.ptr_load(mem);
1548
self.ptr_load(&mem.bump(src_mem_opts.ptr_size().into()));
1549
}
1550
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
1551
}
1552
let src_len = self.local_set_new_tmp(src_mem_opts.ptr());
1553
let src_ptr = self.local_set_new_tmp(src_mem_opts.ptr());
1554
let src_str = WasmString {
1555
ptr: src_ptr,
1556
len: src_len,
1557
opts: src_opts,
1558
};
1559
1560
let dst_str = match src_opts.string_encoding {
1561
StringEncoding::Utf8 => match dst_opts.string_encoding {
1562
StringEncoding::Utf8 => self.string_copy(&src_str, FE::Utf8, dst_opts, FE::Utf8),
1563
StringEncoding::Utf16 => self.string_utf8_to_utf16(&src_str, dst_opts),
1564
StringEncoding::CompactUtf16 => {
1565
self.string_to_compact(&src_str, FE::Utf8, dst_opts)
1566
}
1567
},
1568
1569
StringEncoding::Utf16 => {
1570
self.verify_aligned(src_mem_opts, src_str.ptr.idx, 2);
1571
match dst_opts.string_encoding {
1572
StringEncoding::Utf8 => {
1573
self.string_deflate_to_utf8(&src_str, FE::Utf16, dst_opts)
1574
}
1575
StringEncoding::Utf16 => {
1576
self.string_copy(&src_str, FE::Utf16, dst_opts, FE::Utf16)
1577
}
1578
StringEncoding::CompactUtf16 => {
1579
self.string_to_compact(&src_str, FE::Utf16, dst_opts)
1580
}
1581
}
1582
}
1583
1584
StringEncoding::CompactUtf16 => {
1585
self.verify_aligned(src_mem_opts, src_str.ptr.idx, 2);
1586
1587
// Test the tag big to see if this is a utf16 or a latin1 string
1588
// at runtime...
1589
self.instruction(LocalGet(src_str.len.idx));
1590
self.ptr_uconst(src_mem_opts, UTF16_TAG);
1591
self.ptr_and(src_mem_opts);
1592
self.ptr_if(src_mem_opts, BlockType::Empty);
1593
1594
// In the utf16 block unset the upper bit from the length local
1595
// so further calculations have the right value. Afterwards the
1596
// string transcode proceeds assuming utf16.
1597
self.instruction(LocalGet(src_str.len.idx));
1598
self.ptr_uconst(src_mem_opts, UTF16_TAG);
1599
self.ptr_xor(src_mem_opts);
1600
self.instruction(LocalSet(src_str.len.idx));
1601
let s1 = match dst_opts.string_encoding {
1602
StringEncoding::Utf8 => {
1603
self.string_deflate_to_utf8(&src_str, FE::Utf16, dst_opts)
1604
}
1605
StringEncoding::Utf16 => {
1606
self.string_copy(&src_str, FE::Utf16, dst_opts, FE::Utf16)
1607
}
1608
StringEncoding::CompactUtf16 => {
1609
self.string_compact_utf16_to_compact(&src_str, dst_opts)
1610
}
1611
};
1612
1613
self.instruction(Else);
1614
1615
// In the latin1 block the `src_len` local is already the number
1616
// of code units, so the string transcoding is all that needs to
1617
// happen.
1618
let s2 = match dst_opts.string_encoding {
1619
StringEncoding::Utf16 => {
1620
self.string_copy(&src_str, FE::Latin1, dst_opts, FE::Utf16)
1621
}
1622
StringEncoding::Utf8 => {
1623
self.string_deflate_to_utf8(&src_str, FE::Latin1, dst_opts)
1624
}
1625
StringEncoding::CompactUtf16 => {
1626
self.string_copy(&src_str, FE::Latin1, dst_opts, FE::Latin1)
1627
}
1628
};
1629
// Set our `s2` generated locals to the `s2` generated locals
1630
// as the resulting pointer of this transcode.
1631
self.instruction(LocalGet(s2.ptr.idx));
1632
self.instruction(LocalSet(s1.ptr.idx));
1633
self.instruction(LocalGet(s2.len.idx));
1634
self.instruction(LocalSet(s1.len.idx));
1635
self.instruction(End);
1636
self.free_temp_local(s2.ptr);
1637
self.free_temp_local(s2.len);
1638
s1
1639
}
1640
};
1641
1642
// Store the ptr/length in the desired destination
1643
match dst {
1644
Destination::Stack(s, _) => {
1645
self.instruction(LocalGet(dst_str.ptr.idx));
1646
self.stack_set(&s[..1], dst_mem_opts.ptr());
1647
self.instruction(LocalGet(dst_str.len.idx));
1648
self.stack_set(&s[1..], dst_mem_opts.ptr());
1649
}
1650
Destination::Memory(mem) => {
1651
self.instruction(LocalGet(mem.addr.idx));
1652
self.instruction(LocalGet(dst_str.ptr.idx));
1653
self.ptr_store(mem);
1654
self.instruction(LocalGet(mem.addr.idx));
1655
self.instruction(LocalGet(dst_str.len.idx));
1656
self.ptr_store(&mem.bump(dst_mem_opts.ptr_size().into()));
1657
}
1658
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
1659
}
1660
1661
self.free_temp_local(src_str.ptr);
1662
self.free_temp_local(src_str.len);
1663
self.free_temp_local(dst_str.ptr);
1664
self.free_temp_local(dst_str.len);
1665
}
1666
1667
// Corresponding function for `store_string_copy` in the spec.
1668
//
1669
// This performs a transcoding of the string with a one-pass copy from
1670
// the `src` encoding to the `dst` encoding. This is only possible for
1671
// fixed encodings where the first allocation is guaranteed to be an
1672
// appropriate fit so it's not suitable for all encodings.
1673
//
1674
// Imported host transcoding functions here take the src/dst pointers as
1675
// well as the number of code units in the source (which always matches
1676
// the number of code units in the destination). There is no return
1677
// value from the transcode function since the encoding should always
1678
// work on the first pass.
1679
fn string_copy<'c>(
1680
&mut self,
1681
src: &WasmString<'_>,
1682
src_enc: FE,
1683
dst_opts: &'c Options,
1684
dst_enc: FE,
1685
) -> WasmString<'c> {
1686
assert!(dst_enc.width() >= src_enc.width());
1687
self.validate_string_length(src, dst_enc);
1688
1689
let src_mem_opts = {
1690
match &src.opts.data_model {
1691
DataModel::Gc {} => todo!("CM+GC"),
1692
DataModel::LinearMemory(opts) => opts,
1693
}
1694
};
1695
let dst_mem_opts = {
1696
match &dst_opts.data_model {
1697
DataModel::Gc {} => todo!("CM+GC"),
1698
DataModel::LinearMemory(opts) => opts,
1699
}
1700
};
1701
1702
// Calculate the source byte length given the size of each code
1703
// unit. Note that this shouldn't overflow given
1704
// `validate_string_length` above.
1705
let mut src_byte_len_tmp = None;
1706
let src_byte_len = if src_enc.width() == 1 {
1707
src.len.idx
1708
} else {
1709
assert_eq!(src_enc.width(), 2);
1710
self.instruction(LocalGet(src.len.idx));
1711
self.ptr_uconst(src_mem_opts, 1);
1712
self.ptr_shl(src_mem_opts);
1713
let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());
1714
let ret = tmp.idx;
1715
src_byte_len_tmp = Some(tmp);
1716
ret
1717
};
1718
1719
// Convert the source code units length to the destination byte
1720
// length type.
1721
self.convert_src_len_to_dst(
1722
src.len.idx,
1723
src.opts.data_model.unwrap_memory().ptr(),
1724
dst_opts.data_model.unwrap_memory().ptr(),
1725
);
1726
let dst_len = self.local_tee_new_tmp(dst_opts.data_model.unwrap_memory().ptr());
1727
if dst_enc.width() > 1 {
1728
assert_eq!(dst_enc.width(), 2);
1729
self.ptr_uconst(dst_mem_opts, 1);
1730
self.ptr_shl(dst_mem_opts);
1731
}
1732
let dst_byte_len = self.local_set_new_tmp(dst_opts.data_model.unwrap_memory().ptr());
1733
1734
// Allocate space in the destination using the calculated byte
1735
// length.
1736
let dst = {
1737
let dst_mem = self.malloc(
1738
dst_opts,
1739
MallocSize::Local(dst_byte_len.idx),
1740
dst_enc.width().into(),
1741
);
1742
WasmString {
1743
ptr: dst_mem.addr,
1744
len: dst_len,
1745
opts: dst_opts,
1746
}
1747
};
1748
1749
// Validate that `src_len + src_ptr` and
1750
// `dst_mem.addr_local + dst_byte_len` are both in-bounds. This
1751
// is done by loading the last byte of the string and if that
1752
// doesn't trap then it's known valid.
1753
self.validate_string_inbounds(src, src_byte_len);
1754
self.validate_string_inbounds(&dst, dst_byte_len.idx);
1755
1756
// If the validations pass then the host `transcode` intrinsic
1757
// is invoked. This will either raise a trap or otherwise succeed
1758
// in which case we're done.
1759
let op = if src_enc == dst_enc {
1760
Transcode::Copy(src_enc)
1761
} else {
1762
assert_eq!(src_enc, FE::Latin1);
1763
assert_eq!(dst_enc, FE::Utf16);
1764
Transcode::Latin1ToUtf16
1765
};
1766
let transcode = self.transcoder(src, &dst, op);
1767
self.instruction(LocalGet(src.ptr.idx));
1768
self.instruction(LocalGet(src.len.idx));
1769
self.instruction(LocalGet(dst.ptr.idx));
1770
self.instruction(Call(transcode.as_u32()));
1771
1772
self.free_temp_local(dst_byte_len);
1773
if let Some(tmp) = src_byte_len_tmp {
1774
self.free_temp_local(tmp);
1775
}
1776
1777
dst
1778
}
1779
// Corresponding function for `store_string_to_utf8` in the spec.
1780
//
1781
// This translation works by possibly performing a number of
1782
// reallocations. First a buffer of size input-code-units is used to try
1783
// to get the transcoding correct on the first try. If that fails the
1784
// maximum worst-case size is used and then that is resized down if it's
1785
// too large.
1786
//
1787
// The host transcoding function imported here will receive src ptr/len
1788
// and dst ptr/len and return how many code units were consumed on both
1789
// sides. The amount of code units consumed in the source dictates which
1790
// branches are taken in this conversion.
1791
fn string_deflate_to_utf8<'c>(
1792
&mut self,
1793
src: &WasmString<'_>,
1794
src_enc: FE,
1795
dst_opts: &'c Options,
1796
) -> WasmString<'c> {
1797
let src_mem_opts = match &src.opts.data_model {
1798
DataModel::Gc {} => todo!("CM+GC"),
1799
DataModel::LinearMemory(opts) => opts,
1800
};
1801
let dst_mem_opts = match &dst_opts.data_model {
1802
DataModel::Gc {} => todo!("CM+GC"),
1803
DataModel::LinearMemory(opts) => opts,
1804
};
1805
1806
self.validate_string_length(src, src_enc);
1807
1808
// Optimistically assume that the code unit length of the source is
1809
// all that's needed in the destination. Perform that allocation
1810
// here and proceed to transcoding below.
1811
self.convert_src_len_to_dst(
1812
src.len.idx,
1813
src.opts.data_model.unwrap_memory().ptr(),
1814
dst_opts.data_model.unwrap_memory().ptr(),
1815
);
1816
let dst_len = self.local_tee_new_tmp(dst_opts.data_model.unwrap_memory().ptr());
1817
let dst_byte_len = self.local_set_new_tmp(dst_opts.data_model.unwrap_memory().ptr());
1818
1819
let dst = {
1820
let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 1);
1821
WasmString {
1822
ptr: dst_mem.addr,
1823
len: dst_len,
1824
opts: dst_opts,
1825
}
1826
};
1827
1828
// Ensure buffers are all in-bounds
1829
let mut src_byte_len_tmp = None;
1830
let src_byte_len = match src_enc {
1831
FE::Latin1 => src.len.idx,
1832
FE::Utf16 => {
1833
self.instruction(LocalGet(src.len.idx));
1834
self.ptr_uconst(src_mem_opts, 1);
1835
self.ptr_shl(src_mem_opts);
1836
let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());
1837
let ret = tmp.idx;
1838
src_byte_len_tmp = Some(tmp);
1839
ret
1840
}
1841
FE::Utf8 => unreachable!(),
1842
};
1843
self.validate_string_inbounds(src, src_byte_len);
1844
self.validate_string_inbounds(&dst, dst_byte_len.idx);
1845
1846
// Perform the initial transcode
1847
let op = match src_enc {
1848
FE::Latin1 => Transcode::Latin1ToUtf8,
1849
FE::Utf16 => Transcode::Utf16ToUtf8,
1850
FE::Utf8 => unreachable!(),
1851
};
1852
let transcode = self.transcoder(src, &dst, op);
1853
self.instruction(LocalGet(src.ptr.idx));
1854
self.instruction(LocalGet(src.len.idx));
1855
self.instruction(LocalGet(dst.ptr.idx));
1856
self.instruction(LocalGet(dst_byte_len.idx));
1857
self.instruction(Call(transcode.as_u32()));
1858
self.instruction(LocalSet(dst.len.idx));
1859
let src_len_tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());
1860
1861
// Test if the source was entirely transcoded by comparing
1862
// `src_len_tmp`, the number of code units transcoded from the
1863
// source, with `src_len`, the original number of code units.
1864
self.instruction(LocalGet(src_len_tmp.idx));
1865
self.instruction(LocalGet(src.len.idx));
1866
self.ptr_ne(src_mem_opts);
1867
self.instruction(If(BlockType::Empty));
1868
1869
// Here a worst-case reallocation is performed to grow `dst_mem`.
1870
// In-line a check is also performed that the worst-case byte size
1871
// fits within the maximum size of strings.
1872
self.instruction(LocalGet(dst.ptr.idx)); // old_ptr
1873
self.instruction(LocalGet(dst_byte_len.idx)); // old_size
1874
self.ptr_uconst(dst_mem_opts, 1); // align
1875
let factor = match src_enc {
1876
FE::Latin1 => 2,
1877
FE::Utf16 => 3,
1878
_ => unreachable!(),
1879
};
1880
self.validate_string_length_u8(src, factor);
1881
self.convert_src_len_to_dst(
1882
src.len.idx,
1883
src.opts.data_model.unwrap_memory().ptr(),
1884
dst_opts.data_model.unwrap_memory().ptr(),
1885
);
1886
self.ptr_uconst(dst_mem_opts, factor.into());
1887
self.ptr_mul(dst_mem_opts);
1888
self.instruction(LocalTee(dst_byte_len.idx));
1889
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
1890
self.instruction(LocalSet(dst.ptr.idx));
1891
1892
// Verify that the destination is still in-bounds
1893
self.validate_string_inbounds(&dst, dst_byte_len.idx);
1894
1895
// Perform another round of transcoding that should be guaranteed
1896
// to succeed. Note that all the parameters here are offset by the
1897
// results of the first transcoding to only perform the remaining
1898
// transcode on the final units.
1899
self.instruction(LocalGet(src.ptr.idx));
1900
self.instruction(LocalGet(src_len_tmp.idx));
1901
if let FE::Utf16 = src_enc {
1902
self.ptr_uconst(src_mem_opts, 1);
1903
self.ptr_shl(src_mem_opts);
1904
}
1905
self.ptr_add(src_mem_opts);
1906
self.instruction(LocalGet(src.len.idx));
1907
self.instruction(LocalGet(src_len_tmp.idx));
1908
self.ptr_sub(src_mem_opts);
1909
self.instruction(LocalGet(dst.ptr.idx));
1910
self.instruction(LocalGet(dst.len.idx));
1911
self.ptr_add(dst_mem_opts);
1912
self.instruction(LocalGet(dst_byte_len.idx));
1913
self.instruction(LocalGet(dst.len.idx));
1914
self.ptr_sub(dst_mem_opts);
1915
self.instruction(Call(transcode.as_u32()));
1916
1917
// Add the second result, the amount of destination units encoded,
1918
// to `dst_len` so it's an accurate reflection of the final size of
1919
// the destination buffer.
1920
self.instruction(LocalGet(dst.len.idx));
1921
self.ptr_add(dst_mem_opts);
1922
self.instruction(LocalSet(dst.len.idx));
1923
1924
// In debug mode verify the first result consumed the entire string,
1925
// otherwise simply discard it.
1926
if self.module.debug {
1927
self.instruction(LocalGet(src.len.idx));
1928
self.instruction(LocalGet(src_len_tmp.idx));
1929
self.ptr_sub(src_mem_opts);
1930
self.ptr_ne(src_mem_opts);
1931
self.instruction(If(BlockType::Empty));
1932
self.trap(Trap::AssertFailed("should have finished encoding"));
1933
self.instruction(End);
1934
} else {
1935
self.instruction(Drop);
1936
}
1937
1938
// Perform a downsizing if the worst-case size was too large
1939
self.instruction(LocalGet(dst.len.idx));
1940
self.instruction(LocalGet(dst_byte_len.idx));
1941
self.ptr_ne(dst_mem_opts);
1942
self.instruction(If(BlockType::Empty));
1943
self.instruction(LocalGet(dst.ptr.idx)); // old_ptr
1944
self.instruction(LocalGet(dst_byte_len.idx)); // old_size
1945
self.ptr_uconst(dst_mem_opts, 1); // align
1946
self.instruction(LocalGet(dst.len.idx)); // new_size
1947
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
1948
self.instruction(LocalSet(dst.ptr.idx));
1949
self.instruction(End);
1950
1951
// If the first transcode was enough then assert that the returned
1952
// amount of destination items written equals the byte size.
1953
if self.module.debug {
1954
self.instruction(Else);
1955
1956
self.instruction(LocalGet(dst.len.idx));
1957
self.instruction(LocalGet(dst_byte_len.idx));
1958
self.ptr_ne(dst_mem_opts);
1959
self.instruction(If(BlockType::Empty));
1960
self.trap(Trap::AssertFailed("should have finished encoding"));
1961
self.instruction(End);
1962
}
1963
1964
self.instruction(End); // end of "first transcode not enough"
1965
1966
self.free_temp_local(src_len_tmp);
1967
self.free_temp_local(dst_byte_len);
1968
if let Some(tmp) = src_byte_len_tmp {
1969
self.free_temp_local(tmp);
1970
}
1971
1972
dst
1973
}
1974
1975
// Corresponds to the `store_utf8_to_utf16` function in the spec.
1976
//
1977
// When converting utf-8 to utf-16 a pessimistic allocation is
1978
// done which is twice the byte length of the utf-8 string.
1979
// The host then transcodes and returns how many code units were
1980
// actually used during the transcoding and if it's beneath the
1981
// pessimistic maximum then the buffer is reallocated down to
1982
// a smaller amount.
1983
//
1984
// The host-imported transcoding function takes the src/dst pointer as
1985
// well as the code unit size of both the source and destination. The
1986
// destination should always be big enough to hold the result of the
1987
// transcode and so the result of the host function is how many code
1988
// units were written to the destination.
1989
fn string_utf8_to_utf16<'c>(
1990
&mut self,
1991
src: &WasmString<'_>,
1992
dst_opts: &'c Options,
1993
) -> WasmString<'c> {
1994
let src_mem_opts = match &src.opts.data_model {
1995
DataModel::Gc {} => todo!("CM+GC"),
1996
DataModel::LinearMemory(opts) => opts,
1997
};
1998
let dst_mem_opts = match &dst_opts.data_model {
1999
DataModel::Gc {} => todo!("CM+GC"),
2000
DataModel::LinearMemory(opts) => opts,
2001
};
2002
2003
self.validate_string_length(src, FE::Utf16);
2004
self.convert_src_len_to_dst(
2005
src.len.idx,
2006
src_mem_opts.ptr(),
2007
dst_opts.data_model.unwrap_memory().ptr(),
2008
);
2009
let dst_len = self.local_tee_new_tmp(dst_opts.data_model.unwrap_memory().ptr());
2010
self.ptr_uconst(dst_mem_opts, 1);
2011
self.ptr_shl(dst_mem_opts);
2012
let dst_byte_len = self.local_set_new_tmp(dst_opts.data_model.unwrap_memory().ptr());
2013
let dst = {
2014
let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 2);
2015
WasmString {
2016
ptr: dst_mem.addr,
2017
len: dst_len,
2018
opts: dst_opts,
2019
}
2020
};
2021
2022
self.validate_string_inbounds(src, src.len.idx);
2023
self.validate_string_inbounds(&dst, dst_byte_len.idx);
2024
2025
let transcode = self.transcoder(src, &dst, Transcode::Utf8ToUtf16);
2026
self.instruction(LocalGet(src.ptr.idx));
2027
self.instruction(LocalGet(src.len.idx));
2028
self.instruction(LocalGet(dst.ptr.idx));
2029
self.instruction(Call(transcode.as_u32()));
2030
self.instruction(LocalSet(dst.len.idx));
2031
2032
// If the number of code units returned by transcode is not
2033
// equal to the original number of code units then
2034
// the buffer must be shrunk.
2035
//
2036
// Note that the byte length of the final allocation we
2037
// want is twice the code unit length returned by the
2038
// transcoding function.
2039
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2040
self.instruction(LocalGet(dst.len.idx));
2041
self.ptr_ne(dst_mem_opts);
2042
self.instruction(If(BlockType::Empty));
2043
self.instruction(LocalGet(dst.ptr.idx));
2044
self.instruction(LocalGet(dst_byte_len.idx));
2045
self.ptr_uconst(dst_mem_opts, 2);
2046
self.instruction(LocalGet(dst.len.idx));
2047
self.ptr_uconst(dst_mem_opts, 1);
2048
self.ptr_shl(dst_mem_opts);
2049
self.instruction(Call(match dst.opts.data_model {
2050
DataModel::Gc {} => todo!("CM+GC"),
2051
DataModel::LinearMemory(LinearMemoryOptions { realloc, .. }) => {
2052
realloc.unwrap().as_u32()
2053
}
2054
}));
2055
self.instruction(LocalSet(dst.ptr.idx));
2056
self.instruction(End); // end of shrink-to-fit
2057
2058
self.free_temp_local(dst_byte_len);
2059
2060
dst
2061
}
2062
2063
// Corresponds to `store_probably_utf16_to_latin1_or_utf16` in the spec.
2064
//
2065
// This will try to transcode the input utf16 string to utf16 in the
2066
// destination. If utf16 isn't needed though and latin1 could be used
2067
// then that's used instead and a reallocation to downsize occurs
2068
// afterwards.
2069
//
2070
// The host transcode function here will take the src/dst pointers as
2071
// well as src length. The destination byte length is twice the src code
2072
// unit length. The return value is the tagged length of the returned
2073
// string. If the upper bit is set then utf16 was used and the
2074
// conversion is done. If the upper bit is not set then latin1 was used
2075
// and a downsizing needs to happen.
2076
fn string_compact_utf16_to_compact<'c>(
2077
&mut self,
2078
src: &WasmString<'_>,
2079
dst_opts: &'c Options,
2080
) -> WasmString<'c> {
2081
let src_mem_opts = match &src.opts.data_model {
2082
DataModel::Gc {} => todo!("CM+GC"),
2083
DataModel::LinearMemory(opts) => opts,
2084
};
2085
let dst_mem_opts = match &dst_opts.data_model {
2086
DataModel::Gc {} => todo!("CM+GC"),
2087
DataModel::LinearMemory(opts) => opts,
2088
};
2089
2090
self.validate_string_length(src, FE::Utf16);
2091
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2092
let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());
2093
self.ptr_uconst(dst_mem_opts, 1);
2094
self.ptr_shl(dst_mem_opts);
2095
let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());
2096
let dst = {
2097
let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 2);
2098
WasmString {
2099
ptr: dst_mem.addr,
2100
len: dst_len,
2101
opts: dst_opts,
2102
}
2103
};
2104
2105
self.convert_src_len_to_dst(
2106
dst_byte_len.idx,
2107
dst.opts.data_model.unwrap_memory().ptr(),
2108
src_mem_opts.ptr(),
2109
);
2110
let src_byte_len = self.local_set_new_tmp(src_mem_opts.ptr());
2111
2112
self.validate_string_inbounds(src, src_byte_len.idx);
2113
self.validate_string_inbounds(&dst, dst_byte_len.idx);
2114
2115
let transcode = self.transcoder(src, &dst, Transcode::Utf16ToCompactProbablyUtf16);
2116
self.instruction(LocalGet(src.ptr.idx));
2117
self.instruction(LocalGet(src.len.idx));
2118
self.instruction(LocalGet(dst.ptr.idx));
2119
self.instruction(Call(transcode.as_u32()));
2120
self.instruction(LocalSet(dst.len.idx));
2121
2122
// Assert that the untagged code unit length is the same as the
2123
// source code unit length.
2124
if self.module.debug {
2125
self.instruction(LocalGet(dst.len.idx));
2126
self.ptr_uconst(dst_mem_opts, !UTF16_TAG);
2127
self.ptr_and(dst_mem_opts);
2128
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2129
self.ptr_ne(dst_mem_opts);
2130
self.instruction(If(BlockType::Empty));
2131
self.trap(Trap::AssertFailed("expected equal code units"));
2132
self.instruction(End);
2133
}
2134
2135
// If the UTF16_TAG is set then utf16 was used and the destination
2136
// should be appropriately sized. Bail out of the "is this string
2137
// empty" block and fall through otherwise to resizing.
2138
self.instruction(LocalGet(dst.len.idx));
2139
self.ptr_uconst(dst_mem_opts, UTF16_TAG);
2140
self.ptr_and(dst_mem_opts);
2141
self.ptr_br_if(dst_mem_opts, 0);
2142
2143
// Here `realloc` is used to downsize the string
2144
self.instruction(LocalGet(dst.ptr.idx)); // old_ptr
2145
self.instruction(LocalGet(dst_byte_len.idx)); // old_size
2146
self.ptr_uconst(dst_mem_opts, 2); // align
2147
self.instruction(LocalGet(dst.len.idx)); // new_size
2148
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
2149
self.instruction(LocalSet(dst.ptr.idx));
2150
2151
self.free_temp_local(dst_byte_len);
2152
self.free_temp_local(src_byte_len);
2153
2154
dst
2155
}
2156
2157
// Corresponds to `store_string_to_latin1_or_utf16` in the spec.
2158
//
2159
// This will attempt a first pass of transcoding to latin1 and on
2160
// failure a larger buffer is allocated for utf16 and then utf16 is
2161
// encoded in-place into the buffer. After either latin1 or utf16 the
2162
// buffer is then resized to fit the final string allocation.
2163
fn string_to_compact<'c>(
2164
&mut self,
2165
src: &WasmString<'_>,
2166
src_enc: FE,
2167
dst_opts: &'c Options,
2168
) -> WasmString<'c> {
2169
let src_mem_opts = match &src.opts.data_model {
2170
DataModel::Gc {} => todo!("CM+GC"),
2171
DataModel::LinearMemory(opts) => opts,
2172
};
2173
let dst_mem_opts = match &dst_opts.data_model {
2174
DataModel::Gc {} => todo!("CM+GC"),
2175
DataModel::LinearMemory(opts) => opts,
2176
};
2177
2178
self.validate_string_length(src, src_enc);
2179
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2180
let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());
2181
let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());
2182
let dst = {
2183
let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), 2);
2184
WasmString {
2185
ptr: dst_mem.addr,
2186
len: dst_len,
2187
opts: dst_opts,
2188
}
2189
};
2190
2191
self.validate_string_inbounds(src, src.len.idx);
2192
self.validate_string_inbounds(&dst, dst_byte_len.idx);
2193
2194
// Perform the initial latin1 transcode. This returns the number of
2195
// source code units consumed and the number of destination code
2196
// units (bytes) written.
2197
let (latin1, utf16) = match src_enc {
2198
FE::Utf8 => (Transcode::Utf8ToLatin1, Transcode::Utf8ToCompactUtf16),
2199
FE::Utf16 => (Transcode::Utf16ToLatin1, Transcode::Utf16ToCompactUtf16),
2200
FE::Latin1 => unreachable!(),
2201
};
2202
let transcode_latin1 = self.transcoder(src, &dst, latin1);
2203
let transcode_utf16 = self.transcoder(src, &dst, utf16);
2204
self.instruction(LocalGet(src.ptr.idx));
2205
self.instruction(LocalGet(src.len.idx));
2206
self.instruction(LocalGet(dst.ptr.idx));
2207
self.instruction(Call(transcode_latin1.as_u32()));
2208
self.instruction(LocalSet(dst.len.idx));
2209
let src_len_tmp = self.local_set_new_tmp(src_mem_opts.ptr());
2210
2211
// If the source was entirely consumed then the transcode completed
2212
// and all that's necessary is to optionally shrink the buffer.
2213
self.instruction(LocalGet(src_len_tmp.idx));
2214
self.instruction(LocalGet(src.len.idx));
2215
self.ptr_eq(src_mem_opts);
2216
self.instruction(If(BlockType::Empty)); // if latin1-or-utf16 block
2217
2218
// Test if the original byte length of the allocation is the same as
2219
// the number of written bytes, and if not then shrink the buffer
2220
// with a call to `realloc`.
2221
self.instruction(LocalGet(dst_byte_len.idx));
2222
self.instruction(LocalGet(dst.len.idx));
2223
self.ptr_ne(dst_mem_opts);
2224
self.instruction(If(BlockType::Empty));
2225
self.instruction(LocalGet(dst.ptr.idx)); // old_ptr
2226
self.instruction(LocalGet(dst_byte_len.idx)); // old_size
2227
self.ptr_uconst(dst_mem_opts, 2); // align
2228
self.instruction(LocalGet(dst.len.idx)); // new_size
2229
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
2230
self.instruction(LocalSet(dst.ptr.idx));
2231
self.instruction(End);
2232
2233
// In this block the latin1 encoding failed. The host transcode
2234
// returned how many units were consumed from the source and how
2235
// many bytes were written to the destination. Here the buffer is
2236
// inflated and sized and the second utf16 intrinsic is invoked to
2237
// perform the final inflation.
2238
self.instruction(Else); // else latin1-or-utf16 block
2239
2240
// For utf8 validate that the inflated size is still within bounds.
2241
if src_enc.width() == 1 {
2242
self.validate_string_length_u8(src, 2);
2243
}
2244
2245
// Reallocate the buffer with twice the source code units in byte
2246
// size.
2247
self.instruction(LocalGet(dst.ptr.idx)); // old_ptr
2248
self.instruction(LocalGet(dst_byte_len.idx)); // old_size
2249
self.ptr_uconst(dst_mem_opts, 2); // align
2250
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2251
self.ptr_uconst(dst_mem_opts, 1);
2252
self.ptr_shl(dst_mem_opts);
2253
self.instruction(LocalTee(dst_byte_len.idx));
2254
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
2255
self.instruction(LocalSet(dst.ptr.idx));
2256
2257
// Call the host utf16 transcoding function. This will inflate the
2258
// prior latin1 bytes and then encode the rest of the source string
2259
// as utf16 into the remaining space in the destination buffer.
2260
self.instruction(LocalGet(src.ptr.idx));
2261
self.instruction(LocalGet(src_len_tmp.idx));
2262
if let FE::Utf16 = src_enc {
2263
self.ptr_uconst(src_mem_opts, 1);
2264
self.ptr_shl(src_mem_opts);
2265
}
2266
self.ptr_add(src_mem_opts);
2267
self.instruction(LocalGet(src.len.idx));
2268
self.instruction(LocalGet(src_len_tmp.idx));
2269
self.ptr_sub(src_mem_opts);
2270
self.instruction(LocalGet(dst.ptr.idx));
2271
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2272
self.instruction(LocalGet(dst.len.idx));
2273
self.instruction(Call(transcode_utf16.as_u32()));
2274
self.instruction(LocalSet(dst.len.idx));
2275
2276
// If the returned number of code units written to the destination
2277
// is not equal to the size of the allocation then the allocation is
2278
// resized down to the appropriate size.
2279
//
2280
// Note that the byte size desired is `2*dst_len` and the current
2281
// byte buffer size is `2*src_len` so the `2` factor isn't checked
2282
// here, just the lengths.
2283
self.instruction(LocalGet(dst.len.idx));
2284
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2285
self.ptr_ne(dst_mem_opts);
2286
self.instruction(If(BlockType::Empty));
2287
self.instruction(LocalGet(dst.ptr.idx)); // old_ptr
2288
self.instruction(LocalGet(dst_byte_len.idx)); // old_size
2289
self.ptr_uconst(dst_mem_opts, 2); // align
2290
self.instruction(LocalGet(dst.len.idx));
2291
self.ptr_uconst(dst_mem_opts, 1);
2292
self.ptr_shl(dst_mem_opts);
2293
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
2294
self.instruction(LocalSet(dst.ptr.idx));
2295
self.instruction(End);
2296
2297
// Tag the returned pointer as utf16
2298
self.instruction(LocalGet(dst.len.idx));
2299
self.ptr_uconst(dst_mem_opts, UTF16_TAG);
2300
self.ptr_or(dst_mem_opts);
2301
self.instruction(LocalSet(dst.len.idx));
2302
2303
self.instruction(End); // end latin1-or-utf16 block
2304
2305
self.free_temp_local(src_len_tmp);
2306
self.free_temp_local(dst_byte_len);
2307
2308
dst
2309
}
2310
2311
fn validate_string_length(&mut self, src: &WasmString<'_>, dst: FE) {
2312
self.validate_string_length_u8(src, dst.width())
2313
}
2314
2315
fn validate_string_length_u8(&mut self, s: &WasmString<'_>, dst: u8) {
2316
let mem_opts = match &s.opts.data_model {
2317
DataModel::Gc {} => todo!("CM+GC"),
2318
DataModel::LinearMemory(opts) => opts,
2319
};
2320
2321
// Check to see if the source byte length is out of bounds in
2322
// which case a trap is generated.
2323
self.instruction(LocalGet(s.len.idx));
2324
let max = MAX_STRING_BYTE_LENGTH / u32::from(dst);
2325
self.ptr_uconst(mem_opts, max);
2326
self.ptr_ge_u(mem_opts);
2327
self.instruction(If(BlockType::Empty));
2328
self.trap(Trap::StringLengthTooBig);
2329
self.instruction(End);
2330
}
2331
2332
fn transcoder(
2333
&mut self,
2334
src: &WasmString<'_>,
2335
dst: &WasmString<'_>,
2336
op: Transcode,
2337
) -> FuncIndex {
2338
match (src.opts.data_model, dst.opts.data_model) {
2339
(DataModel::Gc {}, _) | (_, DataModel::Gc {}) => {
2340
todo!("CM+GC")
2341
}
2342
(
2343
DataModel::LinearMemory(LinearMemoryOptions {
2344
memory64: src64,
2345
memory: src_mem,
2346
realloc: _,
2347
}),
2348
DataModel::LinearMemory(LinearMemoryOptions {
2349
memory64: dst64,
2350
memory: dst_mem,
2351
realloc: _,
2352
}),
2353
) => self.module.import_transcoder(Transcoder {
2354
from_memory: src_mem.unwrap(),
2355
from_memory64: src64,
2356
to_memory: dst_mem.unwrap(),
2357
to_memory64: dst64,
2358
op,
2359
}),
2360
}
2361
}
2362
2363
fn validate_string_inbounds(&mut self, s: &WasmString<'_>, byte_len: u32) {
2364
match &s.opts.data_model {
2365
DataModel::Gc {} => todo!("CM+GC"),
2366
DataModel::LinearMemory(opts) => {
2367
self.validate_memory_inbounds(opts, s.ptr.idx, byte_len, Trap::StringLengthOverflow)
2368
}
2369
}
2370
}
2371
2372
fn validate_memory_inbounds(
2373
&mut self,
2374
opts: &LinearMemoryOptions,
2375
ptr_local: u32,
2376
byte_len_local: u32,
2377
trap: Trap,
2378
) {
2379
let extend_to_64 = |me: &mut Self| {
2380
if !opts.memory64 {
2381
me.instruction(I64ExtendI32U);
2382
}
2383
};
2384
2385
self.instruction(Block(BlockType::Empty));
2386
self.instruction(Block(BlockType::Empty));
2387
2388
// Calculate the full byte size of memory with `memory.size`. Note that
2389
// arithmetic here is done always in 64-bits to accommodate 4G memories.
2390
// Additionally it's assumed that 64-bit memories never fill up
2391
// entirely.
2392
self.instruction(MemorySize(opts.memory.unwrap().as_u32()));
2393
extend_to_64(self);
2394
self.instruction(I64Const(16));
2395
self.instruction(I64Shl);
2396
2397
// Calculate the end address of the string. This is done by adding the
2398
// base pointer to the byte length. For 32-bit memories there's no need
2399
// to check for overflow since everything is extended to 64-bit, but for
2400
// 64-bit memories overflow is checked.
2401
self.instruction(LocalGet(ptr_local));
2402
extend_to_64(self);
2403
self.instruction(LocalGet(byte_len_local));
2404
extend_to_64(self);
2405
self.instruction(I64Add);
2406
if opts.memory64 {
2407
let tmp = self.local_tee_new_tmp(ValType::I64);
2408
self.instruction(LocalGet(ptr_local));
2409
self.ptr_lt_u(opts);
2410
self.instruction(BrIf(0));
2411
self.instruction(LocalGet(tmp.idx));
2412
self.free_temp_local(tmp);
2413
}
2414
2415
// If the byte size of memory is greater than the final address of the
2416
// string then the string is invalid. Note that if it's precisely equal
2417
// then that's ok.
2418
self.instruction(I64GeU);
2419
self.instruction(BrIf(1));
2420
2421
self.instruction(End);
2422
self.trap(trap);
2423
self.instruction(End);
2424
}
2425
2426
fn translate_list(
2427
&mut self,
2428
src_ty: TypeListIndex,
2429
src: &Source<'_>,
2430
dst_ty: &InterfaceType,
2431
dst: &Destination,
2432
) {
2433
let src_mem_opts = match &src.opts().data_model {
2434
DataModel::Gc {} => todo!("CM+GC"),
2435
DataModel::LinearMemory(opts) => opts,
2436
};
2437
let dst_mem_opts = match &dst.opts().data_model {
2438
DataModel::Gc {} => todo!("CM+GC"),
2439
DataModel::LinearMemory(opts) => opts,
2440
};
2441
2442
let src_element_ty = &self.types[src_ty].element;
2443
let dst_element_ty = match dst_ty {
2444
InterfaceType::List(r) => &self.types[*r].element,
2445
_ => panic!("expected a list"),
2446
};
2447
let src_opts = src.opts();
2448
let dst_opts = dst.opts();
2449
let (src_size, src_align) = self.types.size_align(src_mem_opts, src_element_ty);
2450
let (dst_size, dst_align) = self.types.size_align(dst_mem_opts, dst_element_ty);
2451
2452
// Load the pointer/length of this list into temporary locals. These
2453
// will be referenced a good deal so this just makes it easier to deal
2454
// with them consistently below rather than trying to reload from memory
2455
// for example.
2456
match src {
2457
Source::Stack(s) => {
2458
assert_eq!(s.locals.len(), 2);
2459
self.stack_get(&s.slice(0..1), src_mem_opts.ptr());
2460
self.stack_get(&s.slice(1..2), src_mem_opts.ptr());
2461
}
2462
Source::Memory(mem) => {
2463
self.ptr_load(mem);
2464
self.ptr_load(&mem.bump(src_mem_opts.ptr_size().into()));
2465
}
2466
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
2467
}
2468
let src_len = self.local_set_new_tmp(src_mem_opts.ptr());
2469
let src_ptr = self.local_set_new_tmp(src_mem_opts.ptr());
2470
2471
// Create a `Memory` operand which will internally assert that the
2472
// `src_ptr` value is properly aligned.
2473
let src_mem = self.memory_operand(src_opts, src_ptr, src_align);
2474
2475
// Calculate the source/destination byte lengths into unique locals.
2476
let src_byte_len = self.calculate_list_byte_len(src_mem_opts, src_len.idx, src_size);
2477
let dst_byte_len = if src_size == dst_size {
2478
self.convert_src_len_to_dst(src_byte_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2479
self.local_set_new_tmp(dst_mem_opts.ptr())
2480
} else if src_mem_opts.ptr() == dst_mem_opts.ptr() {
2481
self.calculate_list_byte_len(dst_mem_opts, src_len.idx, dst_size)
2482
} else {
2483
self.convert_src_len_to_dst(src_byte_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2484
let tmp = self.local_set_new_tmp(dst_mem_opts.ptr());
2485
let ret = self.calculate_list_byte_len(dst_mem_opts, tmp.idx, dst_size);
2486
self.free_temp_local(tmp);
2487
ret
2488
};
2489
2490
// Here `realloc` is invoked (in a `malloc`-like fashion) to allocate
2491
// space for the list in the destination memory. This will also
2492
// internally insert checks that the returned pointer is aligned
2493
// correctly for the destination.
2494
let dst_mem = self.malloc(dst_opts, MallocSize::Local(dst_byte_len.idx), dst_align);
2495
2496
// With all the pointers and byte lengths verity that both the source
2497
// and the destination buffers are in-bounds.
2498
self.validate_memory_inbounds(
2499
src_mem_opts,
2500
src_mem.addr.idx,
2501
src_byte_len.idx,
2502
Trap::ListByteLengthOverflow,
2503
);
2504
self.validate_memory_inbounds(
2505
dst_mem_opts,
2506
dst_mem.addr.idx,
2507
dst_byte_len.idx,
2508
Trap::ListByteLengthOverflow,
2509
);
2510
2511
self.free_temp_local(src_byte_len);
2512
self.free_temp_local(dst_byte_len);
2513
2514
// This is the main body of the loop to actually translate list types.
2515
// Note that if both element sizes are 0 then this won't actually do
2516
// anything so the loop is removed entirely.
2517
if src_size > 0 || dst_size > 0 {
2518
// This block encompasses the entire loop and is use to exit before even
2519
// entering the loop if the list size is zero.
2520
self.instruction(Block(BlockType::Empty));
2521
2522
// Set the `remaining` local and only continue if it's > 0
2523
self.instruction(LocalGet(src_len.idx));
2524
let remaining = self.local_tee_new_tmp(src_mem_opts.ptr());
2525
self.ptr_eqz(src_mem_opts);
2526
self.instruction(BrIf(0));
2527
2528
// Initialize the two destination pointers to their initial values
2529
self.instruction(LocalGet(src_mem.addr.idx));
2530
let cur_src_ptr = self.local_set_new_tmp(src_mem_opts.ptr());
2531
self.instruction(LocalGet(dst_mem.addr.idx));
2532
let cur_dst_ptr = self.local_set_new_tmp(dst_mem_opts.ptr());
2533
2534
self.instruction(Loop(BlockType::Empty));
2535
2536
// Translate the next element in the list
2537
let element_src = Source::Memory(Memory {
2538
opts: src_opts,
2539
offset: 0,
2540
addr: TempLocal::new(cur_src_ptr.idx, cur_src_ptr.ty),
2541
});
2542
let element_dst = Destination::Memory(Memory {
2543
opts: dst_opts,
2544
offset: 0,
2545
addr: TempLocal::new(cur_dst_ptr.idx, cur_dst_ptr.ty),
2546
});
2547
self.translate(src_element_ty, &element_src, dst_element_ty, &element_dst);
2548
2549
// Update the two loop pointers
2550
if src_size > 0 {
2551
self.instruction(LocalGet(cur_src_ptr.idx));
2552
self.ptr_uconst(src_mem_opts, src_size);
2553
self.ptr_add(src_mem_opts);
2554
self.instruction(LocalSet(cur_src_ptr.idx));
2555
}
2556
if dst_size > 0 {
2557
self.instruction(LocalGet(cur_dst_ptr.idx));
2558
self.ptr_uconst(dst_mem_opts, dst_size);
2559
self.ptr_add(dst_mem_opts);
2560
self.instruction(LocalSet(cur_dst_ptr.idx));
2561
}
2562
2563
// Update the remaining count, falling through to break out if it's zero
2564
// now.
2565
self.instruction(LocalGet(remaining.idx));
2566
self.ptr_iconst(src_mem_opts, -1);
2567
self.ptr_add(src_mem_opts);
2568
self.instruction(LocalTee(remaining.idx));
2569
self.ptr_br_if(src_mem_opts, 0);
2570
self.instruction(End); // end of loop
2571
self.instruction(End); // end of block
2572
2573
self.free_temp_local(cur_dst_ptr);
2574
self.free_temp_local(cur_src_ptr);
2575
self.free_temp_local(remaining);
2576
}
2577
2578
// Store the ptr/length in the desired destination
2579
match dst {
2580
Destination::Stack(s, _) => {
2581
self.instruction(LocalGet(dst_mem.addr.idx));
2582
self.stack_set(&s[..1], dst_mem_opts.ptr());
2583
self.convert_src_len_to_dst(src_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2584
self.stack_set(&s[1..], dst_mem_opts.ptr());
2585
}
2586
Destination::Memory(mem) => {
2587
self.instruction(LocalGet(mem.addr.idx));
2588
self.instruction(LocalGet(dst_mem.addr.idx));
2589
self.ptr_store(mem);
2590
self.instruction(LocalGet(mem.addr.idx));
2591
self.convert_src_len_to_dst(src_len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
2592
self.ptr_store(&mem.bump(dst_mem_opts.ptr_size().into()));
2593
}
2594
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
2595
}
2596
2597
self.free_temp_local(src_len);
2598
self.free_temp_local(src_mem.addr);
2599
self.free_temp_local(dst_mem.addr);
2600
}
2601
2602
fn calculate_list_byte_len(
2603
&mut self,
2604
opts: &LinearMemoryOptions,
2605
len_local: u32,
2606
elt_size: u32,
2607
) -> TempLocal {
2608
// Zero-size types are easy to handle here because the byte size of the
2609
// destination is always zero.
2610
if elt_size == 0 {
2611
self.ptr_uconst(opts, 0);
2612
return self.local_set_new_tmp(opts.ptr());
2613
}
2614
2615
// For one-byte elements in the destination the check here can be a bit
2616
// more optimal than the general case below. In these situations if the
2617
// source pointer type is 32-bit then we're guaranteed to not overflow,
2618
// so the source length is simply casted to the destination's type.
2619
//
2620
// If the source is 64-bit then all that needs to be checked is to
2621
// ensure that it does not have the upper 32-bits set.
2622
if elt_size == 1 {
2623
if let ValType::I64 = opts.ptr() {
2624
self.instruction(LocalGet(len_local));
2625
self.instruction(I64Const(32));
2626
self.instruction(I64ShrU);
2627
self.instruction(I32WrapI64);
2628
self.instruction(If(BlockType::Empty));
2629
self.trap(Trap::ListByteLengthOverflow);
2630
self.instruction(End);
2631
}
2632
self.instruction(LocalGet(len_local));
2633
return self.local_set_new_tmp(opts.ptr());
2634
}
2635
2636
// The main check implemented by this function is to verify that
2637
// `src_len_local` does not exceed the 32-bit range. Byte sizes for
2638
// lists must always fit in 32-bits to get transferred to 32-bit
2639
// memories.
2640
self.instruction(Block(BlockType::Empty));
2641
self.instruction(Block(BlockType::Empty));
2642
self.instruction(LocalGet(len_local));
2643
match opts.ptr() {
2644
// The source's list length is guaranteed to be less than 32-bits
2645
// so simply extend it up to a 64-bit type for the multiplication
2646
// below.
2647
ValType::I32 => self.instruction(I64ExtendI32U),
2648
2649
// If the source is a 64-bit memory then if the item length doesn't
2650
// fit in 32-bits the byte length definitely won't, so generate a
2651
// branch to our overflow trap here if any of the upper 32-bits are set.
2652
ValType::I64 => {
2653
self.instruction(I64Const(32));
2654
self.instruction(I64ShrU);
2655
self.instruction(I32WrapI64);
2656
self.instruction(BrIf(0));
2657
self.instruction(LocalGet(len_local));
2658
}
2659
2660
_ => unreachable!(),
2661
}
2662
2663
// Next perform a 64-bit multiplication with the element byte size that
2664
// is itself guaranteed to fit in 32-bits. The result is then checked
2665
// to see if we overflowed the 32-bit space. The two input operands to
2666
// the multiplication are guaranteed to be 32-bits at most which means
2667
// that this multiplication shouldn't overflow.
2668
//
2669
// The result of the multiplication is saved into a local as well to
2670
// get the result afterwards.
2671
self.instruction(I64Const(elt_size.into()));
2672
self.instruction(I64Mul);
2673
let tmp = self.local_tee_new_tmp(ValType::I64);
2674
// Branch to success if the upper 32-bits are zero, otherwise
2675
// fall-through to the trap.
2676
self.instruction(I64Const(32));
2677
self.instruction(I64ShrU);
2678
self.instruction(I64Eqz);
2679
self.instruction(BrIf(1));
2680
self.instruction(End);
2681
self.trap(Trap::ListByteLengthOverflow);
2682
self.instruction(End);
2683
2684
// If a fresh local was used to store the result of the multiplication
2685
// then convert it down to 32-bits which should be guaranteed to not
2686
// lose information at this point.
2687
if opts.ptr() == ValType::I64 {
2688
tmp
2689
} else {
2690
self.instruction(LocalGet(tmp.idx));
2691
self.instruction(I32WrapI64);
2692
self.free_temp_local(tmp);
2693
self.local_set_new_tmp(ValType::I32)
2694
}
2695
}
2696
2697
fn convert_src_len_to_dst(
2698
&mut self,
2699
src_len_local: u32,
2700
src_ptr_ty: ValType,
2701
dst_ptr_ty: ValType,
2702
) {
2703
self.instruction(LocalGet(src_len_local));
2704
match (src_ptr_ty, dst_ptr_ty) {
2705
(ValType::I32, ValType::I64) => self.instruction(I64ExtendI32U),
2706
(ValType::I64, ValType::I32) => self.instruction(I32WrapI64),
2707
(src, dst) => assert_eq!(src, dst),
2708
}
2709
}
2710
2711
fn translate_record(
2712
&mut self,
2713
src_ty: TypeRecordIndex,
2714
src: &Source<'_>,
2715
dst_ty: &InterfaceType,
2716
dst: &Destination,
2717
) {
2718
let src_ty = &self.types[src_ty];
2719
let dst_ty = match dst_ty {
2720
InterfaceType::Record(r) => &self.types[*r],
2721
_ => panic!("expected a record"),
2722
};
2723
2724
// TODO: subtyping
2725
assert_eq!(src_ty.fields.len(), dst_ty.fields.len());
2726
2727
// First a map is made of the source fields to where they're coming
2728
// from (e.g. which offset or which locals). This map is keyed by the
2729
// fields' names
2730
let mut src_fields = HashMap::new();
2731
for (i, src) in src
2732
.record_field_srcs(self.types, src_ty.fields.iter().map(|f| f.ty))
2733
.enumerate()
2734
{
2735
let field = &src_ty.fields[i];
2736
src_fields.insert(&field.name, (src, &field.ty));
2737
}
2738
2739
// .. and next translation is performed in the order of the destination
2740
// fields in case the destination is the stack to ensure that the stack
2741
// has the fields all in the right order.
2742
//
2743
// Note that the lookup in `src_fields` is an infallible lookup which
2744
// will panic if the field isn't found.
2745
//
2746
// TODO: should that lookup be fallible with subtyping?
2747
for (i, dst) in dst
2748
.record_field_dsts(self.types, dst_ty.fields.iter().map(|f| f.ty))
2749
.enumerate()
2750
{
2751
let field = &dst_ty.fields[i];
2752
let (src, src_ty) = &src_fields[&field.name];
2753
self.translate(src_ty, src, &field.ty, &dst);
2754
}
2755
}
2756
2757
fn translate_flags(
2758
&mut self,
2759
src_ty: TypeFlagsIndex,
2760
src: &Source<'_>,
2761
dst_ty: &InterfaceType,
2762
dst: &Destination,
2763
) {
2764
let src_ty = &self.types[src_ty];
2765
let dst_ty = match dst_ty {
2766
InterfaceType::Flags(r) => &self.types[*r],
2767
_ => panic!("expected a record"),
2768
};
2769
2770
// TODO: subtyping
2771
//
2772
// Notably this implementation does not support reordering flags from
2773
// the source to the destination nor having more flags in the
2774
// destination. Currently this is a copy from source to destination
2775
// in-bulk. Otherwise reordering indices would have to have some sort of
2776
// fancy bit twiddling tricks or something like that.
2777
assert_eq!(src_ty.names, dst_ty.names);
2778
let cnt = src_ty.names.len();
2779
match FlagsSize::from_count(cnt) {
2780
FlagsSize::Size0 => {}
2781
FlagsSize::Size1 => {
2782
let mask = if cnt == 8 { 0xff } else { (1 << cnt) - 1 };
2783
self.convert_u8_mask(src, dst, mask);
2784
}
2785
FlagsSize::Size2 => {
2786
let mask = if cnt == 16 { 0xffff } else { (1 << cnt) - 1 };
2787
self.convert_u16_mask(src, dst, mask);
2788
}
2789
FlagsSize::Size4Plus(n) => {
2790
let srcs = src.record_field_srcs(self.types, (0..n).map(|_| InterfaceType::U32));
2791
let dsts = dst.record_field_dsts(self.types, (0..n).map(|_| InterfaceType::U32));
2792
let n = usize::from(n);
2793
for (i, (src, dst)) in srcs.zip(dsts).enumerate() {
2794
let mask = if i == n - 1 && (cnt % 32 != 0) {
2795
(1 << (cnt % 32)) - 1
2796
} else {
2797
0xffffffff
2798
};
2799
self.convert_u32_mask(&src, &dst, mask);
2800
}
2801
}
2802
}
2803
}
2804
2805
fn translate_tuple(
2806
&mut self,
2807
src_ty: TypeTupleIndex,
2808
src: &Source<'_>,
2809
dst_ty: &InterfaceType,
2810
dst: &Destination,
2811
) {
2812
let src_ty = &self.types[src_ty];
2813
let dst_ty = match dst_ty {
2814
InterfaceType::Tuple(t) => &self.types[*t],
2815
_ => panic!("expected a tuple"),
2816
};
2817
2818
// TODO: subtyping
2819
assert_eq!(src_ty.types.len(), dst_ty.types.len());
2820
2821
let srcs = src
2822
.record_field_srcs(self.types, src_ty.types.iter().copied())
2823
.zip(src_ty.types.iter());
2824
let dsts = dst
2825
.record_field_dsts(self.types, dst_ty.types.iter().copied())
2826
.zip(dst_ty.types.iter());
2827
for ((src, src_ty), (dst, dst_ty)) in srcs.zip(dsts) {
2828
self.translate(src_ty, &src, dst_ty, &dst);
2829
}
2830
}
2831
2832
fn translate_variant(
2833
&mut self,
2834
src_ty: TypeVariantIndex,
2835
src: &Source<'_>,
2836
dst_ty: &InterfaceType,
2837
dst: &Destination,
2838
) {
2839
let src_ty = &self.types[src_ty];
2840
let dst_ty = match dst_ty {
2841
InterfaceType::Variant(t) => &self.types[*t],
2842
_ => panic!("expected a variant"),
2843
};
2844
2845
let src_info = variant_info(self.types, src_ty.cases.iter().map(|(_, c)| c.as_ref()));
2846
let dst_info = variant_info(self.types, dst_ty.cases.iter().map(|(_, c)| c.as_ref()));
2847
2848
let iter = src_ty
2849
.cases
2850
.iter()
2851
.enumerate()
2852
.map(|(src_i, (src_case, src_case_ty))| {
2853
let dst_i = dst_ty
2854
.cases
2855
.iter()
2856
.position(|(c, _)| c == src_case)
2857
.unwrap();
2858
let dst_case_ty = &dst_ty.cases[dst_i];
2859
let src_i = u32::try_from(src_i).unwrap();
2860
let dst_i = u32::try_from(dst_i).unwrap();
2861
VariantCase {
2862
src_i,
2863
src_ty: src_case_ty.as_ref(),
2864
dst_i,
2865
dst_ty: dst_case_ty.as_ref(),
2866
}
2867
});
2868
self.convert_variant(src, &src_info, dst, &dst_info, iter);
2869
}
2870
2871
fn translate_enum(
2872
&mut self,
2873
src_ty: TypeEnumIndex,
2874
src: &Source<'_>,
2875
dst_ty: &InterfaceType,
2876
dst: &Destination,
2877
) {
2878
let src_ty = &self.types[src_ty];
2879
let dst_ty = match dst_ty {
2880
InterfaceType::Enum(t) => &self.types[*t],
2881
_ => panic!("expected an option"),
2882
};
2883
2884
debug_assert_eq!(src_ty.info.size, dst_ty.info.size);
2885
debug_assert_eq!(src_ty.names.len(), dst_ty.names.len());
2886
debug_assert!(
2887
src_ty
2888
.names
2889
.iter()
2890
.zip(dst_ty.names.iter())
2891
.all(|(a, b)| a == b)
2892
);
2893
2894
// Get the discriminant.
2895
match src {
2896
Source::Stack(s) => self.stack_get(&s.slice(0..1), ValType::I32),
2897
Source::Memory(mem) => match src_ty.info.size {
2898
DiscriminantSize::Size1 => self.i32_load8u(mem),
2899
DiscriminantSize::Size2 => self.i32_load16u(mem),
2900
DiscriminantSize::Size4 => self.i32_load(mem),
2901
},
2902
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
2903
}
2904
let tmp = self.local_tee_new_tmp(ValType::I32);
2905
2906
// Assert that the discriminant is valid.
2907
self.instruction(I32Const(i32::try_from(src_ty.names.len()).unwrap()));
2908
self.instruction(I32GtU);
2909
self.instruction(If(BlockType::Empty));
2910
self.trap(Trap::InvalidDiscriminant);
2911
self.instruction(End);
2912
2913
// Save the discriminant to the destination.
2914
match dst {
2915
Destination::Stack(stack, _) => {
2916
self.local_get_tmp(&tmp);
2917
self.stack_set(&stack[..1], ValType::I32)
2918
}
2919
Destination::Memory(mem) => {
2920
self.push_dst_addr(dst);
2921
self.local_get_tmp(&tmp);
2922
match dst_ty.info.size {
2923
DiscriminantSize::Size1 => self.i32_store8(mem),
2924
DiscriminantSize::Size2 => self.i32_store16(mem),
2925
DiscriminantSize::Size4 => self.i32_store(mem),
2926
}
2927
}
2928
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
2929
}
2930
self.free_temp_local(tmp);
2931
}
2932
2933
fn translate_option(
2934
&mut self,
2935
src_ty: TypeOptionIndex,
2936
src: &Source<'_>,
2937
dst_ty: &InterfaceType,
2938
dst: &Destination,
2939
) {
2940
let src_ty = &self.types[src_ty].ty;
2941
let dst_ty = match dst_ty {
2942
InterfaceType::Option(t) => &self.types[*t].ty,
2943
_ => panic!("expected an option"),
2944
};
2945
let src_ty = Some(src_ty);
2946
let dst_ty = Some(dst_ty);
2947
2948
let src_info = variant_info(self.types, [None, src_ty]);
2949
let dst_info = variant_info(self.types, [None, dst_ty]);
2950
2951
self.convert_variant(
2952
src,
2953
&src_info,
2954
dst,
2955
&dst_info,
2956
[
2957
VariantCase {
2958
src_i: 0,
2959
dst_i: 0,
2960
src_ty: None,
2961
dst_ty: None,
2962
},
2963
VariantCase {
2964
src_i: 1,
2965
dst_i: 1,
2966
src_ty,
2967
dst_ty,
2968
},
2969
]
2970
.into_iter(),
2971
);
2972
}
2973
2974
fn translate_result(
2975
&mut self,
2976
src_ty: TypeResultIndex,
2977
src: &Source<'_>,
2978
dst_ty: &InterfaceType,
2979
dst: &Destination,
2980
) {
2981
let src_ty = &self.types[src_ty];
2982
let dst_ty = match dst_ty {
2983
InterfaceType::Result(t) => &self.types[*t],
2984
_ => panic!("expected a result"),
2985
};
2986
2987
let src_info = variant_info(self.types, [src_ty.ok.as_ref(), src_ty.err.as_ref()]);
2988
let dst_info = variant_info(self.types, [dst_ty.ok.as_ref(), dst_ty.err.as_ref()]);
2989
2990
self.convert_variant(
2991
src,
2992
&src_info,
2993
dst,
2994
&dst_info,
2995
[
2996
VariantCase {
2997
src_i: 0,
2998
dst_i: 0,
2999
src_ty: src_ty.ok.as_ref(),
3000
dst_ty: dst_ty.ok.as_ref(),
3001
},
3002
VariantCase {
3003
src_i: 1,
3004
dst_i: 1,
3005
src_ty: src_ty.err.as_ref(),
3006
dst_ty: dst_ty.err.as_ref(),
3007
},
3008
]
3009
.into_iter(),
3010
);
3011
}
3012
3013
fn convert_variant<'c>(
3014
&mut self,
3015
src: &Source<'_>,
3016
src_info: &VariantInfo,
3017
dst: &Destination,
3018
dst_info: &VariantInfo,
3019
src_cases: impl ExactSizeIterator<Item = VariantCase<'c>>,
3020
) {
3021
// The outermost block is special since it has the result type of the
3022
// translation here. That will depend on the `dst`.
3023
let outer_block_ty = match dst {
3024
Destination::Stack(dst_flat, _) => match dst_flat.len() {
3025
0 => BlockType::Empty,
3026
1 => BlockType::Result(dst_flat[0]),
3027
_ => {
3028
let ty = self.module.core_types.function(&[], &dst_flat);
3029
BlockType::FunctionType(ty)
3030
}
3031
},
3032
Destination::Memory(_) => BlockType::Empty,
3033
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
3034
};
3035
self.instruction(Block(outer_block_ty));
3036
3037
// After the outermost block generate a new block for each of the
3038
// remaining cases.
3039
let src_cases_len = src_cases.len();
3040
for _ in 0..src_cases_len - 1 {
3041
self.instruction(Block(BlockType::Empty));
3042
}
3043
3044
// Generate a block for an invalid variant discriminant
3045
self.instruction(Block(BlockType::Empty));
3046
3047
// And generate one final block that we'll be jumping out of with the
3048
// `br_table`
3049
self.instruction(Block(BlockType::Empty));
3050
3051
// Load the discriminant
3052
match src {
3053
Source::Stack(s) => self.stack_get(&s.slice(0..1), ValType::I32),
3054
Source::Memory(mem) => match src_info.size {
3055
DiscriminantSize::Size1 => self.i32_load8u(mem),
3056
DiscriminantSize::Size2 => self.i32_load16u(mem),
3057
DiscriminantSize::Size4 => self.i32_load(mem),
3058
},
3059
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
3060
}
3061
3062
// Generate the `br_table` for the discriminant. Each case has an
3063
// offset of 1 to skip the trapping block.
3064
let mut targets = Vec::new();
3065
for i in 0..src_cases_len {
3066
targets.push((i + 1) as u32);
3067
}
3068
self.instruction(BrTable(targets[..].into(), 0));
3069
self.instruction(End); // end the `br_table` block
3070
3071
self.trap(Trap::InvalidDiscriminant);
3072
self.instruction(End); // end the "invalid discriminant" block
3073
3074
// Translate each case individually within its own block. Note that the
3075
// iteration order here places the first case in the innermost block
3076
// and the last case in the outermost block. This matches the order
3077
// of the jump targets in the `br_table` instruction.
3078
let src_cases_len = u32::try_from(src_cases_len).unwrap();
3079
for case in src_cases {
3080
let VariantCase {
3081
src_i,
3082
src_ty,
3083
dst_i,
3084
dst_ty,
3085
} = case;
3086
3087
// Translate the discriminant here, noting that `dst_i` may be
3088
// different than `src_i`.
3089
self.push_dst_addr(dst);
3090
self.instruction(I32Const(dst_i as i32));
3091
match dst {
3092
Destination::Stack(stack, _) => self.stack_set(&stack[..1], ValType::I32),
3093
Destination::Memory(mem) => match dst_info.size {
3094
DiscriminantSize::Size1 => self.i32_store8(mem),
3095
DiscriminantSize::Size2 => self.i32_store16(mem),
3096
DiscriminantSize::Size4 => self.i32_store(mem),
3097
},
3098
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
3099
}
3100
3101
let src_payload = src.payload_src(self.types, src_info, src_ty);
3102
let dst_payload = dst.payload_dst(self.types, dst_info, dst_ty);
3103
3104
// Translate the payload of this case using the various types from
3105
// the dst/src.
3106
match (src_ty, dst_ty) {
3107
(Some(src_ty), Some(dst_ty)) => {
3108
self.translate(src_ty, &src_payload, dst_ty, &dst_payload);
3109
}
3110
(None, None) => {}
3111
_ => unimplemented!(),
3112
}
3113
3114
// If the results of this translation were placed on the stack then
3115
// the stack values may need to be padded with more zeros due to
3116
// this particular case being possibly smaller than the entire
3117
// variant. That's handled here by pushing remaining zeros after
3118
// accounting for the discriminant pushed as well as the results of
3119
// this individual payload.
3120
if let Destination::Stack(payload_results, _) = dst_payload {
3121
if let Destination::Stack(dst_results, _) = dst {
3122
let remaining = &dst_results[1..][payload_results.len()..];
3123
for ty in remaining {
3124
match ty {
3125
ValType::I32 => self.instruction(I32Const(0)),
3126
ValType::I64 => self.instruction(I64Const(0)),
3127
ValType::F32 => self.instruction(F32Const(0.0.into())),
3128
ValType::F64 => self.instruction(F64Const(0.0.into())),
3129
_ => unreachable!(),
3130
}
3131
}
3132
}
3133
}
3134
3135
// Branch to the outermost block. Note that this isn't needed for
3136
// the outermost case since it simply falls through.
3137
if src_i != src_cases_len - 1 {
3138
self.instruction(Br(src_cases_len - src_i - 1));
3139
}
3140
self.instruction(End); // end this case's block
3141
}
3142
}
3143
3144
fn translate_future(
3145
&mut self,
3146
src_ty: TypeFutureTableIndex,
3147
src: &Source<'_>,
3148
dst_ty: &InterfaceType,
3149
dst: &Destination,
3150
) {
3151
let dst_ty = match dst_ty {
3152
InterfaceType::Future(t) => *t,
3153
_ => panic!("expected a `Future`"),
3154
};
3155
let transfer = self.module.import_future_transfer();
3156
self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);
3157
}
3158
3159
fn translate_stream(
3160
&mut self,
3161
src_ty: TypeStreamTableIndex,
3162
src: &Source<'_>,
3163
dst_ty: &InterfaceType,
3164
dst: &Destination,
3165
) {
3166
let dst_ty = match dst_ty {
3167
InterfaceType::Stream(t) => *t,
3168
_ => panic!("expected a `Stream`"),
3169
};
3170
let transfer = self.module.import_stream_transfer();
3171
self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);
3172
}
3173
3174
fn translate_error_context(
3175
&mut self,
3176
src_ty: TypeComponentLocalErrorContextTableIndex,
3177
src: &Source<'_>,
3178
dst_ty: &InterfaceType,
3179
dst: &Destination,
3180
) {
3181
let dst_ty = match dst_ty {
3182
InterfaceType::ErrorContext(t) => *t,
3183
_ => panic!("expected an `ErrorContext`"),
3184
};
3185
let transfer = self.module.import_error_context_transfer();
3186
self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);
3187
}
3188
3189
fn translate_own(
3190
&mut self,
3191
src_ty: TypeResourceTableIndex,
3192
src: &Source<'_>,
3193
dst_ty: &InterfaceType,
3194
dst: &Destination,
3195
) {
3196
let dst_ty = match dst_ty {
3197
InterfaceType::Own(t) => *t,
3198
_ => panic!("expected an `Own`"),
3199
};
3200
let transfer = self.module.import_resource_transfer_own();
3201
self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);
3202
}
3203
3204
fn translate_borrow(
3205
&mut self,
3206
src_ty: TypeResourceTableIndex,
3207
src: &Source<'_>,
3208
dst_ty: &InterfaceType,
3209
dst: &Destination,
3210
) {
3211
let dst_ty = match dst_ty {
3212
InterfaceType::Borrow(t) => *t,
3213
_ => panic!("expected an `Borrow`"),
3214
};
3215
3216
let transfer = self.module.import_resource_transfer_borrow();
3217
self.translate_handle(src_ty.as_u32(), src, dst_ty.as_u32(), dst, transfer);
3218
}
3219
3220
/// Translates the index `src`, which resides in the table `src_ty`, into
3221
/// and index within `dst_ty` and is stored at `dst`.
3222
///
3223
/// Actual translation of the index happens in a wasmtime libcall, which a
3224
/// cranelift-generated trampoline to satisfy this import will call. The
3225
/// `transfer` function is an imported function which takes the src, src_ty,
3226
/// and dst_ty, and returns the dst index.
3227
fn translate_handle(
3228
&mut self,
3229
src_ty: u32,
3230
src: &Source<'_>,
3231
dst_ty: u32,
3232
dst: &Destination,
3233
transfer: FuncIndex,
3234
) {
3235
self.push_dst_addr(dst);
3236
match src {
3237
Source::Memory(mem) => self.i32_load(mem),
3238
Source::Stack(stack) => self.stack_get(stack, ValType::I32),
3239
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
3240
}
3241
self.instruction(I32Const(src_ty as i32));
3242
self.instruction(I32Const(dst_ty as i32));
3243
self.instruction(Call(transfer.as_u32()));
3244
match dst {
3245
Destination::Memory(mem) => self.i32_store(mem),
3246
Destination::Stack(stack, _) => self.stack_set(stack, ValType::I32),
3247
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
3248
}
3249
}
3250
3251
fn trap_if_not_flag(&mut self, flags_global: GlobalIndex, flag_to_test: i32, trap: Trap) {
3252
self.instruction(GlobalGet(flags_global.as_u32()));
3253
self.instruction(I32Const(flag_to_test));
3254
self.instruction(I32And);
3255
self.instruction(I32Eqz);
3256
self.instruction(If(BlockType::Empty));
3257
self.trap(trap);
3258
self.instruction(End);
3259
}
3260
3261
fn assert_not_flag(&mut self, flags_global: GlobalIndex, flag_to_test: i32, msg: &'static str) {
3262
self.instruction(GlobalGet(flags_global.as_u32()));
3263
self.instruction(I32Const(flag_to_test));
3264
self.instruction(I32And);
3265
self.instruction(If(BlockType::Empty));
3266
self.trap(Trap::AssertFailed(msg));
3267
self.instruction(End);
3268
}
3269
3270
fn set_flag(&mut self, flags_global: GlobalIndex, flag_to_set: i32, value: bool) {
3271
self.instruction(GlobalGet(flags_global.as_u32()));
3272
if value {
3273
self.instruction(I32Const(flag_to_set));
3274
self.instruction(I32Or);
3275
} else {
3276
self.instruction(I32Const(!flag_to_set));
3277
self.instruction(I32And);
3278
}
3279
self.instruction(GlobalSet(flags_global.as_u32()));
3280
}
3281
3282
fn verify_aligned(&mut self, opts: &LinearMemoryOptions, addr_local: u32, align: u32) {
3283
// If the alignment is 1 then everything is trivially aligned and the
3284
// check can be omitted.
3285
if align == 1 {
3286
return;
3287
}
3288
self.instruction(LocalGet(addr_local));
3289
assert!(align.is_power_of_two());
3290
self.ptr_uconst(opts, align - 1);
3291
self.ptr_and(opts);
3292
self.ptr_if(opts, BlockType::Empty);
3293
self.trap(Trap::UnalignedPointer);
3294
self.instruction(End);
3295
}
3296
3297
fn assert_aligned(&mut self, ty: &InterfaceType, mem: &Memory) {
3298
let mem_opts = mem.mem_opts();
3299
if !self.module.debug {
3300
return;
3301
}
3302
let align = self.types.align(mem_opts, ty);
3303
if align == 1 {
3304
return;
3305
}
3306
assert!(align.is_power_of_two());
3307
self.instruction(LocalGet(mem.addr.idx));
3308
self.ptr_uconst(mem_opts, mem.offset);
3309
self.ptr_add(mem_opts);
3310
self.ptr_uconst(mem_opts, align - 1);
3311
self.ptr_and(mem_opts);
3312
self.ptr_if(mem_opts, BlockType::Empty);
3313
self.trap(Trap::AssertFailed("pointer not aligned"));
3314
self.instruction(End);
3315
}
3316
3317
fn malloc<'c>(&mut self, opts: &'c Options, size: MallocSize, align: u32) -> Memory<'c> {
3318
match &opts.data_model {
3319
DataModel::Gc {} => todo!("CM+GC"),
3320
DataModel::LinearMemory(mem_opts) => {
3321
let realloc = mem_opts.realloc.unwrap();
3322
self.ptr_uconst(mem_opts, 0);
3323
self.ptr_uconst(mem_opts, 0);
3324
self.ptr_uconst(mem_opts, align);
3325
match size {
3326
MallocSize::Const(size) => self.ptr_uconst(mem_opts, size),
3327
MallocSize::Local(idx) => self.instruction(LocalGet(idx)),
3328
}
3329
self.instruction(Call(realloc.as_u32()));
3330
let addr = self.local_set_new_tmp(mem_opts.ptr());
3331
self.memory_operand(opts, addr, align)
3332
}
3333
}
3334
}
3335
3336
fn memory_operand<'c>(&mut self, opts: &'c Options, addr: TempLocal, align: u32) -> Memory<'c> {
3337
let ret = Memory {
3338
addr,
3339
offset: 0,
3340
opts,
3341
};
3342
self.verify_aligned(opts.data_model.unwrap_memory(), ret.addr.idx, align);
3343
ret
3344
}
3345
3346
/// Generates a new local in this function of the `ty` specified,
3347
/// initializing it with the top value on the current wasm stack.
3348
///
3349
/// The returned `TempLocal` must be freed after it is finished with
3350
/// `free_temp_local`.
3351
fn local_tee_new_tmp(&mut self, ty: ValType) -> TempLocal {
3352
self.gen_temp_local(ty, LocalTee)
3353
}
3354
3355
/// Same as `local_tee_new_tmp` but initializes the local with `LocalSet`
3356
/// instead of `LocalTee`.
3357
fn local_set_new_tmp(&mut self, ty: ValType) -> TempLocal {
3358
self.gen_temp_local(ty, LocalSet)
3359
}
3360
3361
fn local_get_tmp(&mut self, local: &TempLocal) {
3362
self.instruction(LocalGet(local.idx));
3363
}
3364
3365
fn gen_temp_local(&mut self, ty: ValType, insn: fn(u32) -> Instruction<'static>) -> TempLocal {
3366
// First check to see if any locals are available in this function which
3367
// were previously generated but are no longer in use.
3368
if let Some(idx) = self.free_locals.get_mut(&ty).and_then(|v| v.pop()) {
3369
self.instruction(insn(idx));
3370
return TempLocal {
3371
ty,
3372
idx,
3373
needs_free: true,
3374
};
3375
}
3376
3377
// Failing that generate a fresh new local.
3378
let locals = &mut self.module.funcs[self.result].locals;
3379
match locals.last_mut() {
3380
Some((cnt, prev_ty)) if ty == *prev_ty => *cnt += 1,
3381
_ => locals.push((1, ty)),
3382
}
3383
self.nlocals += 1;
3384
let idx = self.nlocals - 1;
3385
self.instruction(insn(idx));
3386
TempLocal {
3387
ty,
3388
idx,
3389
needs_free: true,
3390
}
3391
}
3392
3393
/// Used to release a `TempLocal` from a particular lexical scope to allow
3394
/// its possible reuse in later scopes.
3395
fn free_temp_local(&mut self, mut local: TempLocal) {
3396
assert!(local.needs_free);
3397
self.free_locals
3398
.entry(local.ty)
3399
.or_insert(Vec::new())
3400
.push(local.idx);
3401
local.needs_free = false;
3402
}
3403
3404
fn instruction(&mut self, instr: Instruction) {
3405
instr.encode(&mut self.code);
3406
}
3407
3408
fn trap(&mut self, trap: Trap) {
3409
self.traps.push((self.code.len(), trap));
3410
self.instruction(Unreachable);
3411
}
3412
3413
/// Flushes out the current `code` instructions (and `traps` if there are
3414
/// any) into the destination function.
3415
///
3416
/// This is a noop if no instructions have been encoded yet.
3417
fn flush_code(&mut self) {
3418
if self.code.is_empty() {
3419
return;
3420
}
3421
self.module.funcs[self.result].body.push(Body::Raw(
3422
mem::take(&mut self.code),
3423
mem::take(&mut self.traps),
3424
));
3425
}
3426
3427
fn finish(mut self) {
3428
// Append the final `end` instruction which all functions require, and
3429
// then empty out the temporary buffer in `Compiler`.
3430
self.instruction(End);
3431
self.flush_code();
3432
3433
// Flag the function as "done" which helps with an assert later on in
3434
// emission that everything was eventually finished.
3435
self.module.funcs[self.result].filled_in = true;
3436
}
3437
3438
/// Fetches the value contained with the local specified by `stack` and
3439
/// converts it to `dst_ty`.
3440
///
3441
/// This is only intended for use in primitive operations where `stack` is
3442
/// guaranteed to have only one local. The type of the local on the stack is
3443
/// then converted to `dst_ty` appropriately. Note that the types may be
3444
/// different due to the "flattening" of variant types.
3445
fn stack_get(&mut self, stack: &Stack<'_>, dst_ty: ValType) {
3446
assert_eq!(stack.locals.len(), 1);
3447
let (idx, src_ty) = stack.locals[0];
3448
self.instruction(LocalGet(idx));
3449
match (src_ty, dst_ty) {
3450
(ValType::I32, ValType::I32)
3451
| (ValType::I64, ValType::I64)
3452
| (ValType::F32, ValType::F32)
3453
| (ValType::F64, ValType::F64) => {}
3454
3455
(ValType::I32, ValType::F32) => self.instruction(F32ReinterpretI32),
3456
(ValType::I64, ValType::I32) => {
3457
self.assert_i64_upper_bits_not_set(idx);
3458
self.instruction(I32WrapI64);
3459
}
3460
(ValType::I64, ValType::F64) => self.instruction(F64ReinterpretI64),
3461
(ValType::I64, ValType::F32) => {
3462
self.assert_i64_upper_bits_not_set(idx);
3463
self.instruction(I32WrapI64);
3464
self.instruction(F32ReinterpretI32);
3465
}
3466
3467
// should not be possible given the `join` function for variants
3468
(ValType::I32, ValType::I64)
3469
| (ValType::I32, ValType::F64)
3470
| (ValType::F32, ValType::I32)
3471
| (ValType::F32, ValType::I64)
3472
| (ValType::F32, ValType::F64)
3473
| (ValType::F64, ValType::I32)
3474
| (ValType::F64, ValType::I64)
3475
| (ValType::F64, ValType::F32)
3476
3477
// not used in the component model
3478
| (ValType::Ref(_), _)
3479
| (_, ValType::Ref(_))
3480
| (ValType::V128, _)
3481
| (_, ValType::V128) => {
3482
panic!("cannot get {dst_ty:?} from {src_ty:?} local");
3483
}
3484
}
3485
}
3486
3487
fn assert_i64_upper_bits_not_set(&mut self, local: u32) {
3488
if !self.module.debug {
3489
return;
3490
}
3491
self.instruction(LocalGet(local));
3492
self.instruction(I64Const(32));
3493
self.instruction(I64ShrU);
3494
self.instruction(I32WrapI64);
3495
self.instruction(If(BlockType::Empty));
3496
self.trap(Trap::AssertFailed("upper bits are unexpectedly set"));
3497
self.instruction(End);
3498
}
3499
3500
/// Converts the top value on the WebAssembly stack which has type
3501
/// `src_ty` to `dst_tys[0]`.
3502
///
3503
/// This is only intended for conversion of primitives where the `dst_tys`
3504
/// list is known to be of length 1.
3505
fn stack_set(&mut self, dst_tys: &[ValType], src_ty: ValType) {
3506
assert_eq!(dst_tys.len(), 1);
3507
let dst_ty = dst_tys[0];
3508
match (src_ty, dst_ty) {
3509
(ValType::I32, ValType::I32)
3510
| (ValType::I64, ValType::I64)
3511
| (ValType::F32, ValType::F32)
3512
| (ValType::F64, ValType::F64) => {}
3513
3514
(ValType::F32, ValType::I32) => self.instruction(I32ReinterpretF32),
3515
(ValType::I32, ValType::I64) => self.instruction(I64ExtendI32U),
3516
(ValType::F64, ValType::I64) => self.instruction(I64ReinterpretF64),
3517
(ValType::F32, ValType::I64) => {
3518
self.instruction(I32ReinterpretF32);
3519
self.instruction(I64ExtendI32U);
3520
}
3521
3522
// should not be possible given the `join` function for variants
3523
(ValType::I64, ValType::I32)
3524
| (ValType::F64, ValType::I32)
3525
| (ValType::I32, ValType::F32)
3526
| (ValType::I64, ValType::F32)
3527
| (ValType::F64, ValType::F32)
3528
| (ValType::I32, ValType::F64)
3529
| (ValType::I64, ValType::F64)
3530
| (ValType::F32, ValType::F64)
3531
3532
// not used in the component model
3533
| (ValType::Ref(_), _)
3534
| (_, ValType::Ref(_))
3535
| (ValType::V128, _)
3536
| (_, ValType::V128) => {
3537
panic!("cannot get {dst_ty:?} from {src_ty:?} local");
3538
}
3539
}
3540
}
3541
3542
fn i32_load8u(&mut self, mem: &Memory) {
3543
self.instruction(LocalGet(mem.addr.idx));
3544
self.instruction(I32Load8U(mem.memarg(0)));
3545
}
3546
3547
fn i32_load8s(&mut self, mem: &Memory) {
3548
self.instruction(LocalGet(mem.addr.idx));
3549
self.instruction(I32Load8S(mem.memarg(0)));
3550
}
3551
3552
fn i32_load16u(&mut self, mem: &Memory) {
3553
self.instruction(LocalGet(mem.addr.idx));
3554
self.instruction(I32Load16U(mem.memarg(1)));
3555
}
3556
3557
fn i32_load16s(&mut self, mem: &Memory) {
3558
self.instruction(LocalGet(mem.addr.idx));
3559
self.instruction(I32Load16S(mem.memarg(1)));
3560
}
3561
3562
fn i32_load(&mut self, mem: &Memory) {
3563
self.instruction(LocalGet(mem.addr.idx));
3564
self.instruction(I32Load(mem.memarg(2)));
3565
}
3566
3567
fn i64_load(&mut self, mem: &Memory) {
3568
self.instruction(LocalGet(mem.addr.idx));
3569
self.instruction(I64Load(mem.memarg(3)));
3570
}
3571
3572
fn ptr_load(&mut self, mem: &Memory) {
3573
if mem.mem_opts().memory64 {
3574
self.i64_load(mem);
3575
} else {
3576
self.i32_load(mem);
3577
}
3578
}
3579
3580
fn ptr_add(&mut self, opts: &LinearMemoryOptions) {
3581
if opts.memory64 {
3582
self.instruction(I64Add);
3583
} else {
3584
self.instruction(I32Add);
3585
}
3586
}
3587
3588
fn ptr_sub(&mut self, opts: &LinearMemoryOptions) {
3589
if opts.memory64 {
3590
self.instruction(I64Sub);
3591
} else {
3592
self.instruction(I32Sub);
3593
}
3594
}
3595
3596
fn ptr_mul(&mut self, opts: &LinearMemoryOptions) {
3597
if opts.memory64 {
3598
self.instruction(I64Mul);
3599
} else {
3600
self.instruction(I32Mul);
3601
}
3602
}
3603
3604
fn ptr_ge_u(&mut self, opts: &LinearMemoryOptions) {
3605
if opts.memory64 {
3606
self.instruction(I64GeU);
3607
} else {
3608
self.instruction(I32GeU);
3609
}
3610
}
3611
3612
fn ptr_lt_u(&mut self, opts: &LinearMemoryOptions) {
3613
if opts.memory64 {
3614
self.instruction(I64LtU);
3615
} else {
3616
self.instruction(I32LtU);
3617
}
3618
}
3619
3620
fn ptr_shl(&mut self, opts: &LinearMemoryOptions) {
3621
if opts.memory64 {
3622
self.instruction(I64Shl);
3623
} else {
3624
self.instruction(I32Shl);
3625
}
3626
}
3627
3628
fn ptr_eqz(&mut self, opts: &LinearMemoryOptions) {
3629
if opts.memory64 {
3630
self.instruction(I64Eqz);
3631
} else {
3632
self.instruction(I32Eqz);
3633
}
3634
}
3635
3636
fn ptr_uconst(&mut self, opts: &LinearMemoryOptions, val: u32) {
3637
if opts.memory64 {
3638
self.instruction(I64Const(val.into()));
3639
} else {
3640
self.instruction(I32Const(val as i32));
3641
}
3642
}
3643
3644
fn ptr_iconst(&mut self, opts: &LinearMemoryOptions, val: i32) {
3645
if opts.memory64 {
3646
self.instruction(I64Const(val.into()));
3647
} else {
3648
self.instruction(I32Const(val));
3649
}
3650
}
3651
3652
fn ptr_eq(&mut self, opts: &LinearMemoryOptions) {
3653
if opts.memory64 {
3654
self.instruction(I64Eq);
3655
} else {
3656
self.instruction(I32Eq);
3657
}
3658
}
3659
3660
fn ptr_ne(&mut self, opts: &LinearMemoryOptions) {
3661
if opts.memory64 {
3662
self.instruction(I64Ne);
3663
} else {
3664
self.instruction(I32Ne);
3665
}
3666
}
3667
3668
fn ptr_and(&mut self, opts: &LinearMemoryOptions) {
3669
if opts.memory64 {
3670
self.instruction(I64And);
3671
} else {
3672
self.instruction(I32And);
3673
}
3674
}
3675
3676
fn ptr_or(&mut self, opts: &LinearMemoryOptions) {
3677
if opts.memory64 {
3678
self.instruction(I64Or);
3679
} else {
3680
self.instruction(I32Or);
3681
}
3682
}
3683
3684
fn ptr_xor(&mut self, opts: &LinearMemoryOptions) {
3685
if opts.memory64 {
3686
self.instruction(I64Xor);
3687
} else {
3688
self.instruction(I32Xor);
3689
}
3690
}
3691
3692
fn ptr_if(&mut self, opts: &LinearMemoryOptions, ty: BlockType) {
3693
if opts.memory64 {
3694
self.instruction(I64Const(0));
3695
self.instruction(I64Ne);
3696
}
3697
self.instruction(If(ty));
3698
}
3699
3700
fn ptr_br_if(&mut self, opts: &LinearMemoryOptions, depth: u32) {
3701
if opts.memory64 {
3702
self.instruction(I64Const(0));
3703
self.instruction(I64Ne);
3704
}
3705
self.instruction(BrIf(depth));
3706
}
3707
3708
fn f32_load(&mut self, mem: &Memory) {
3709
self.instruction(LocalGet(mem.addr.idx));
3710
self.instruction(F32Load(mem.memarg(2)));
3711
}
3712
3713
fn f64_load(&mut self, mem: &Memory) {
3714
self.instruction(LocalGet(mem.addr.idx));
3715
self.instruction(F64Load(mem.memarg(3)));
3716
}
3717
3718
fn push_dst_addr(&mut self, dst: &Destination) {
3719
if let Destination::Memory(mem) = dst {
3720
self.instruction(LocalGet(mem.addr.idx));
3721
}
3722
}
3723
3724
fn i32_store8(&mut self, mem: &Memory) {
3725
self.instruction(I32Store8(mem.memarg(0)));
3726
}
3727
3728
fn i32_store16(&mut self, mem: &Memory) {
3729
self.instruction(I32Store16(mem.memarg(1)));
3730
}
3731
3732
fn i32_store(&mut self, mem: &Memory) {
3733
self.instruction(I32Store(mem.memarg(2)));
3734
}
3735
3736
fn i64_store(&mut self, mem: &Memory) {
3737
self.instruction(I64Store(mem.memarg(3)));
3738
}
3739
3740
fn ptr_store(&mut self, mem: &Memory) {
3741
if mem.mem_opts().memory64 {
3742
self.i64_store(mem);
3743
} else {
3744
self.i32_store(mem);
3745
}
3746
}
3747
3748
fn f32_store(&mut self, mem: &Memory) {
3749
self.instruction(F32Store(mem.memarg(2)));
3750
}
3751
3752
fn f64_store(&mut self, mem: &Memory) {
3753
self.instruction(F64Store(mem.memarg(3)));
3754
}
3755
}
3756
3757
impl<'a> Source<'a> {
3758
/// Given this `Source` returns an iterator over the `Source` for each of
3759
/// the component `fields` specified.
3760
///
3761
/// This will automatically slice stack-based locals to the appropriate
3762
/// width for each component type and additionally calculate the appropriate
3763
/// offset for each memory-based type.
3764
fn record_field_srcs<'b>(
3765
&'b self,
3766
types: &'b ComponentTypesBuilder,
3767
fields: impl IntoIterator<Item = InterfaceType> + 'b,
3768
) -> impl Iterator<Item = Source<'a>> + 'b
3769
where
3770
'a: 'b,
3771
{
3772
let mut offset = 0;
3773
fields.into_iter().map(move |ty| match self {
3774
Source::Memory(mem) => {
3775
let mem = next_field_offset(&mut offset, types, &ty, mem);
3776
Source::Memory(mem)
3777
}
3778
Source::Stack(stack) => {
3779
let cnt = types.flat_types(&ty).unwrap().len() as u32;
3780
offset += cnt;
3781
Source::Stack(stack.slice((offset - cnt) as usize..offset as usize))
3782
}
3783
Source::Struct(_) => todo!(),
3784
Source::Array(_) => todo!(),
3785
})
3786
}
3787
3788
/// Returns the corresponding discriminant source and payload source f
3789
fn payload_src(
3790
&self,
3791
types: &ComponentTypesBuilder,
3792
info: &VariantInfo,
3793
case: Option<&InterfaceType>,
3794
) -> Source<'a> {
3795
match self {
3796
Source::Stack(s) => {
3797
let flat_len = match case {
3798
Some(case) => types.flat_types(case).unwrap().len(),
3799
None => 0,
3800
};
3801
Source::Stack(s.slice(1..s.locals.len()).slice(0..flat_len))
3802
}
3803
Source::Memory(mem) => {
3804
let mem = if mem.mem_opts().memory64 {
3805
mem.bump(info.payload_offset64)
3806
} else {
3807
mem.bump(info.payload_offset32)
3808
};
3809
Source::Memory(mem)
3810
}
3811
Source::Struct(_) | Source::Array(_) => todo!("CM+GC"),
3812
}
3813
}
3814
3815
fn opts(&self) -> &'a Options {
3816
match self {
3817
Source::Stack(s) => s.opts,
3818
Source::Memory(mem) => mem.opts,
3819
Source::Struct(s) => s.opts,
3820
Source::Array(a) => a.opts,
3821
}
3822
}
3823
}
3824
3825
impl<'a> Destination<'a> {
3826
/// Same as `Source::record_field_srcs` but for destinations.
3827
fn record_field_dsts<'b, I>(
3828
&'b self,
3829
types: &'b ComponentTypesBuilder,
3830
fields: I,
3831
) -> impl Iterator<Item = Destination<'b>> + use<'b, I>
3832
where
3833
'a: 'b,
3834
I: IntoIterator<Item = InterfaceType> + 'b,
3835
{
3836
let mut offset = 0;
3837
fields.into_iter().map(move |ty| match self {
3838
Destination::Memory(mem) => {
3839
let mem = next_field_offset(&mut offset, types, &ty, mem);
3840
Destination::Memory(mem)
3841
}
3842
Destination::Stack(s, opts) => {
3843
let cnt = types.flat_types(&ty).unwrap().len() as u32;
3844
offset += cnt;
3845
Destination::Stack(&s[(offset - cnt) as usize..offset as usize], opts)
3846
}
3847
Destination::Struct(_) => todo!(),
3848
Destination::Array(_) => todo!(),
3849
})
3850
}
3851
3852
/// Returns the corresponding discriminant source and payload source f
3853
fn payload_dst(
3854
&self,
3855
types: &ComponentTypesBuilder,
3856
info: &VariantInfo,
3857
case: Option<&InterfaceType>,
3858
) -> Destination<'_> {
3859
match self {
3860
Destination::Stack(s, opts) => {
3861
let flat_len = match case {
3862
Some(case) => types.flat_types(case).unwrap().len(),
3863
None => 0,
3864
};
3865
Destination::Stack(&s[1..][..flat_len], opts)
3866
}
3867
Destination::Memory(mem) => {
3868
let mem = if mem.mem_opts().memory64 {
3869
mem.bump(info.payload_offset64)
3870
} else {
3871
mem.bump(info.payload_offset32)
3872
};
3873
Destination::Memory(mem)
3874
}
3875
Destination::Struct(_) | Destination::Array(_) => todo!("CM+GC"),
3876
}
3877
}
3878
3879
fn opts(&self) -> &'a Options {
3880
match self {
3881
Destination::Stack(_, opts) => opts,
3882
Destination::Memory(mem) => mem.opts,
3883
Destination::Struct(s) => s.opts,
3884
Destination::Array(a) => a.opts,
3885
}
3886
}
3887
}
3888
3889
fn next_field_offset<'a>(
3890
offset: &mut u32,
3891
types: &ComponentTypesBuilder,
3892
field: &InterfaceType,
3893
mem: &Memory<'a>,
3894
) -> Memory<'a> {
3895
let abi = types.canonical_abi(field);
3896
let offset = if mem.mem_opts().memory64 {
3897
abi.next_field64(offset)
3898
} else {
3899
abi.next_field32(offset)
3900
};
3901
mem.bump(offset)
3902
}
3903
3904
impl<'a> Memory<'a> {
3905
fn memarg(&self, align: u32) -> MemArg {
3906
MemArg {
3907
offset: u64::from(self.offset),
3908
align,
3909
memory_index: self.mem_opts().memory.unwrap().as_u32(),
3910
}
3911
}
3912
3913
fn bump(&self, offset: u32) -> Memory<'a> {
3914
Memory {
3915
opts: self.opts,
3916
addr: TempLocal::new(self.addr.idx, self.addr.ty),
3917
offset: self.offset + offset,
3918
}
3919
}
3920
}
3921
3922
impl<'a> Stack<'a> {
3923
fn slice(&self, range: Range<usize>) -> Stack<'a> {
3924
Stack {
3925
locals: &self.locals[range],
3926
opts: self.opts,
3927
}
3928
}
3929
}
3930
3931
struct VariantCase<'a> {
3932
src_i: u32,
3933
src_ty: Option<&'a InterfaceType>,
3934
dst_i: u32,
3935
dst_ty: Option<&'a InterfaceType>,
3936
}
3937
3938
fn variant_info<'a, I>(types: &ComponentTypesBuilder, cases: I) -> VariantInfo
3939
where
3940
I: IntoIterator<Item = Option<&'a InterfaceType>>,
3941
I::IntoIter: ExactSizeIterator,
3942
{
3943
VariantInfo::new(
3944
cases
3945
.into_iter()
3946
.map(|ty| ty.map(|ty| types.canonical_abi(ty))),
3947
)
3948
.0
3949
}
3950
3951
enum MallocSize {
3952
Const(u32),
3953
Local(u32),
3954
}
3955
3956
struct WasmString<'a> {
3957
ptr: TempLocal,
3958
len: TempLocal,
3959
opts: &'a Options,
3960
}
3961
3962
struct TempLocal {
3963
idx: u32,
3964
ty: ValType,
3965
needs_free: bool,
3966
}
3967
3968
impl TempLocal {
3969
fn new(idx: u32, ty: ValType) -> TempLocal {
3970
TempLocal {
3971
idx,
3972
ty,
3973
needs_free: false,
3974
}
3975
}
3976
}
3977
3978
impl std::ops::Drop for TempLocal {
3979
fn drop(&mut self) {
3980
if self.needs_free {
3981
panic!("temporary local not free'd");
3982
}
3983
}
3984
}
3985
3986
impl From<FlatType> for ValType {
3987
fn from(ty: FlatType) -> ValType {
3988
match ty {
3989
FlatType::I32 => ValType::I32,
3990
FlatType::I64 => ValType::I64,
3991
FlatType::F32 => ValType::F32,
3992
FlatType::F64 => ValType::F64,
3993
}
3994
}
3995
}
3996
3997