gen_wasm: Get rid of individual locals for values in stack memory

All values in stack memory can share the stack frame pointer,
with different offsets.

This avoids having to generate initialisation code for individual
pointers we used to have. It should also make things more efficient
when the runtime compiles Wasm to machine code. It can just assign
the stack frame pointer to a single CPU register and leave it there.

The original idea was to make params and local variables work the same.
(A struct param will be passed as a pointer to caller stack memory.)
But now I don't think that's worth it. Some match expressions get more
awkward this way but we might be able to refactor that to be nicer.
This commit is contained in:
Brian Carroll 2021-10-02 15:12:25 +01:00
parent 4aa2452e01
commit ffa6ff0a62
3 changed files with 134 additions and 97 deletions

View file

@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout};
use crate::layout::WasmLayout;
use crate::storage::SymbolStorage;
use crate::{
copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE,
pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE,
PTR_TYPE,
};
@ -165,7 +165,7 @@ impl<'a> WasmBackend<'a> {
symbol: Symbol,
kind: LocalKind,
) -> SymbolStorage {
let local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32);
let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32);
let storage = match kind {
LocalKind::Parameter => {
@ -173,13 +173,13 @@ impl<'a> WasmBackend<'a> {
self.arg_types.push(wasm_layout.value_type());
match wasm_layout {
WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive {
local_id,
local_id: next_local_id,
value_type,
size,
},
WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive {
local_id,
local_id: next_local_id,
value_type: PTR_TYPE,
size: PTR_SIZE,
},
@ -188,7 +188,7 @@ impl<'a> WasmBackend<'a> {
size,
alignment_bytes,
} => SymbolStorage::ParamStackMemory {
local_id,
local_id: next_local_id,
size,
alignment_bytes,
},
@ -199,12 +199,14 @@ impl<'a> WasmBackend<'a> {
match wasm_layout {
WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive {
local_id,
local_id: next_local_id,
value_type,
size,
},
WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id },
WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory {
local_id: next_local_id,
},
WasmLayout::StackMemory {
size,
@ -216,27 +218,12 @@ impl<'a> WasmBackend<'a> {
match self.stack_frame_pointer {
None => {
// This is the first stack-memory variable in the function
// That means we can reuse it as the stack frame pointer,
// and it will get initialised at the start of the function
self.stack_frame_pointer = Some(local_id);
}
Some(frame_ptr_id) => {
// This local points to the base of a struct, at an offset from the stack frame pointer
// Having one local per variable means params and locals work the same way in code gen.
// (alternatively we could use one frame pointer + offset for all struct variables)
self.instructions.extend([
GetLocal(frame_ptr_id.0),
I32Const(offset),
I32Add,
SetLocal(local_id.0),
]);
self.stack_frame_pointer = Some(next_local_id);
}
Some(_) => {}
};
SymbolStorage::VarStackMemory {
local_id,
size,
offset: offset as u32,
alignment_bytes,
@ -262,12 +249,12 @@ impl<'a> WasmBackend<'a> {
fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
let storage = self.get_symbol_storage(sym);
storage.local_id()
storage.local_id(self.stack_frame_pointer)
}
fn load_symbol(&mut self, sym: &Symbol) {
let storage = self.get_symbol_storage(sym);
let index: u32 = storage.local_id().0;
let index: u32 = storage.local_id(self.stack_frame_pointer).0;
self.instructions.push(GetLocal(index));
}
@ -317,7 +304,7 @@ impl<'a> WasmBackend<'a> {
let wasm_layout = WasmLayout::new(layout);
let local_id = self
.insert_local(wasm_layout, *sym, LocalKind::Variable)
.local_id();
.local_id(self.stack_frame_pointer);
self.build_expr(sym, expr, layout)?;
@ -338,19 +325,35 @@ impl<'a> WasmBackend<'a> {
match storage {
VarStackMemory {
local_id,
size,
alignment_bytes,
..
offset,
} => {
let copy = MemoryCopy {
from_ptr: self.stack_frame_pointer.unwrap(),
from_offset: *offset,
to_ptr: LocalId(0),
to_offset: 0,
size: *size,
alignment_bytes: *alignment_bytes,
};
copy.generate(&mut self.instructions);
}
| ParamStackMemory {
ParamStackMemory {
local_id,
size,
alignment_bytes,
} => {
let from = *local_id;
let to = LocalId(0);
copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0);
let copy = MemoryCopy {
from_ptr: *local_id,
from_offset: 0,
to_ptr: LocalId(0),
to_offset: 0,
size: *size,
alignment_bytes: *alignment_bytes,
};
copy.generate(&mut self.instructions);
}
ParamPrimitive { local_id, .. }
@ -423,7 +426,7 @@ impl<'a> WasmBackend<'a> {
let wasm_layout = WasmLayout::new(&parameter.layout);
let local_id = self
.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable)
.local_id();
.local_id(self.stack_frame_pointer);
jp_parameter_local_ids.push(local_id);
}
@ -547,28 +550,27 @@ impl<'a> WasmBackend<'a> {
let storage = self.get_symbol_storage(sym).to_owned();
if let Layout::Struct(field_layouts) = layout {
match storage {
SymbolStorage::VarStackMemory { local_id, size, .. }
| SymbolStorage::ParamStackMemory { local_id, size, .. } => {
if size > 0 {
let mut relative_offset = 0;
for (field, _) in fields.iter().zip(field_layouts.iter()) {
relative_offset += self.copy_symbol_to_pointer_at_offset(
local_id,
relative_offset,
field,
);
}
} else {
return Err(format!("Not supported yet: zero-size struct at {:?}", sym));
}
let (local_id, size) = match storage {
SymbolStorage::VarStackMemory { size, .. } => {
(self.stack_frame_pointer.unwrap(), size)
}
SymbolStorage::ParamStackMemory { local_id, size, .. } => (local_id, size),
_ => {
return Err(format!(
"Cannot create struct {:?} with storage {:?}",
sym, storage
));
}
};
if size > 0 {
let mut relative_offset = 0;
for (field, _) in fields.iter().zip(field_layouts.iter()) {
relative_offset +=
self.copy_symbol_to_pointer_at_offset(local_id, relative_offset, field);
}
} else {
return Err(format!("Not supported yet: zero-size struct at {:?}", sym));
}
} else {
// Struct expression but not Struct layout => single element. Copy it.
@ -585,7 +587,12 @@ impl<'a> WasmBackend<'a> {
from_symbol: &Symbol,
) -> u32 {
let from_storage = self.get_symbol_storage(from_symbol).to_owned();
from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset)
from_storage.copy_to_memory(
&mut self.instructions,
to_ptr,
to_offset,
self.stack_frame_pointer,
)
}
fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) {
@ -594,18 +601,21 @@ impl<'a> WasmBackend<'a> {
if !has_stack_memory {
debug_assert!(from.value_type() == to.value_type());
self.instructions.push(GetLocal(from.local_id().0));
self.instructions.push(SetLocal(to.local_id().0));
self.instructions
.push(GetLocal(from.local_id(self.stack_frame_pointer).0));
self.instructions
.push(SetLocal(to.local_id(self.stack_frame_pointer).0));
} else {
let (size, alignment_bytes) = from.stack_size_and_alignment();
copy_memory(
&mut self.instructions,
from.local_id(),
to.local_id(),
let copy = MemoryCopy {
from_ptr: from.local_id(self.stack_frame_pointer),
to_ptr: to.local_id(self.stack_frame_pointer),
from_offset: from.address_offset().unwrap(),
to_offset: to.address_offset().unwrap(),
size,
alignment_bytes,
0,
);
};
copy.generate(&mut self.instructions);
}
}

View file

@ -121,36 +121,40 @@ fn encode_alignment(bytes: u32) -> u32 {
}
}
fn copy_memory(
instructions: &mut Vec<Instruction>,
pub struct MemoryCopy {
from_ptr: LocalId,
from_offset: u32,
to_ptr: LocalId,
to_offset: u32,
size: u32,
alignment_bytes: u32,
offset: u32,
) {
let alignment_flag = encode_alignment(alignment_bytes);
let mut current_offset = offset;
while size - current_offset >= 8 {
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(from_ptr.0));
instructions.push(I64Load(alignment_flag, current_offset));
instructions.push(I64Store(alignment_flag, current_offset));
current_offset += 8;
}
if size - current_offset >= 4 {
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(from_ptr.0));
instructions.push(I32Load(alignment_flag, current_offset));
instructions.push(I32Store(alignment_flag, current_offset));
current_offset += 4;
impl MemoryCopy {
pub fn generate(&self, instructions: &mut Vec<Instruction>) {
let alignment_flag = encode_alignment(self.alignment_bytes);
let mut i = 0;
while self.size - i >= 8 {
instructions.push(GetLocal(self.to_ptr.0));
instructions.push(GetLocal(self.from_ptr.0));
instructions.push(I64Load(alignment_flag, i + self.from_offset));
instructions.push(I64Store(alignment_flag, i + self.to_offset));
i += 8;
}
if self.size - i >= 4 {
instructions.push(GetLocal(self.to_ptr.0));
instructions.push(GetLocal(self.from_ptr.0));
instructions.push(I32Load(alignment_flag, i + self.from_offset));
instructions.push(I32Store(alignment_flag, i + self.to_offset));
i += 4;
}
while self.size - i > 0 {
instructions.push(GetLocal(self.to_ptr.0));
instructions.push(GetLocal(self.from_ptr.0));
instructions.push(I32Load8U(alignment_flag, i + self.from_offset));
instructions.push(I32Store8(alignment_flag, i + self.to_offset));
i += 1;
}
while size - current_offset > 0 {
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(from_ptr.0));
instructions.push(I32Load8U(alignment_flag, current_offset));
instructions.push(I32Store8(alignment_flag, current_offset));
current_offset += 1;
}
}

View file

@ -1,4 +1,4 @@
use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8};
use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8};
use parity_wasm::elements::{Instruction, Instruction::*, ValueType};
#[derive(Debug, Clone)]
@ -14,7 +14,6 @@ pub enum SymbolStorage {
size: u32,
},
VarStackMemory {
local_id: LocalId,
size: u32,
offset: u32,
alignment_bytes: u32,
@ -30,12 +29,12 @@ pub enum SymbolStorage {
}
impl SymbolStorage {
pub fn local_id(&self) -> LocalId {
pub fn local_id(&self, stack_frame_pointer: Option<LocalId>) -> LocalId {
match self {
Self::ParamPrimitive { local_id, .. } => *local_id,
Self::ParamStackMemory { local_id, .. } => *local_id,
Self::VarPrimitive { local_id, .. } => *local_id,
Self::VarStackMemory { local_id, .. } => *local_id,
Self::VarStackMemory { .. } => stack_frame_pointer.unwrap(),
Self::VarHeapMemory { local_id, .. } => *local_id,
}
}
@ -60,6 +59,16 @@ impl SymbolStorage {
}
}
pub fn address_offset(&self) -> Option<u32> {
match self {
Self::ParamStackMemory { .. } => Some(0),
Self::VarStackMemory { offset, .. } => Some(*offset),
Self::ParamPrimitive { .. } => None,
Self::VarPrimitive { .. } => None,
Self::VarHeapMemory { .. } => None,
}
}
pub fn stack_size_and_alignment(&self) -> (u32, u32) {
match self {
Self::VarStackMemory {
@ -82,6 +91,7 @@ impl SymbolStorage {
instructions: &mut Vec<Instruction>,
to_pointer: LocalId,
to_offset: u32,
stack_frame_pointer: Option<LocalId>,
) -> u32 {
match self {
Self::ParamPrimitive {
@ -117,21 +127,34 @@ impl SymbolStorage {
local_id,
size,
alignment_bytes,
} => {
let copy = MemoryCopy {
from_ptr: *local_id,
from_offset: 0,
to_ptr: to_pointer,
to_offset,
size: *size,
alignment_bytes: *alignment_bytes,
};
copy.generate(instructions);
*size
}
| Self::VarStackMemory {
local_id,
Self::VarStackMemory {
size,
alignment_bytes,
offset,
..
} => {
copy_memory(
instructions,
*local_id,
to_pointer,
*size,
*alignment_bytes,
let copy = MemoryCopy {
from_ptr: stack_frame_pointer.unwrap(),
from_offset: *offset,
to_ptr: to_pointer,
to_offset,
);
size: *size,
alignment_bytes: *alignment_bytes,
};
copy.generate(instructions);
*size
}