/// An expression, or a list of instructions, in the WebAssembly text format. /// /// This expression type will parse s-expression-folded instructions into a flat /// list of instructions for emission later on. The implicit `end` instruction /// at the end of an expression is not included in the `instrs` field. #[derive(Debug)] #[allow(missing_docs)] pubstruct Expression<'a> { /// Instructions in this expression. pub instrs: Box<[Instruction<'a>]>,
/// Branch hints, if any, found while parsing instructions. pub branch_hints: Box<[BranchHint]>,
/// Optionally parsed spans of all instructions in `instrs`. /// /// This value is `None` as it's disabled by default. This can be enabled /// through the /// [`ParseBuffer::track_instr_spans`](crate::parser::ParseBuffer::track_instr_spans) /// function. /// /// This is not tracked by default due to the memory overhead and limited /// use of this field. pub instr_spans: Option<Box<[Span]>>,
}
/// A `@metadata.code.branch_hint` in the code, associated with a If or BrIf /// This instruction is a placeholder and won't produce anything. Its purpose /// is to store the offset of the following instruction and check that /// it's followed by `br_if` or `if`. #[derive(Debug)] pubstruct BranchHint { /// Index of instructions in `instrs` field of `Expression` that this hint /// applies to. pub instr_index: usize, /// The value of this branch hint pub value: u32,
}
impl<'a> Expression<'a> { /// Creates an expression from the single `instr` specified. pubfn one(instr: Instruction<'a>) -> Expression<'a> {
Expression {
instrs: [instr].into(),
branch_hints: Box::new([]),
instr_spans: None,
}
}
/// Parse an expression formed from a single folded instruction. /// /// Attempts to parse an expression formed from a single folded instruction. /// /// This method will mutate the state of `parser` after attempting to parse /// the expression. If an error happens then it is likely fatal and /// there is no guarantee of how many tokens have been consumed from /// `parser`. /// /// # Errors /// /// This function will return an error if the expression could not be /// parsed. Note that creating an [`crate::Error`] is not exactly a cheap /// operation, so [`crate::Error`] is typically fatal and propagated all the /// way back to the top parse call site. pubfn parse_folded_instruction(parser: Parser<'a>) -> Result<Self> { letmut exprs = ExpressionParser::new(parser);
exprs.parse_folded_instruction(parser)?;
Ok(Expression {
instrs: exprs.raw_instrs.into(),
branch_hints: exprs.branch_hints.into(),
instr_spans: exprs.spans.map(|s| s.into()),
})
}
}
/// Helper struct used to parse an `Expression` with helper methods and such. /// /// The primary purpose of this is to avoid defining expression parsing as a /// call-thread-stack recursive function. Since we're parsing user input that /// runs the risk of blowing the call stack, so we want to be sure to use a heap /// stack structure wherever possible. struct ExpressionParser<'a> { /// The flat list of instructions that we've parsed so far, and will /// eventually become the final `Expression`. /// /// Appended to with `push_instr` to ensure that this is the same length of /// `spans` if `spans` is used.
raw_instrs: Vec<Instruction<'a>>,
/// Descriptor of all our nested s-expr blocks. This only happens when /// instructions themselves are nested.
stack: Vec<Level<'a>>,
/// Related to the branch hints proposal. /// Will be used later to collect the offsets in the final binary. /// <(index of branch instructions, BranchHintAnnotation)>
branch_hints: Vec<BranchHint>,
/// Storage for all span information in `raw_instrs`. Optionally disabled to /// reduce memory consumption of parsing expressions.
spans: Option<Vec<Span>>,
}
enum Paren {
None,
Left,
Right(Span),
}
/// A "kind" of nested block that we can be parsing inside of. enum Level<'a> { /// This is a normal `block` or `loop` or similar, where the instruction /// payload here is pushed when the block is exited.
EndWith(Instruction<'a>, Option<Span>),
/// This is a pretty special variant which means that we're parsing an `if` /// statement, and the state of the `if` parsing is tracked internally in /// the payload. If(If<'a>),
/// This means we're either parsing inside of `(then ...)` or `(else ...)` /// which don't correspond to terminating instructions, we're just in a /// nested block.
IfArm,
/// This means we are finishing the parsing of a branch hint annotation.
BranchHint,
}
/// Possible states of "what is currently being parsed?" in an `if` expression. enumIf<'a> { /// Only the `if` instruction has been parsed, next thing to parse is the /// clause, if any, of the `if` instruction. /// /// This parse ends when `(then ...)` is encountered.
Clause(Instruction<'a>, Span), /// Currently parsing the `then` block, and afterwards a closing paren is /// required or an `(else ...)` expression.
Then, /// Parsing the `else` expression, nothing can come after. Else,
}
fn parse(&mutself, parser: Parser<'a>) -> Result<()> { // Here we parse instructions in a loop, and we do not recursively // invoke this parse function to avoid blowing the stack on // deeply-recursive parses. // // Our loop generally only finishes once there's no more input left int // the `parser`. If there's some unclosed delimiters though (on our // `stack`), then we also keep parsing to generate error messages if // there's no input left. while !parser.is_empty() || !self.stack.is_empty() { // As a small ease-of-life adjustment here, if we're parsing inside // of an `if block then we require that all sub-components are // s-expressions surrounded by `(` and `)`, so verify that here. iflet Some(Level::If(_)) = self.stack.last() { if !parser.is_empty() && !parser.peek::<LParen>()? { return Err(parser.error("expected `(`"));
}
}
matchself.paren(parser)? { // No parenthesis seen? Then we just parse the next instruction // and move on.
Paren::None => { let span = parser.cur_span(); self.push_instr(parser.parse()?, span);
}
// If we see a left-parenthesis then things are a little // special. We handle block-like instructions specially // (`block`, `loop`, and `if`), and otherwise all other // instructions simply get appended once we reach the end of the // s-expression. // // In all cases here we push something onto the `stack` to get // popped when the `)` character is seen.
Paren::Left => { // First up is handling `if` parsing, which is funky in a // whole bunch of ways. See the method internally for more // information. ifself.handle_if_lparen(parser)? { continue;
}
// Handle the case of a branch hint annotation if parser.peek::<annotation::metadata_code_branch_hint>()? { self.parse_branch_hint(parser)?; self.stack.push(Level::BranchHint); continue;
}
let span = parser.cur_span(); match parser.parse()? { // If block/loop show up then we just need to be sure to // push an `end` instruction whenever the `)` token is // seen
i @ Instruction::Block(_)
| i @ Instruction::Loop(_)
| i @ Instruction::TryTable(_) => { self.push_instr(i, span); self.stack
.push(Level::EndWith(Instruction::End(None), None));
}
// Parsing an `if` instruction is super tricky, so we // push an `If` scope and we let all our scope-based // parsing handle the remaining items.
i @ Instruction::If(_) => { self.stack.push(Level::If(If::Clause(i, span)));
}
// Anything else means that we're parsing a nested form // such as `(i32.add ...)` which means that the // instruction we parsed will be coming at the end.
other => self.stack.push(Level::EndWith(other, Some(span))),
}
}
// If we registered a `)` token as being seen, then we're // guaranteed there's an item in the `stack` stack for us to // pop. We peel that off and take a look at what it says to do.
Paren::Right(span) => matchself.stack.pop().unwrap() {
Level::EndWith(i, s) => self.push_instr(i, s.unwrap_or(span)),
Level::IfArm => {}
Level::BranchHint => {}
// If an `if` statement hasn't parsed the clause or `then` // block, then that's an error because there weren't enough // items in the `if` statement. Otherwise we're just careful // to terminate with an `end` instruction.
Level::If(If::Clause(..)) => { return Err(parser.error("previous `if` had no `then`"));
}
Level::If(_) => { self.push_instr(Instruction::End(None), span);
}
},
}
}
Ok(())
}
/// State transitions with parsing an `if` statement. /// /// The syntactical form of an `if` statement looks like: /// /// ```wat /// (if ($clause)... (then $then) (else $else)) /// ``` /// /// THis method is called after a `(` is parsed within the `(if ...` block. /// This determines what to do next. /// /// Returns `true` if the rest of the arm above should be skipped, or /// `false` if we should parse the next item as an instruction (because we /// didn't handle the lparen here). fn handle_if_lparen(&mutself, parser: Parser<'a>) -> Result<bool> { // Only execute the code below if there's an `If` listed last. let i = matchself.stack.last_mut() {
Some(Level::If(i)) => i,
_ => return Ok(false),
};
match i { // If the clause is still being parsed then interpret this `(` as a // folded instruction unless it starts with `then`, in which case // this transitions to the `Then` state and a new level has been // reached. If::Clause(if_instr, if_instr_span) => { if !parser.peek::<kw::then>()? { return Ok(false);
}
parser.parse::<kw::then>()?; let instr = mem::replace(if_instr, Instruction::End(None)); let span = *if_instr_span;
*i = If::Then; self.push_instr(instr, span); self.stack.push(Level::IfArm);
Ok(true)
}
// Previously we were parsing the `(then ...)` clause so this next // `(` must be followed by `else`. If::Then => { let span = parser.parse::<kw::r#else>()?.0;
*i = If::Else; self.push_instr(Instruction::Else(None), span); self.stack.push(Level::IfArm);
Ok(true)
}
// If after a `(else ...` clause is parsed there's another `(` then // that's not syntactically allowed. If::Else => Err(parser.error("unexpected token: too many payloads inside of `(if)`")),
}
}
impl Encode for Instruction<'_> { #[allow(non_snake_case)] fn encode(&self, v: &mut Vec<u8>) { matchself {
$(
Instruction::$name $((instructions!(@first x $($arg)*)))? => { fn encode<'a>($(arg: &instructions!(@ty $($arg)*),)? v: &mut Vec<u8>) {
instructions!(@encode v $($binary)*);
$(<instructions!(@ty $($arg)*) as Encode>::encode(arg, v);)?
}
encode($( instructions!(@first x $($arg)*), )? v)
}
)*
}
}
}
impl<'a> Instruction<'a> { /// Returns the associated [`MemArg`] if one is available for this /// instruction. #[allow(unused_variables, non_snake_case)] pubfn memarg_mut(&mutself) -> Option<&mutMemArg<'a>> { matchself {
$(
Instruction::$name $((instructions!(@memarg_binding a $($arg)*)))? => {
instructions!(@get_memarg a $($($arg)*)?)
}
)*
}
}
}
);
// simd opcodes prefixed with `0xfd` get a varuint32 encoding for their payload
(@encode $dst:ident 0xfd, $simd:tt) => ({
$dst.push(0xfd);
<u32 as Encode>::encode(&$simd, $dst);
});
(@encode $dst:ident $($bytes:tt)*) => ($dst.extend_from_slice(&[$($bytes)*]););
// As shown in #1095 the size of this variant is somewhat performance-sensitive // since big `*.wat` files will have a lot of these. This is a small ratchet to // make sure that this enum doesn't become larger than it already is, although // ideally it also wouldn't be as large as it is now. const _: () = { let size = std::mem::size_of::<Instruction<'_>>(); let pointer = std::mem::size_of::<u64>();
assert!(size <= pointer * 11);
};
/// Extra information associated with block-related instructions. /// /// This is used to label blocks and also annotate what types are expected for /// the block. #[derive(Debug, Clone)] #[allow(missing_docs)] pubstruct BlockType<'a> { pub label: Option<Id<'a>>, pub label_name: Option<NameAnnotation<'a>>, pub ty: TypeUse<'a, FunctionType<'a>>,
}
#[derive(Debug, Clone)] #[allow(missing_docs)] pubenum TryTableCatchKind<'a> { // Catch a tagged exception, do not capture an exnref.
Catch(Index<'a>), // Catch a tagged exception, and capture the exnref.
CatchRef(Index<'a>), // Catch any exception, do not capture an exnref.
CatchAll, // Catch any exception, and capture the exnref.
CatchAllRef,
}
/// Payload for lane-related instructions. Unsigned with no + prefix. #[derive(Debug, Clone)] pubstruct LaneArg { /// The lane argument. pub lane: u8,
}
impl<'a> Parse<'a> for LaneArg { fn parse(parser: Parser<'a>) -> Result<Self> { let lane = parser.step(|c| { iflet Some((i, rest)) = c.integer()? { if i.sign() == None { let (src, radix) = i.val(); let val = u8::from_str_radix(src, radix)
.map_err(|_| c.error("malformed lane index"))?;
Ok((val, rest))
} else {
Err(c.error("unexpected token"))
}
} else {
Err(c.error("expected a lane index"))
}
})?;
Ok(LaneArg { lane })
}
}
/// Payload for memory-related instructions indicating offset/alignment of /// memory accesses. #[derive(Debug, Clone)] pubstruct MemArg<'a> { /// The alignment of this access. /// /// This is not stored as a log, this is the actual alignment (e.g. 1, 2, 4, /// 8, etc). pub align: u32, /// The offset, in bytes of this access. pub offset: u64, /// The memory index we're accessing pub memory: Index<'a>,
}
impl<'a> MemArg<'a> { fn parse(parser: Parser<'a>, default_align: u32) -> Result<Self> { fn parse_field<T>(
name: &str,
parser: Parser<'_>,
f: impl FnOnce(Cursor<'_>, &str, u32) -> Result<T>,
) -> Result<Option<T>> {
parser.step(|c| { let (kw, rest) = match c.keyword()? {
Some(p) => p,
None => return Ok((None, c)),
}; if !kw.starts_with(name) { return Ok((None, c));
} let kw = &kw[name.len()..]; if !kw.starts_with('=') { return Ok((None, c));
} let num = &kw[1..]; let num = iflet Some(stripped) = num.strip_prefix("0x") {
f(c, stripped, 16)?
} else {
f(c, num, 10)?
};
let memory = parser
.parse::<Option<_>>()?
.unwrap_or_else(|| Index::Num(0, parser.prev_span())); let offset = parse_u64("offset", parser)?.unwrap_or(0); let align = match parse_u32("align", parser)? {
Some(n) if !n.is_power_of_two() => { return Err(parser.error("alignment must be a power of two"))
}
n => n.unwrap_or(default_align),
};
Ok(MemArg {
offset,
align,
memory,
})
}
}
/// Extra data associated with the `loadN_lane` and `storeN_lane` instructions. #[derive(Debug, Clone)] pubstruct LoadOrStoreLane<'a> { /// The memory argument for this instruction. pub memarg: MemArg<'a>, /// The lane argument for this instruction. pub lane: LaneArg,
}
impl<'a> LoadOrStoreLane<'a> { fn parse(parser: Parser<'a>, default_align: u32) -> Result<Self> { // This is sort of funky. The first integer we see could be the lane // index, but it could also be the memory index. To determine what it is // then if we see a second integer we need to look further. let has_memarg = parser.step(|c| match c.integer()? {
Some((_, after_int)) => { // Two integers in a row? That means that the first one is the // memory index and the second must be the lane index. if after_int.integer()?.is_some() { return Ok((true, c));
}
// If the first integer is trailed by `offset=...` or // `align=...` then this is definitely a memarg. iflet Some((kw, _)) = after_int.keyword()? { if kw.starts_with("offset=") || kw.starts_with("align=") { return Ok((true, c));
}
}
// Otherwise the first integer was trailed by something that // didn't look like a memarg, so this must be the lane index.
Ok((false, c))
}
// Not an integer here? That must mean that this must be the memarg // first followed by the trailing index.
None => Ok((true, c)),
})?;
Ok(LoadOrStoreLane {
memarg: if has_memarg {
MemArg::parse(parser, default_align)?
} else {
MemArg {
align: default_align,
offset: 0,
memory: Index::Num(0, parser.prev_span()),
}
},
lane: LaneArg::parse(parser)?,
})
}
}
/// Extra data associated with the `call_indirect` instruction. #[derive(Debug, Clone)] pubstruct CallIndirect<'a> { /// The table that this call is going to be indexing. pub table: Index<'a>, /// Type type signature that this `call_indirect` instruction is using. pub ty: TypeUse<'a, FunctionType<'a>>,
}
impl<'a> Parse<'a> for CallIndirect<'a> { fn parse(parser: Parser<'a>) -> Result<Self> { let prev_span = parser.prev_span(); let table: Option<_> = parser.parse()?; let ty = parser.parse::<TypeUse<'a, FunctionTypeNoNames<'a>>>()?;
Ok(CallIndirect {
table: table.unwrap_or(Index::Num(0, prev_span)),
ty: ty.into(),
})
}
}
/// Extra data associated with the `table.init` instruction #[derive(Debug, Clone)] pubstruct TableInit<'a> { /// The index of the table we're copying into. pub table: Index<'a>, /// The index of the element segment we're copying into a table. pub elem: Index<'a>,
}
impl<'a> Parse<'a> for TableInit<'a> { fn parse(parser: Parser<'a>) -> Result<Self> { let prev_span = parser.prev_span(); let (elem, table) = if parser.peek2::<Index>()? { let table = parser.parse()?;
(parser.parse()?, table)
} else {
(parser.parse()?, Index::Num(0, prev_span))
};
Ok(TableInit { table, elem })
}
}
/// Extra data associated with the `table.copy` instruction. #[derive(Debug, Clone)] pubstruct TableCopy<'a> { /// The index of the destination table to copy into. pub dst: Index<'a>, /// The index of the source table to copy from. pub src: Index<'a>,
}
/// Extra data associated with unary table instructions. #[derive(Debug, Clone)] pubstruct TableArg<'a> { /// The index of the table argument. pub dst: Index<'a>,
}
// `TableArg` could be an unwrapped as an `Index` if not for this custom parse // behavior: if we cannot parse a table index, we default to table `0`. impl<'a> Parse<'a> for TableArg<'a> { fn parse(parser: Parser<'a>) -> Result<Self> { let dst = iflet Some(dst) = parser.parse()? {
dst
} else {
Index::Num(0, parser.prev_span())
};
Ok(TableArg { dst })
}
}
/// Extra data associated with unary memory instructions. #[derive(Debug, Clone)] pubstruct MemoryArg<'a> { /// The index of the memory space. pub mem: Index<'a>,
}
impl<'a> Parse<'a> for MemoryArg<'a> { fn parse(parser: Parser<'a>) -> Result<Self> { let mem = iflet Some(mem) = parser.parse()? {
mem
} else {
Index::Num(0, parser.prev_span())
};
Ok(MemoryArg { mem })
}
}
/// Extra data associated with the `memory.init` instruction #[derive(Debug, Clone)] pubstruct MemoryInit<'a> { /// The index of the data segment we're copying into memory. pub data: Index<'a>, /// The index of the memory we're copying into, pub mem: Index<'a>,
}
impl<'a> Parse<'a> for MemoryInit<'a> { fn parse(parser: Parser<'a>) -> Result<Self> { let prev_span = parser.prev_span(); let (data, mem) = if parser.peek2::<Index>()? { let memory = parser.parse()?;
(parser.parse()?, memory)
} else {
(parser.parse()?, Index::Num(0, prev_span))
};
Ok(MemoryInit { data, mem })
}
}
/// Extra data associated with the `memory.copy` instruction #[derive(Debug, Clone)] pubstruct MemoryCopy<'a> { /// The index of the memory we're copying from. pub src: Index<'a>, /// The index of the memory we're copying to. pub dst: Index<'a>,
}
/// Extra data associated with the `struct.get/set` instructions #[derive(Debug, Clone)] pubstruct StructAccess<'a> { /// The index of the struct type we're accessing. pub r#struct: Index<'a>, /// The index of the field of the struct we're accessing pub field: Index<'a>,
}
/// Extra data associated with the `array.fill` instruction #[derive(Debug, Clone)] pubstruct ArrayFill<'a> { /// The index of the array type we're filling. pub array: Index<'a>,
}
/// Extra data associated with the `array.copy` instruction #[derive(Debug, Clone)] pubstruct ArrayCopy<'a> { /// The index of the array type we're copying to. pub dest_array: Index<'a>, /// The index of the array type we're copying from. pub src_array: Index<'a>,
}
/// Extra data associated with the `array.init_[data/elem]` instruction #[derive(Debug, Clone)] pubstruct ArrayInit<'a> { /// The index of the array type we're initializing. pub array: Index<'a>, /// The index of the data or elem segment we're reading from. pub segment: Index<'a>,
}
/// Extra data associated with the `array.new_fixed` instruction #[derive(Debug, Clone)] pubstruct ArrayNewFixed<'a> { /// The index of the array type we're accessing. pub array: Index<'a>, /// The amount of values to initialize the array with. pub length: u32,
}
/// Extra data associated with the `array.new_data` instruction #[derive(Debug, Clone)] pubstruct ArrayNewData<'a> { /// The index of the array type we're accessing. pub array: Index<'a>, /// The data segment to initialize from. pub data_idx: Index<'a>,
}
/// Extra data associated with the `array.new_elem` instruction #[derive(Debug, Clone)] pubstruct ArrayNewElem<'a> { /// The index of the array type we're accessing. pub array: Index<'a>, /// The elem segment to initialize from. pub elem_idx: Index<'a>,
}
/// Extra data associated with the `ref.cast` instruction #[derive(Debug, Clone)] pubstruct RefCast<'a> { /// The type to cast to. pub r#type: RefType<'a>,
}
/// Extra data associated with the `ref.test` instruction #[derive(Debug, Clone)] pubstruct RefTest<'a> { /// The type to test for. pub r#type: RefType<'a>,
}
/// Extra data associated with the `br_on_cast` instruction #[derive(Debug, Clone)] pubstruct BrOnCast<'a> { /// The label to branch to. pub label: Index<'a>, /// The type we're casting from. pub from_type: RefType<'a>, /// The type we're casting to. pub to_type: RefType<'a>,
}
/// Extra data associated with the `br_on_cast_fail` instruction #[derive(Debug, Clone)] pubstruct BrOnCastFail<'a> { /// The label to branch to. pub label: Index<'a>, /// The type we're casting from. pub from_type: RefType<'a>, /// The type we're casting to. pub to_type: RefType<'a>,
}
/// The memory ordering for atomic instructions. /// /// For an in-depth explanation of memory orderings, see the C++ documentation /// for [`memory_order`] or the Rust documentation for [`atomic::Ordering`]. /// /// [`memory_order`]: https://en.cppreference.com/w/cpp/atomic/memory_order /// [`atomic::Ordering`]: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html #[derive(Clone, Debug)] pubenum Ordering { /// Like `AcqRel` but all threads see all sequentially consistent operations /// in the same order.
AcqRel, /// For a load, it acquires; this orders all operations before the last /// "releasing" store. For a store, it releases; this orders all operations /// before it at the next "acquiring" load.
SeqCst,
}
impl<'a> Parse<'a> for Ordering { fn parse(parser: Parser<'a>) -> Result<Self> { if parser.peek::<kw::seq_cst>()? {
parser.parse::<kw::seq_cst>()?;
Ok(Ordering::SeqCst)
} elseif parser.peek::<kw::acq_rel>()? {
parser.parse::<kw::acq_rel>()?;
Ok(Ordering::AcqRel)
} else {
Err(parser.error("expected a memory ordering: `seq_cst` or `acq_rel`"))
}
}
}
/// Add a memory [`Ordering`] to the argument `T` of some instruction. /// /// This is helpful for many kinds of `*.atomic.*` instructions introduced by /// the shared-everything-threads proposal. Many of these instructions "build /// on" existing instructions by simply adding a memory order to them. #[derive(Clone, Debug)] pubstruct Ordered<T> { /// The memory ordering for this atomic instruction. pub ordering: Ordering, /// The original argument type. pub inner: T,
}
impl<'a, T> Parse<'a> for Ordered<T> where
T: Parse<'a>,
{ fn parse(parser: Parser<'a>) -> Result<Self> { let ordering = parser.parse()?; let inner = parser.parse()?;
Ok(Ordered { ordering, inner })
}
}
/// Different ways to specify a `v128.const` instruction #[derive(Clone, Debug)] #[allow(missing_docs)] pubenum V128Const {
I8x16([i8; 16]),
I16x8([i16; 8]),
I32x4([i32; 4]),
I64x2([i64; 2]),
F32x4([F32; 4]),
F64x2([F64; 2]),
}
impl V128Const { /// Returns the raw little-ended byte sequence used to represent this /// `v128` constant` /// /// This is typically suitable for encoding as the payload of the /// `v128.const` instruction. #[rustfmt::skip] pubfn to_le_bytes(&self) -> [u8; 16] { matchself {
V128Const::I8x16(arr) => [
arr[0] as u8,
arr[1] as u8,
arr[2] as u8,
arr[3] as u8,
arr[4] as u8,
arr[5] as u8,
arr[6] as u8,
arr[7] as u8,
arr[8] as u8,
arr[9] as u8,
arr[10] as u8,
arr[11] as u8,
arr[12] as u8,
arr[13] as u8,
arr[14] as u8,
arr[15] as u8,
],
V128Const::I16x8(arr) => { let a1 = arr[0].to_le_bytes(); let a2 = arr[1].to_le_bytes(); let a3 = arr[2].to_le_bytes(); let a4 = arr[3].to_le_bytes(); let a5 = arr[4].to_le_bytes(); let a6 = arr[5].to_le_bytes(); let a7 = arr[6].to_le_bytes(); let a8 = arr[7].to_le_bytes();
[
a1[0], a1[1],
a2[0], a2[1],
a3[0], a3[1],
a4[0], a4[1],
a5[0], a5[1],
a6[0], a6[1],
a7[0], a7[1],
a8[0], a8[1],
]
}
V128Const::I32x4(arr) => { let a1 = arr[0].to_le_bytes(); let a2 = arr[1].to_le_bytes(); let a3 = arr[2].to_le_bytes(); let a4 = arr[3].to_le_bytes();
[
a1[0], a1[1], a1[2], a1[3],
a2[0], a2[1], a2[2], a2[3],
a3[0], a3[1], a3[2], a3[3],
a4[0], a4[1], a4[2], a4[3],
]
}
V128Const::I64x2(arr) => { let a1 = arr[0].to_le_bytes(); let a2 = arr[1].to_le_bytes();
[
a1[0], a1[1], a1[2], a1[3], a1[4], a1[5], a1[6], a1[7],
a2[0], a2[1], a2[2], a2[3], a2[4], a2[5], a2[6], a2[7],
]
}
V128Const::F32x4(arr) => { let a1 = arr[0].bits.to_le_bytes(); let a2 = arr[1].bits.to_le_bytes(); let a3 = arr[2].bits.to_le_bytes(); let a4 = arr[3].bits.to_le_bytes();
[
a1[0], a1[1], a1[2], a1[3],
a2[0], a2[1], a2[2], a2[3],
a3[0], a3[1], a3[2], a3[3],
a4[0], a4[1], a4[2], a4[3],
]
}
V128Const::F64x2(arr) => { let a1 = arr[0].bits.to_le_bytes(); let a2 = arr[1].bits.to_le_bytes();
[
a1[0], a1[1], a1[2], a1[3], a1[4], a1[5], a1[6], a1[7],
a2[0], a2[1], a2[2], a2[3], a2[4], a2[5], a2[6], a2[7],
]
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.