mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
wasm: Ensure TableSection matches ElementSection
This commit is contained in:
parent
8620cdf75c
commit
b1f15799e3
4 changed files with 157 additions and 44 deletions
|
@ -20,7 +20,7 @@ use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
||||||
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
|
||||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||||
use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol};
|
use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol};
|
||||||
use crate::wasm_module::sections::{DataMode, DataSegment};
|
use crate::wasm_module::sections::{DataMode, DataSegment, Limits};
|
||||||
use crate::wasm_module::{
|
use crate::wasm_module::{
|
||||||
code_builder, CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType,
|
code_builder, CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType,
|
||||||
WasmModule,
|
WasmModule,
|
||||||
|
@ -153,7 +153,9 @@ impl<'a> WasmBackend<'a> {
|
||||||
wasm_fn_index
|
wasm_fn_index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) {
|
pub fn finalize(mut self) -> (WasmModule<'a>, Vec<'a, u32>) {
|
||||||
|
let fn_table_size = 1 + self.module.element.max_table_index();
|
||||||
|
self.module.table.function_table.limits = Limits::MinMax(fn_table_size, fn_table_size);
|
||||||
(self.module, self.called_preload_fns)
|
(self.module, self.called_preload_fns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +295,11 @@ impl<'a> WasmBackend<'a> {
|
||||||
///
|
///
|
||||||
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
|
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
|
||||||
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
|
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
|
||||||
pub fn build_higher_order_wrapper(&mut self, wrapper_lookup_idx: usize, inner_lookup_idx: usize) {
|
pub fn build_higher_order_wrapper(
|
||||||
|
&mut self,
|
||||||
|
wrapper_lookup_idx: usize,
|
||||||
|
inner_lookup_idx: usize,
|
||||||
|
) {
|
||||||
use Align::*;
|
use Align::*;
|
||||||
use ValueType::*;
|
use ValueType::*;
|
||||||
|
|
||||||
|
@ -1591,6 +1597,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a refcount increment procedure and return its Wasm function index
|
||||||
pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 {
|
pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 {
|
||||||
let ident_ids = self
|
let ident_ids = self
|
||||||
.interns
|
.interns
|
||||||
|
@ -1613,8 +1620,6 @@ impl<'a> WasmBackend<'a> {
|
||||||
.position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout)
|
.position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let wasm_fn_index = self.fn_index_offset + proc_index as u32;
|
self.fn_index_offset + proc_index as u32
|
||||||
|
|
||||||
wasm_fn_index
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ impl Align {
|
||||||
if (max_align >= Align::Bytes2) && (offset & 1 == 0) {
|
if (max_align >= Align::Bytes2) && (offset & 1 == 0) {
|
||||||
return Align::Bytes2;
|
return Align::Bytes2;
|
||||||
}
|
}
|
||||||
return Align::Bytes1;
|
Align::Bytes1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}
|
||||||
use self::linking::{LinkingSection, RelocationSection};
|
use self::linking::{LinkingSection, RelocationSection};
|
||||||
use self::sections::{
|
use self::sections::{
|
||||||
CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection,
|
CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection,
|
||||||
ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection,
|
ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TableSection,
|
||||||
|
TypeSection,
|
||||||
};
|
};
|
||||||
use self::serialize::{SerialBuffer, Serialize};
|
use self::serialize::{SerialBuffer, Serialize};
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ pub struct WasmModule<'a> {
|
||||||
pub types: TypeSection<'a>,
|
pub types: TypeSection<'a>,
|
||||||
pub import: ImportSection<'a>,
|
pub import: ImportSection<'a>,
|
||||||
pub function: FunctionSection<'a>,
|
pub function: FunctionSection<'a>,
|
||||||
pub table: OpaqueSection<'a>,
|
pub table: TableSection,
|
||||||
pub memory: MemorySection<'a>,
|
pub memory: MemorySection<'a>,
|
||||||
pub global: GlobalSection<'a>,
|
pub global: GlobalSection<'a>,
|
||||||
pub export: ExportSection<'a>,
|
pub export: ExportSection<'a>,
|
||||||
|
@ -138,7 +139,7 @@ impl<'a> WasmModule<'a> {
|
||||||
let function = FunctionSection::preload(arena, bytes, &mut cursor);
|
let function = FunctionSection::preload(arena, bytes, &mut cursor);
|
||||||
let defined_fn_signatures = function.parse(arena);
|
let defined_fn_signatures = function.parse(arena);
|
||||||
|
|
||||||
let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor);
|
let table = TableSection::preload(bytes, &mut cursor);
|
||||||
|
|
||||||
let memory = MemorySection::preload(arena, bytes, &mut cursor);
|
let memory = MemorySection::preload(arena, bytes, &mut cursor);
|
||||||
|
|
||||||
|
|
|
@ -276,33 +276,6 @@ impl<'a> Section<'a> for TypeSection<'a> {
|
||||||
*
|
*
|
||||||
*******************************************************************/
|
*******************************************************************/
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
||||||
pub enum RefType {
|
|
||||||
Func = 0x70,
|
|
||||||
Extern = 0x6f,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TableType {
|
|
||||||
pub ref_type: RefType,
|
|
||||||
pub limits: Limits,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for TableType {
|
|
||||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
|
||||||
buffer.append_u8(self.ref_type as u8);
|
|
||||||
self.limits.serialize(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SkipBytes for TableType {
|
|
||||||
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
|
|
||||||
u8::skip_bytes(bytes, cursor);
|
|
||||||
Limits::skip_bytes(bytes, cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ImportDesc {
|
pub enum ImportDesc {
|
||||||
Func { signature_index: u32 },
|
Func { signature_index: u32 },
|
||||||
|
@ -457,6 +430,104 @@ impl<'a> FunctionSection<'a> {
|
||||||
|
|
||||||
section_impl!(FunctionSection, SectionId::Function);
|
section_impl!(FunctionSection, SectionId::Function);
|
||||||
|
|
||||||
|
/*******************************************************************
|
||||||
|
*
|
||||||
|
* Table section
|
||||||
|
*
|
||||||
|
* Defines tables used for indirect references to host memory.
|
||||||
|
* The table *contents* are elsewhere, in the ElementSection.
|
||||||
|
*
|
||||||
|
*******************************************************************/
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub enum RefType {
|
||||||
|
Func = 0x70,
|
||||||
|
Extern = 0x6f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TableType {
|
||||||
|
pub ref_type: RefType,
|
||||||
|
pub limits: Limits,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for TableType {
|
||||||
|
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||||
|
buffer.append_u8(self.ref_type as u8);
|
||||||
|
self.limits.serialize(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkipBytes for TableType {
|
||||||
|
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
|
||||||
|
u8::skip_bytes(bytes, cursor);
|
||||||
|
Limits::skip_bytes(bytes, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TableSection {
|
||||||
|
pub function_table: TableType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableSection {
|
||||||
|
const ID: SectionId = SectionId::Table;
|
||||||
|
|
||||||
|
pub fn preload(module_bytes: &[u8], mod_cursor: &mut usize) -> Self {
|
||||||
|
let (count, section_bytes) = parse_section(Self::ID, module_bytes, mod_cursor);
|
||||||
|
|
||||||
|
match count {
|
||||||
|
0 => TableSection {
|
||||||
|
function_table: TableType {
|
||||||
|
ref_type: RefType::Func,
|
||||||
|
limits: Limits::MinMax(0, 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
if section_bytes[0] != RefType::Func as u8 {
|
||||||
|
internal_error!("Only funcref tables are supported")
|
||||||
|
}
|
||||||
|
let mut section_cursor = 1;
|
||||||
|
let limits = Limits::parse(section_bytes, &mut section_cursor);
|
||||||
|
|
||||||
|
TableSection {
|
||||||
|
function_table: TableType {
|
||||||
|
ref_type: RefType::Func,
|
||||||
|
limits,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => internal_error!("Multiple tables are not supported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
let section_id_bytes = 1;
|
||||||
|
let section_length_bytes = 1;
|
||||||
|
let num_tables_bytes = 1;
|
||||||
|
let ref_type_bytes = 1;
|
||||||
|
let limits_bytes = match self.function_table.limits {
|
||||||
|
Limits::Min(_) => MAX_SIZE_ENCODED_U32,
|
||||||
|
Limits::MinMax(..) => 2 * MAX_SIZE_ENCODED_U32,
|
||||||
|
};
|
||||||
|
|
||||||
|
section_id_bytes + section_length_bytes + num_tables_bytes + ref_type_bytes + limits_bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for TableSection {
|
||||||
|
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||||
|
let header_indices = write_section_header(buffer, Self::ID);
|
||||||
|
|
||||||
|
let num_tables: u32 = 1;
|
||||||
|
num_tables.serialize(buffer);
|
||||||
|
self.function_table.serialize(buffer);
|
||||||
|
|
||||||
|
update_section_size(buffer, header_indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
*
|
*
|
||||||
* Memory section
|
* Memory section
|
||||||
|
@ -502,6 +573,21 @@ impl SkipBytes for Limits {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Limits {
|
||||||
|
fn parse(bytes: &[u8], cursor: &mut usize) -> Self {
|
||||||
|
let variant_id = bytes[*cursor];
|
||||||
|
*cursor += 1;
|
||||||
|
|
||||||
|
let min = parse_u32_or_panic(bytes, cursor);
|
||||||
|
if variant_id == LimitsId::MinMax as u8 {
|
||||||
|
let max = parse_u32_or_panic(bytes, cursor);
|
||||||
|
Limits::MinMax(min, max)
|
||||||
|
} else {
|
||||||
|
Limits::Min(min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MemorySection<'a> {
|
pub struct MemorySection<'a> {
|
||||||
pub count: u32,
|
pub count: u32,
|
||||||
|
@ -584,6 +670,13 @@ impl ConstExpr {
|
||||||
|
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unwrap_i32(&self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::I32(x) => *x,
|
||||||
|
_ => internal_error!("Expected ConstExpr to be I32"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for ConstExpr {
|
impl Serialize for ConstExpr {
|
||||||
|
@ -803,6 +896,7 @@ enum ElementSegmentFormatId {
|
||||||
ActiveImplicitTableIndex = 0x00,
|
ActiveImplicitTableIndex = 0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Segment initialises a subrange of elements in a table. Normally there's just one Segment.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ElementSegment<'a> {
|
struct ElementSegment<'a> {
|
||||||
offset: ConstExpr, // The starting table index for the segment
|
offset: ConstExpr, // The starting table index for the segment
|
||||||
|
@ -856,9 +950,8 @@ impl<'a> Serialize for ElementSegment<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An "element" represents an indirectly-callable function the Wasm runtime's function table.
|
/// An Element is an entry in a Table (see TableSection)
|
||||||
/// Future Wasm versions might have tables where the elements are DOM references or other things.
|
/// The only currently supported Element type is a function reference, used for indirect calls.
|
||||||
/// Elements can be initialised in groups called "segments". Normally there's just one.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ElementSection<'a> {
|
pub struct ElementSection<'a> {
|
||||||
segments: Vec<'a, ElementSegment<'a>>,
|
segments: Vec<'a, ElementSegment<'a>>,
|
||||||
|
@ -892,21 +985,35 @@ impl<'a> ElementSection<'a> {
|
||||||
|
|
||||||
/// Get a table index for a function (equivalent to a function pointer)
|
/// Get a table index for a function (equivalent to a function pointer)
|
||||||
/// The function will be inserted into the table if it's not already there.
|
/// The function will be inserted into the table if it's not already there.
|
||||||
/// This index is what the call_indirect instruction expects
|
/// This index is what the call_indirect instruction expects.
|
||||||
/// (It works mostly the same as with pointers, except you can't jump to arbitrary code)
|
/// (This works mostly the same as function pointers, except hackers can't jump to arbitrary code)
|
||||||
pub fn get_fn_table_index(&mut self, fn_index: u32) -> i32 {
|
pub fn get_fn_table_index(&mut self, fn_index: u32) -> i32 {
|
||||||
// In practice there is always one segment. We allow a bit more generality by using the last one.
|
// In practice there is always one segment. We allow a bit more generality by using the last one.
|
||||||
let segment = self.segments.last_mut().unwrap();
|
let segment = self.segments.last_mut().unwrap();
|
||||||
|
let offset = segment.offset.unwrap_i32();
|
||||||
let pos = segment.fn_indices.iter().position(|f| *f == fn_index);
|
let pos = segment.fn_indices.iter().position(|f| *f == fn_index);
|
||||||
if let Some(existing_table_index) = pos {
|
if let Some(existing_table_index) = pos {
|
||||||
existing_table_index as i32
|
offset + existing_table_index as i32
|
||||||
} else {
|
} else {
|
||||||
let new_table_index = segment.fn_indices.len();
|
let new_table_index = segment.fn_indices.len();
|
||||||
segment.fn_indices.push(fn_index);
|
segment.fn_indices.push(fn_index);
|
||||||
new_table_index as i32
|
offset + new_table_index as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Number of elements in the table
|
||||||
|
pub fn max_table_index(&self) -> u32 {
|
||||||
|
let mut result = 0;
|
||||||
|
for s in self.segments.iter() {
|
||||||
|
let max_index = s.offset.unwrap_i32() + s.fn_indices.len() as i32;
|
||||||
|
if max_index > result {
|
||||||
|
result = max_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Approximate serialized byte size (for buffer capacity)
|
||||||
pub fn size(&self) -> usize {
|
pub fn size(&self) -> usize {
|
||||||
self.segments.iter().map(|seg| seg.size()).sum()
|
self.segments.iter().map(|seg| seg.size()).sum()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue