mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
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:
parent
4aa2452e01
commit
ffa6ff0a62
3 changed files with 134 additions and 97 deletions
|
@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout};
|
||||||
use crate::layout::WasmLayout;
|
use crate::layout::WasmLayout;
|
||||||
use crate::storage::SymbolStorage;
|
use crate::storage::SymbolStorage;
|
||||||
use crate::{
|
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,
|
PTR_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
kind: LocalKind,
|
kind: LocalKind,
|
||||||
) -> SymbolStorage {
|
) -> 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 {
|
let storage = match kind {
|
||||||
LocalKind::Parameter => {
|
LocalKind::Parameter => {
|
||||||
|
@ -173,13 +173,13 @@ impl<'a> WasmBackend<'a> {
|
||||||
self.arg_types.push(wasm_layout.value_type());
|
self.arg_types.push(wasm_layout.value_type());
|
||||||
match wasm_layout {
|
match wasm_layout {
|
||||||
WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive {
|
WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive {
|
||||||
local_id,
|
local_id: next_local_id,
|
||||||
value_type,
|
value_type,
|
||||||
size,
|
size,
|
||||||
},
|
},
|
||||||
|
|
||||||
WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive {
|
WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive {
|
||||||
local_id,
|
local_id: next_local_id,
|
||||||
value_type: PTR_TYPE,
|
value_type: PTR_TYPE,
|
||||||
size: PTR_SIZE,
|
size: PTR_SIZE,
|
||||||
},
|
},
|
||||||
|
@ -188,7 +188,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
} => SymbolStorage::ParamStackMemory {
|
} => SymbolStorage::ParamStackMemory {
|
||||||
local_id,
|
local_id: next_local_id,
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
},
|
},
|
||||||
|
@ -199,12 +199,14 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
match wasm_layout {
|
match wasm_layout {
|
||||||
WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive {
|
WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive {
|
||||||
local_id,
|
local_id: next_local_id,
|
||||||
value_type,
|
value_type,
|
||||||
size,
|
size,
|
||||||
},
|
},
|
||||||
|
|
||||||
WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id },
|
WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory {
|
||||||
|
local_id: next_local_id,
|
||||||
|
},
|
||||||
|
|
||||||
WasmLayout::StackMemory {
|
WasmLayout::StackMemory {
|
||||||
size,
|
size,
|
||||||
|
@ -216,27 +218,12 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
match self.stack_frame_pointer {
|
match self.stack_frame_pointer {
|
||||||
None => {
|
None => {
|
||||||
// This is the first stack-memory variable in the function
|
self.stack_frame_pointer = Some(next_local_id);
|
||||||
// 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),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
Some(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
SymbolStorage::VarStackMemory {
|
SymbolStorage::VarStackMemory {
|
||||||
local_id,
|
|
||||||
size,
|
size,
|
||||||
offset: offset as u32,
|
offset: offset as u32,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
|
@ -262,12 +249,12 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
|
fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
|
||||||
let storage = self.get_symbol_storage(sym);
|
let storage = self.get_symbol_storage(sym);
|
||||||
storage.local_id()
|
storage.local_id(self.stack_frame_pointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_symbol(&mut self, sym: &Symbol) {
|
fn load_symbol(&mut self, sym: &Symbol) {
|
||||||
let storage = self.get_symbol_storage(sym);
|
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));
|
self.instructions.push(GetLocal(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +304,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
let wasm_layout = WasmLayout::new(layout);
|
let wasm_layout = WasmLayout::new(layout);
|
||||||
let local_id = self
|
let local_id = self
|
||||||
.insert_local(wasm_layout, *sym, LocalKind::Variable)
|
.insert_local(wasm_layout, *sym, LocalKind::Variable)
|
||||||
.local_id();
|
.local_id(self.stack_frame_pointer);
|
||||||
|
|
||||||
self.build_expr(sym, expr, layout)?;
|
self.build_expr(sym, expr, layout)?;
|
||||||
|
|
||||||
|
@ -338,19 +325,35 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
match storage {
|
match storage {
|
||||||
VarStackMemory {
|
VarStackMemory {
|
||||||
local_id,
|
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
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,
|
local_id,
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
} => {
|
} => {
|
||||||
let from = *local_id;
|
let copy = MemoryCopy {
|
||||||
let to = LocalId(0);
|
from_ptr: *local_id,
|
||||||
copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0);
|
from_offset: 0,
|
||||||
|
to_ptr: LocalId(0),
|
||||||
|
to_offset: 0,
|
||||||
|
size: *size,
|
||||||
|
alignment_bytes: *alignment_bytes,
|
||||||
|
};
|
||||||
|
copy.generate(&mut self.instructions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ParamPrimitive { local_id, .. }
|
ParamPrimitive { local_id, .. }
|
||||||
|
@ -423,7 +426,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
let wasm_layout = WasmLayout::new(¶meter.layout);
|
let wasm_layout = WasmLayout::new(¶meter.layout);
|
||||||
let local_id = self
|
let local_id = self
|
||||||
.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable)
|
.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable)
|
||||||
.local_id();
|
.local_id(self.stack_frame_pointer);
|
||||||
|
|
||||||
jp_parameter_local_ids.push(local_id);
|
jp_parameter_local_ids.push(local_id);
|
||||||
}
|
}
|
||||||
|
@ -547,28 +550,27 @@ impl<'a> WasmBackend<'a> {
|
||||||
let storage = self.get_symbol_storage(sym).to_owned();
|
let storage = self.get_symbol_storage(sym).to_owned();
|
||||||
|
|
||||||
if let Layout::Struct(field_layouts) = layout {
|
if let Layout::Struct(field_layouts) = layout {
|
||||||
match storage {
|
let (local_id, size) = match storage {
|
||||||
SymbolStorage::VarStackMemory { local_id, size, .. }
|
SymbolStorage::VarStackMemory { size, .. } => {
|
||||||
| SymbolStorage::ParamStackMemory { local_id, size, .. } => {
|
(self.stack_frame_pointer.unwrap(), 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
SymbolStorage::ParamStackMemory { local_id, size, .. } => (local_id, size),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Cannot create struct {:?} with storage {:?}",
|
"Cannot create struct {:?} with storage {:?}",
|
||||||
sym, 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 {
|
} else {
|
||||||
// Struct expression but not Struct layout => single element. Copy it.
|
// Struct expression but not Struct layout => single element. Copy it.
|
||||||
|
@ -585,7 +587,12 @@ impl<'a> WasmBackend<'a> {
|
||||||
from_symbol: &Symbol,
|
from_symbol: &Symbol,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
let from_storage = self.get_symbol_storage(from_symbol).to_owned();
|
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) {
|
fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) {
|
||||||
|
@ -594,18 +601,21 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
if !has_stack_memory {
|
if !has_stack_memory {
|
||||||
debug_assert!(from.value_type() == to.value_type());
|
debug_assert!(from.value_type() == to.value_type());
|
||||||
self.instructions.push(GetLocal(from.local_id().0));
|
self.instructions
|
||||||
self.instructions.push(SetLocal(to.local_id().0));
|
.push(GetLocal(from.local_id(self.stack_frame_pointer).0));
|
||||||
|
self.instructions
|
||||||
|
.push(SetLocal(to.local_id(self.stack_frame_pointer).0));
|
||||||
} else {
|
} else {
|
||||||
let (size, alignment_bytes) = from.stack_size_and_alignment();
|
let (size, alignment_bytes) = from.stack_size_and_alignment();
|
||||||
copy_memory(
|
let copy = MemoryCopy {
|
||||||
&mut self.instructions,
|
from_ptr: from.local_id(self.stack_frame_pointer),
|
||||||
from.local_id(),
|
to_ptr: to.local_id(self.stack_frame_pointer),
|
||||||
to.local_id(),
|
from_offset: from.address_offset().unwrap(),
|
||||||
|
to_offset: to.address_offset().unwrap(),
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
0,
|
};
|
||||||
);
|
copy.generate(&mut self.instructions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,36 +121,40 @@ fn encode_alignment(bytes: u32) -> u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_memory(
|
pub struct MemoryCopy {
|
||||||
instructions: &mut Vec<Instruction>,
|
|
||||||
from_ptr: LocalId,
|
from_ptr: LocalId,
|
||||||
|
from_offset: u32,
|
||||||
to_ptr: LocalId,
|
to_ptr: LocalId,
|
||||||
|
to_offset: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
alignment_bytes: u32,
|
alignment_bytes: u32,
|
||||||
offset: u32,
|
}
|
||||||
) {
|
|
||||||
let alignment_flag = encode_alignment(alignment_bytes);
|
impl MemoryCopy {
|
||||||
let mut current_offset = offset;
|
pub fn generate(&self, instructions: &mut Vec<Instruction>) {
|
||||||
while size - current_offset >= 8 {
|
let alignment_flag = encode_alignment(self.alignment_bytes);
|
||||||
instructions.push(GetLocal(to_ptr.0));
|
let mut i = 0;
|
||||||
instructions.push(GetLocal(from_ptr.0));
|
while self.size - i >= 8 {
|
||||||
instructions.push(I64Load(alignment_flag, current_offset));
|
instructions.push(GetLocal(self.to_ptr.0));
|
||||||
instructions.push(I64Store(alignment_flag, current_offset));
|
instructions.push(GetLocal(self.from_ptr.0));
|
||||||
current_offset += 8;
|
instructions.push(I64Load(alignment_flag, i + self.from_offset));
|
||||||
}
|
instructions.push(I64Store(alignment_flag, i + self.to_offset));
|
||||||
if size - current_offset >= 4 {
|
i += 8;
|
||||||
instructions.push(GetLocal(to_ptr.0));
|
}
|
||||||
instructions.push(GetLocal(from_ptr.0));
|
if self.size - i >= 4 {
|
||||||
instructions.push(I32Load(alignment_flag, current_offset));
|
instructions.push(GetLocal(self.to_ptr.0));
|
||||||
instructions.push(I32Store(alignment_flag, current_offset));
|
instructions.push(GetLocal(self.from_ptr.0));
|
||||||
current_offset += 4;
|
instructions.push(I32Load(alignment_flag, i + self.from_offset));
|
||||||
}
|
instructions.push(I32Store(alignment_flag, i + self.to_offset));
|
||||||
while size - current_offset > 0 {
|
i += 4;
|
||||||
instructions.push(GetLocal(to_ptr.0));
|
}
|
||||||
instructions.push(GetLocal(from_ptr.0));
|
while self.size - i > 0 {
|
||||||
instructions.push(I32Load8U(alignment_flag, current_offset));
|
instructions.push(GetLocal(self.to_ptr.0));
|
||||||
instructions.push(I32Store8(alignment_flag, current_offset));
|
instructions.push(GetLocal(self.from_ptr.0));
|
||||||
current_offset += 1;
|
instructions.push(I32Load8U(alignment_flag, i + self.from_offset));
|
||||||
|
instructions.push(I32Store8(alignment_flag, i + self.to_offset));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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};
|
use parity_wasm::elements::{Instruction, Instruction::*, ValueType};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -14,7 +14,6 @@ pub enum SymbolStorage {
|
||||||
size: u32,
|
size: u32,
|
||||||
},
|
},
|
||||||
VarStackMemory {
|
VarStackMemory {
|
||||||
local_id: LocalId,
|
|
||||||
size: u32,
|
size: u32,
|
||||||
offset: u32,
|
offset: u32,
|
||||||
alignment_bytes: u32,
|
alignment_bytes: u32,
|
||||||
|
@ -30,12 +29,12 @@ pub enum SymbolStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SymbolStorage {
|
impl SymbolStorage {
|
||||||
pub fn local_id(&self) -> LocalId {
|
pub fn local_id(&self, stack_frame_pointer: Option<LocalId>) -> LocalId {
|
||||||
match self {
|
match self {
|
||||||
Self::ParamPrimitive { local_id, .. } => *local_id,
|
Self::ParamPrimitive { local_id, .. } => *local_id,
|
||||||
Self::ParamStackMemory { local_id, .. } => *local_id,
|
Self::ParamStackMemory { local_id, .. } => *local_id,
|
||||||
Self::VarPrimitive { 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,
|
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) {
|
pub fn stack_size_and_alignment(&self) -> (u32, u32) {
|
||||||
match self {
|
match self {
|
||||||
Self::VarStackMemory {
|
Self::VarStackMemory {
|
||||||
|
@ -82,6 +91,7 @@ impl SymbolStorage {
|
||||||
instructions: &mut Vec<Instruction>,
|
instructions: &mut Vec<Instruction>,
|
||||||
to_pointer: LocalId,
|
to_pointer: LocalId,
|
||||||
to_offset: u32,
|
to_offset: u32,
|
||||||
|
stack_frame_pointer: Option<LocalId>,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::ParamPrimitive {
|
Self::ParamPrimitive {
|
||||||
|
@ -117,21 +127,34 @@ impl SymbolStorage {
|
||||||
local_id,
|
local_id,
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
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,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
|
offset,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
copy_memory(
|
let copy = MemoryCopy {
|
||||||
instructions,
|
from_ptr: stack_frame_pointer.unwrap(),
|
||||||
*local_id,
|
from_offset: *offset,
|
||||||
to_pointer,
|
to_ptr: to_pointer,
|
||||||
*size,
|
|
||||||
*alignment_bytes,
|
|
||||||
to_offset,
|
to_offset,
|
||||||
);
|
size: *size,
|
||||||
|
alignment_bytes: *alignment_bytes,
|
||||||
|
};
|
||||||
|
copy.generate(instructions);
|
||||||
*size
|
*size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue