//! Data structures for representing decoded wasm modules.12use crate::prelude::*;3use crate::*;4use alloc::collections::BTreeMap;5use core::ops::Range;6use cranelift_entity::{EntityRef, packed_option::ReservedValue};7use serde_derive::{Deserialize, Serialize};89/// A WebAssembly linear memory initializer.10#[derive(Clone, Debug, Serialize, Deserialize)]11pub struct MemoryInitializer {12/// The index of a linear memory to initialize.13pub memory_index: MemoryIndex,14/// The base offset to start this segment at.15pub offset: ConstExpr,16/// The range of the data to write within the linear memory.17///18/// This range indexes into a separately stored data section which will be19/// provided with the compiled module's code as well.20pub data: Range<u32>,21}2223/// Similar to the above `MemoryInitializer` but only used when memory24/// initializers are statically known to be valid.25#[derive(Clone, Debug, Serialize, Deserialize)]26pub struct StaticMemoryInitializer {27/// The 64-bit offset, in bytes, of where this initializer starts.28pub offset: u64,2930/// The range of data to write at `offset`, where these indices are indexes31/// into the compiled wasm module's data section.32pub data: Range<u32>,33}3435/// The type of WebAssembly linear memory initialization to use for a module.36#[derive(Debug, Serialize, Deserialize)]37pub enum MemoryInitialization {38/// Memory initialization is segmented.39///40/// Segmented initialization can be used for any module, but it is required41/// if:42///43/// * A data segment referenced an imported memory.44/// * A data segment uses a global base.45///46/// Segmented initialization is performed by processing the complete set of47/// data segments when the module is instantiated.48///49/// This is the default memory initialization type.50Segmented(Vec<MemoryInitializer>),5152/// Memory initialization is statically known and involves a single `memcpy`53/// or otherwise simply making the defined data visible.54///55/// To be statically initialized everything must reference a defined memory56/// and all data segments have a statically known in-bounds base (no57/// globals).58///59/// This form of memory initialization is a more optimized version of60/// `Segmented` where memory can be initialized with one of a few methods:61///62/// * First it could be initialized with a single `memcpy` of data from the63/// module to the linear memory.64/// * Otherwise techniques like `mmap` are also possible to make this data,65/// which might reside in a compiled module on disk, available immediately66/// in a linear memory's address space.67///68/// To facilitate the latter of these techniques the `try_static_init`69/// function below, which creates this variant, takes a host page size70/// argument which can page-align everything to make mmap-ing possible.71Static {72/// The initialization contents for each linear memory.73///74/// This array has, for each module's own linear memory, the contents75/// necessary to initialize it. If the memory has a `None` value then no76/// initialization is necessary (it's zero-filled). Otherwise with77/// `Some` the first element of the tuple is the offset in memory to78/// start the initialization and the `Range` is the range within the79/// final data section of the compiled module of bytes to copy into the80/// memory.81///82/// The offset, range base, and range end are all guaranteed to be page83/// aligned to the page size passed in to `try_static_init`.84map: PrimaryMap<MemoryIndex, Option<StaticMemoryInitializer>>,85},86}8788impl Default for MemoryInitialization {89fn default() -> Self {90Self::Segmented(Vec::new())91}92}9394impl MemoryInitialization {95/// Returns whether this initialization is of the form96/// `MemoryInitialization::Segmented`.97pub fn is_segmented(&self) -> bool {98match self {99MemoryInitialization::Segmented(_) => true,100_ => false,101}102}103104/// Performs the memory initialization steps for this set of initializers.105///106/// This will perform wasm initialization in compliance with the wasm spec107/// and how data segments are processed. This doesn't need to necessarily108/// only be called as part of initialization, however, as it's structured to109/// allow learning about memory ahead-of-time at compile time possibly.110///111/// This function will return true if all memory initializers are processed112/// successfully. If any initializer hits an error or, for example, a113/// global value is needed but `None` is returned, then false will be114/// returned. At compile-time this typically means that the "error" in115/// question needs to be deferred to runtime, and at runtime this means116/// that an invalid initializer has been found and a trap should be117/// generated.118pub fn init_memory(&self, state: &mut dyn InitMemory) -> bool {119let initializers = match self {120// Fall through below to the segmented memory one-by-one121// initialization.122MemoryInitialization::Segmented(list) => list,123124// If previously switched to static initialization then pass through125// all those parameters here to the `write` callback.126//127// Note that existence of `Static` already guarantees that all128// indices are in-bounds.129MemoryInitialization::Static { map } => {130for (index, init) in map {131if let Some(init) = init {132let result = state.write(index, init);133if !result {134return result;135}136}137}138return true;139}140};141142for initializer in initializers {143let &MemoryInitializer {144memory_index,145ref offset,146ref data,147} = initializer;148149// First up determine the start/end range and verify that they're150// in-bounds for the initial size of the memory at `memory_index`.151// Note that this can bail if we don't have access to globals yet152// (e.g. this is a task happening before instantiation at153// compile-time).154let start = match state.eval_offset(memory_index, offset) {155Some(start) => start,156None => return false,157};158let len = u64::try_from(data.len()).unwrap();159let end = match start.checked_add(len) {160Some(end) => end,161None => return false,162};163164match state.memory_size_in_bytes(memory_index) {165Ok(max) => {166if end > max {167return false;168}169}170171// Note that computing the minimum can overflow if the page size172// is the default 64KiB and the memory's minimum size in pages173// is `1 << 48`, the maximum number of minimum pages for 64-bit174// memories. We don't return `false` to signal an error here and175// instead defer the error to runtime, when it will be176// impossible to allocate that much memory anyways.177Err(_) => {}178}179180// The limits of the data segment have been validated at this point181// so the `write` callback is called with the range of data being182// written. Any erroneous result is propagated upwards.183let init = StaticMemoryInitializer {184offset: start,185data: data.clone(),186};187let result = state.write(memory_index, &init);188if !result {189return result;190}191}192193return true;194}195}196197/// The various callbacks provided here are used to drive the smaller bits of198/// memory initialization.199pub trait InitMemory {200/// Returns the size, in bytes, of the memory specified. For compile-time201/// purposes this would be the memory type's minimum size.202fn memory_size_in_bytes(&mut self, memory_index: MemoryIndex) -> Result<u64, SizeOverflow>;203204/// Returns the value of the constant expression, as a `u64`. Note that205/// this may involve zero-extending a 32-bit global to a 64-bit number. May206/// return `None` to indicate that the expression involves a value which is207/// not available yet.208fn eval_offset(&mut self, memory_index: MemoryIndex, expr: &ConstExpr) -> Option<u64>;209210/// A callback used to actually write data. This indicates that the211/// specified memory must receive the specified range of data at the212/// specified offset. This can return false on failure.213fn write(&mut self, memory_index: MemoryIndex, init: &StaticMemoryInitializer) -> bool;214}215216/// Table initialization data for all tables in the module.217#[derive(Debug, Default, Serialize, Deserialize)]218pub struct TableInitialization {219/// Initial values for tables defined within the module itself.220///221/// This contains the initial values and initializers for tables defined222/// within a wasm, so excluding imported tables. This initializer can223/// represent null-initialized tables, element-initialized tables (e.g. with224/// the function-references proposal), or precomputed images of table225/// initialization. For example table initializers to a table that are all226/// in-bounds will get removed from `segment` and moved into227/// `initial_values` here.228pub initial_values: PrimaryMap<DefinedTableIndex, TableInitialValue>,229230/// Element segments present in the initial wasm module which are executed231/// at instantiation time.232///233/// These element segments are iterated over during instantiation to apply234/// any segments that weren't already moved into `initial_values` above.235pub segments: Vec<TableSegment>,236}237238/// Initial value for all elements in a table.239#[derive(Clone, Debug, Serialize, Deserialize)]240pub enum TableInitialValue {241/// Initialize each table element to null, optionally setting some elements242/// to non-null given the precomputed image.243Null {244/// A precomputed image of table initializers for this table.245///246/// This image is constructed during `try_func_table_init` and247/// null-initialized elements are represented with248/// `FuncIndex::reserved_value()`. Note that this image is empty by249/// default and may not encompass the entire span of the table in which250/// case the elements are initialized to null.251precomputed: Vec<FuncIndex>,252},253/// An arbitrary const expression.254Expr(ConstExpr),255}256257/// A WebAssembly table initializer segment.258#[derive(Clone, Debug, Serialize, Deserialize)]259pub struct TableSegment {260/// The index of a table to initialize.261pub table_index: TableIndex,262/// The base offset to start this segment at.263pub offset: ConstExpr,264/// The values to write into the table elements.265pub elements: TableSegmentElements,266}267268/// Elements of a table segment, either a list of functions or list of arbitrary269/// expressions.270#[derive(Clone, Debug, Serialize, Deserialize)]271pub enum TableSegmentElements {272/// A sequential list of functions where `FuncIndex::reserved_value()`273/// indicates a null function.274Functions(Box<[FuncIndex]>),275/// Arbitrary expressions, aka either functions, null or a load of a global.276Expressions(Box<[ConstExpr]>),277}278279impl TableSegmentElements {280/// Returns the number of elements in this segment.281pub fn len(&self) -> u64 {282match self {283Self::Functions(s) => u64::try_from(s.len()).unwrap(),284Self::Expressions(s) => u64::try_from(s.len()).unwrap(),285}286}287}288289/// A translated WebAssembly module, excluding the function bodies and290/// memory initializers.291#[derive(Debug, Serialize, Deserialize)]292pub struct Module {293/// This module's index.294pub module_index: StaticModuleIndex,295296/// The name of this wasm module, often found in the wasm file.297pub name: Option<String>,298299/// All import records, in the order they are declared in the module.300pub initializers: Vec<Initializer>,301302/// Exported entities.303pub exports: IndexMap<String, EntityIndex>,304305/// The module "start" function, if present.306pub start_func: Option<FuncIndex>,307308/// WebAssembly table initialization data, per table.309pub table_initialization: TableInitialization,310311/// WebAssembly linear memory initializer.312pub memory_initialization: MemoryInitialization,313314/// WebAssembly passive elements.315pub passive_elements: Vec<TableSegmentElements>,316317/// The map from passive element index (element segment index space) to index in `passive_elements`.318pub passive_elements_map: BTreeMap<ElemIndex, usize>,319320/// The map from passive data index (data segment index space) to index in `passive_data`.321pub passive_data_map: BTreeMap<DataIndex, Range<u32>>,322323/// Types declared in the wasm module.324pub types: PrimaryMap<TypeIndex, EngineOrModuleTypeIndex>,325326/// Number of imported or aliased functions in the module.327pub num_imported_funcs: usize,328329/// Number of imported or aliased tables in the module.330pub num_imported_tables: usize,331332/// Number of imported or aliased memories in the module.333pub num_imported_memories: usize,334335/// Number of imported or aliased globals in the module.336pub num_imported_globals: usize,337338/// Number of imported or aliased tags in the module.339pub num_imported_tags: usize,340341/// Does this module need a GC heap to run?342pub needs_gc_heap: bool,343344/// Number of functions that "escape" from this module may need to have a345/// `VMFuncRef` constructed for them.346///347/// This is also the number of functions in the `functions` array below with348/// an `func_ref` index (and is the maximum func_ref index).349pub num_escaped_funcs: usize,350351/// Types of functions, imported and local.352pub functions: PrimaryMap<FuncIndex, FunctionType>,353354/// WebAssembly tables.355pub tables: PrimaryMap<TableIndex, Table>,356357/// WebAssembly linear memory plans.358pub memories: PrimaryMap<MemoryIndex, Memory>,359360/// WebAssembly global variables.361pub globals: PrimaryMap<GlobalIndex, Global>,362363/// WebAssembly global initializers for locally-defined globals.364pub global_initializers: PrimaryMap<DefinedGlobalIndex, ConstExpr>,365366/// WebAssembly exception and control tags.367pub tags: PrimaryMap<TagIndex, Tag>,368}369370/// Initialization routines for creating an instance, encompassing imports,371/// modules, instances, aliases, etc.372#[derive(Debug, Serialize, Deserialize)]373pub enum Initializer {374/// An imported item is required to be provided.375Import {376/// Name of this import377name: String,378/// The field name projection of this import379field: String,380/// Where this import will be placed, which also has type information381/// about the import.382index: EntityIndex,383},384}385386impl Module {387/// Allocates the module data structures.388pub fn new(module_index: StaticModuleIndex) -> Self {389Self {390module_index,391name: Default::default(),392initializers: Default::default(),393exports: Default::default(),394start_func: Default::default(),395table_initialization: Default::default(),396memory_initialization: Default::default(),397passive_elements: Default::default(),398passive_elements_map: Default::default(),399passive_data_map: Default::default(),400types: Default::default(),401num_imported_funcs: Default::default(),402num_imported_tables: Default::default(),403num_imported_memories: Default::default(),404num_imported_globals: Default::default(),405num_imported_tags: Default::default(),406needs_gc_heap: Default::default(),407num_escaped_funcs: Default::default(),408functions: Default::default(),409tables: Default::default(),410memories: Default::default(),411globals: Default::default(),412global_initializers: Default::default(),413tags: Default::default(),414}415}416417/// Convert a `DefinedFuncIndex` into a `FuncIndex`.418#[inline]419pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex {420FuncIndex::new(self.num_imported_funcs + defined_func.index())421}422423/// Convert a `FuncIndex` into a `DefinedFuncIndex`. Returns None if the424/// index is an imported function.425#[inline]426pub fn defined_func_index(&self, func: FuncIndex) -> Option<DefinedFuncIndex> {427if func.index() < self.num_imported_funcs {428None429} else {430Some(DefinedFuncIndex::new(431func.index() - self.num_imported_funcs,432))433}434}435436/// Test whether the given function index is for an imported function.437#[inline]438pub fn is_imported_function(&self, index: FuncIndex) -> bool {439index.index() < self.num_imported_funcs440}441442/// Convert a `DefinedTableIndex` into a `TableIndex`.443#[inline]444pub fn table_index(&self, defined_table: DefinedTableIndex) -> TableIndex {445TableIndex::new(self.num_imported_tables + defined_table.index())446}447448/// Convert a `TableIndex` into a `DefinedTableIndex`. Returns None if the449/// index is an imported table.450#[inline]451pub fn defined_table_index(&self, table: TableIndex) -> Option<DefinedTableIndex> {452if table.index() < self.num_imported_tables {453None454} else {455Some(DefinedTableIndex::new(456table.index() - self.num_imported_tables,457))458}459}460461/// Test whether the given table index is for an imported table.462#[inline]463pub fn is_imported_table(&self, index: TableIndex) -> bool {464index.index() < self.num_imported_tables465}466467/// Convert a `DefinedMemoryIndex` into a `MemoryIndex`.468#[inline]469pub fn memory_index(&self, defined_memory: DefinedMemoryIndex) -> MemoryIndex {470MemoryIndex::new(self.num_imported_memories + defined_memory.index())471}472473/// Convert a `MemoryIndex` into a `DefinedMemoryIndex`. Returns None if the474/// index is an imported memory.475#[inline]476pub fn defined_memory_index(&self, memory: MemoryIndex) -> Option<DefinedMemoryIndex> {477if memory.index() < self.num_imported_memories {478None479} else {480Some(DefinedMemoryIndex::new(481memory.index() - self.num_imported_memories,482))483}484}485486/// Convert a `DefinedMemoryIndex` into an `OwnedMemoryIndex`. Returns None487/// if the index is an imported memory.488#[inline]489pub fn owned_memory_index(&self, memory: DefinedMemoryIndex) -> OwnedMemoryIndex {490assert!(491memory.index() < self.memories.len(),492"non-shared memory must have an owned index"493);494495// Once we know that the memory index is not greater than the number of496// plans, we can iterate through the plans up to the memory index and497// count how many are not shared (i.e., owned).498let owned_memory_index = self499.memories500.iter()501.skip(self.num_imported_memories)502.take(memory.index())503.filter(|(_, mp)| !mp.shared)504.count();505OwnedMemoryIndex::new(owned_memory_index)506}507508/// Test whether the given memory index is for an imported memory.509#[inline]510pub fn is_imported_memory(&self, index: MemoryIndex) -> bool {511index.index() < self.num_imported_memories512}513514/// Convert a `DefinedGlobalIndex` into a `GlobalIndex`.515#[inline]516pub fn global_index(&self, defined_global: DefinedGlobalIndex) -> GlobalIndex {517GlobalIndex::new(self.num_imported_globals + defined_global.index())518}519520/// Convert a `GlobalIndex` into a `DefinedGlobalIndex`. Returns None if the521/// index is an imported global.522#[inline]523pub fn defined_global_index(&self, global: GlobalIndex) -> Option<DefinedGlobalIndex> {524if global.index() < self.num_imported_globals {525None526} else {527Some(DefinedGlobalIndex::new(528global.index() - self.num_imported_globals,529))530}531}532533/// Test whether the given global index is for an imported global.534#[inline]535pub fn is_imported_global(&self, index: GlobalIndex) -> bool {536index.index() < self.num_imported_globals537}538539/// Test whether the given tag index is for an imported tag.540#[inline]541pub fn is_imported_tag(&self, index: TagIndex) -> bool {542index.index() < self.num_imported_tags543}544545/// Convert a `DefinedTagIndex` into a `TagIndex`.546#[inline]547pub fn tag_index(&self, defined_tag: DefinedTagIndex) -> TagIndex {548TagIndex::new(self.num_imported_tags + defined_tag.index())549}550551/// Convert a `TagIndex` into a `DefinedTagIndex`. Returns None if the552/// index is an imported tag.553#[inline]554pub fn defined_tag_index(&self, tag: TagIndex) -> Option<DefinedTagIndex> {555if tag.index() < self.num_imported_tags {556None557} else {558Some(DefinedTagIndex::new(tag.index() - self.num_imported_tags))559}560}561562/// Returns an iterator of all the imports in this module, along with their563/// module name, field name, and type that's being imported.564pub fn imports(&self) -> impl ExactSizeIterator<Item = (&str, &str, EntityType)> {565self.initializers.iter().map(move |i| match i {566Initializer::Import { name, field, index } => {567(name.as_str(), field.as_str(), self.type_of(*index))568}569})570}571572/// Get this module's `i`th import.573pub fn import(&self, i: usize) -> Option<(&str, &str, EntityType)> {574match self.initializers.get(i)? {575Initializer::Import { name, field, index } => Some((name, field, self.type_of(*index))),576}577}578579/// Returns the type of an item based on its index580pub fn type_of(&self, index: EntityIndex) -> EntityType {581match index {582EntityIndex::Global(i) => EntityType::Global(self.globals[i]),583EntityIndex::Table(i) => EntityType::Table(self.tables[i]),584EntityIndex::Memory(i) => EntityType::Memory(self.memories[i]),585EntityIndex::Function(i) => EntityType::Function(self.functions[i].signature),586EntityIndex::Tag(i) => EntityType::Tag(self.tags[i]),587}588}589590/// Appends a new tag to this module with the given type information.591pub fn push_tag(592&mut self,593signature: impl Into<EngineOrModuleTypeIndex>,594exception: impl Into<EngineOrModuleTypeIndex>,595) -> TagIndex {596let signature = signature.into();597let exception = exception.into();598self.tags.push(Tag {599signature,600exception,601})602}603604/// Appends a new function to this module with the given type information,605/// used for functions that either don't escape or aren't certain whether606/// they escape yet.607pub fn push_function(&mut self, signature: impl Into<EngineOrModuleTypeIndex>) -> FuncIndex {608let signature = signature.into();609self.functions.push(FunctionType {610signature,611func_ref: FuncRefIndex::reserved_value(),612})613}614615/// Returns an iterator over all of the defined function indices in this616/// module.617pub fn defined_func_indices(&self) -> impl ExactSizeIterator<Item = DefinedFuncIndex> + use<> {618(0..self.functions.len() - self.num_imported_funcs).map(|i| DefinedFuncIndex::new(i))619}620621/// Returns the number of functions defined by this module itself: all622/// functions minus imported functions.623pub fn num_defined_funcs(&self) -> usize {624self.functions.len() - self.num_imported_funcs625}626627/// Returns the number of tables defined by this module itself: all tables628/// minus imported tables.629pub fn num_defined_tables(&self) -> usize {630self.tables.len() - self.num_imported_tables631}632633/// Returns the number of memories defined by this module itself: all634/// memories minus imported memories.635pub fn num_defined_memories(&self) -> usize {636self.memories.len() - self.num_imported_memories637}638639/// Returns the number of globals defined by this module itself: all640/// globals minus imported globals.641pub fn num_defined_globals(&self) -> usize {642self.globals.len() - self.num_imported_globals643}644645/// Returns the number of tags defined by this module itself: all tags646/// minus imported tags.647pub fn num_defined_tags(&self) -> usize {648self.tags.len() - self.num_imported_tags649}650651/// Tests whether `index` is valid for this module.652pub fn is_valid(&self, index: EntityIndex) -> bool {653match index {654EntityIndex::Function(i) => self.functions.is_valid(i),655EntityIndex::Table(i) => self.tables.is_valid(i),656EntityIndex::Memory(i) => self.memories.is_valid(i),657EntityIndex::Global(i) => self.globals.is_valid(i),658EntityIndex::Tag(i) => self.tags.is_valid(i),659}660}661}662663impl TypeTrace for Module {664fn trace<F, E>(&self, func: &mut F) -> Result<(), E>665where666F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,667{668// NB: Do not `..` elide unmodified fields so that we get compile errors669// when adding new fields that might need re-canonicalization.670let Self {671module_index: _,672name: _,673initializers: _,674exports: _,675start_func: _,676table_initialization: _,677memory_initialization: _,678passive_elements: _,679passive_elements_map: _,680passive_data_map: _,681types,682num_imported_funcs: _,683num_imported_tables: _,684num_imported_memories: _,685num_imported_globals: _,686num_imported_tags: _,687num_escaped_funcs: _,688needs_gc_heap: _,689functions,690tables,691memories: _,692globals,693global_initializers: _,694tags,695} = self;696697for t in types.values().copied() {698func(t)?;699}700for f in functions.values() {701f.trace(func)?;702}703for t in tables.values() {704t.trace(func)?;705}706for g in globals.values() {707g.trace(func)?;708}709for t in tags.values() {710t.trace(func)?;711}712Ok(())713}714715fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>716where717F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,718{719// NB: Do not `..` elide unmodified fields so that we get compile errors720// when adding new fields that might need re-canonicalization.721let Self {722module_index: _,723name: _,724initializers: _,725exports: _,726start_func: _,727table_initialization: _,728memory_initialization: _,729passive_elements: _,730passive_elements_map: _,731passive_data_map: _,732types,733num_imported_funcs: _,734num_imported_tables: _,735num_imported_memories: _,736num_imported_globals: _,737num_imported_tags: _,738num_escaped_funcs: _,739needs_gc_heap: _,740functions,741tables,742memories: _,743globals,744global_initializers: _,745tags,746} = self;747748for t in types.values_mut() {749func(t)?;750}751for f in functions.values_mut() {752f.trace_mut(func)?;753}754for t in tables.values_mut() {755t.trace_mut(func)?;756}757for g in globals.values_mut() {758g.trace_mut(func)?;759}760for t in tags.values_mut() {761t.trace_mut(func)?;762}763Ok(())764}765}766767/// Type information about functions in a wasm module.768#[derive(Debug, Serialize, Deserialize)]769pub struct FunctionType {770/// The type of this function, indexed into the module-wide type tables for771/// a module compilation.772pub signature: EngineOrModuleTypeIndex,773/// The index into the funcref table, if present. Note that this is774/// `reserved_value()` if the function does not escape from a module.775pub func_ref: FuncRefIndex,776}777778impl TypeTrace for FunctionType {779fn trace<F, E>(&self, func: &mut F) -> Result<(), E>780where781F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,782{783func(self.signature)784}785786fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>787where788F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,789{790func(&mut self.signature)791}792}793794impl FunctionType {795/// Returns whether this function's type is one that "escapes" the current796/// module, meaning that the function is exported, used in `ref.func`, used797/// in a table, etc.798pub fn is_escaping(&self) -> bool {799!self.func_ref.is_reserved_value()800}801}802803/// Index into the funcref table within a VMContext for a function.804#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]805pub struct FuncRefIndex(u32);806cranelift_entity::entity_impl!(FuncRefIndex);807808809