use std::ops::Range;
use anyhow::{Result, bail, ensure};
use wasm_encoder::{Encode, Instruction};
use wasmparser::{Operator, RelocationType};
use super::{ModifyContext, StoreType};
use crate::emit::modify::{
CustomModify, RelocationContext, SymbolOffset, SymbolOp,
relocation::{DataSymbolTag, FunctionIndexTag},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstantExtractionEntry {
pub(super) entry: wasmparser::RelocationEntry,
}
impl ConstantExtractionEntry {
pub fn replace_const_get_with_global_get<V>(
&self,
symbol_offset: SymbolOp<V>,
ctx: ModifyContext<'_, '_>,
) -> Result<()>
where
V: TryInto<usize>, {
ensure!(
matches!(ctx.instruction, Operator::I32Const { .. }),
"Unsupported relocation operand"
);
let SymbolOp::GotBased {
got: got_global_index,
value: got_offset,
} = symbol_offset
else {
bail!(
"replace_const_get_with_global_get called with static offset symbol {:?}",
self.entry
);
};
let got_offset = got_offset
.try_into()
.map_err(|_| anyhow::anyhow!("Offset is too large to fit in usize"))?;
let result_ix = Instruction::GlobalGet(got_global_index.as_raw_index() as u32);
log::trace!(
"Replacing func[{name}:{range:?}] {src_ix:?} with {result_ix:?}, addend {addend}",
addend = self.entry.addend,
range = self.range(),
name = ctx.function_name,
src_ix = ctx.instruction
);
result_ix.encode(ctx.writer);
let offset = got_offset as i32;
Instruction::I32Const(offset).encode(ctx.writer);
Instruction::I32Add.encode(ctx.writer);
Ok(())
}
pub fn replace_memory_offset_with_global_get(
&self,
symbol_offset: SymbolOffset,
ctx: ModifyContext<'_, '_>,
) -> Result<()> {
let SymbolOffset::GotBased {
got: got_global_index,
value: got_offset,
} = symbol_offset
else {
bail!(
"replace_memory_offset_with_global_get called with static offset symbol {:?}",
self.entry
);
};
let offset = got_offset as u64;
let fix_offset = |memarg: wasmparser::MemArg| wasm_encoder::MemArg {
align: memarg.align as u32,
memory_index: memarg.memory,
offset,
};
let (store, ix) = match ctx.instruction {
Operator::F32Load { memarg } => (None, Instruction::F32Load(fix_offset(memarg))),
Operator::F64Load { memarg } => (None, Instruction::F64Load(fix_offset(memarg))),
Operator::I32Load { memarg } => (None, Instruction::I32Load(fix_offset(memarg))),
Operator::I64Load { memarg } => (None, Instruction::I64Load(fix_offset(memarg))),
Operator::I32Load8U { memarg } => (None, Instruction::I32Load8U(fix_offset(memarg))),
Operator::I32Load8S { memarg } => (None, Instruction::I32Load8S(fix_offset(memarg))),
Operator::I32Load16U { memarg } => (None, Instruction::I32Load16U(fix_offset(memarg))),
Operator::I32Load16S { memarg } => (None, Instruction::I32Load16S(fix_offset(memarg))),
Operator::I64Load8U { memarg } => (None, Instruction::I64Load8U(fix_offset(memarg))),
Operator::I64Load8S { memarg } => (None, Instruction::I64Load8S(fix_offset(memarg))),
Operator::I64Load16U { memarg } => (None, Instruction::I64Load16U(fix_offset(memarg))),
Operator::I64Load16S { memarg } => (None, Instruction::I64Load16S(fix_offset(memarg))),
Operator::I64Load32U { memarg } => (None, Instruction::I64Load32U(fix_offset(memarg))),
Operator::I64Load32S { memarg } => (None, Instruction::I64Load32S(fix_offset(memarg))),
Operator::I32Store { memarg } => (
Some(StoreType::I32),
Instruction::I32Store(fix_offset(memarg)),
),
Operator::I64Store { memarg } => (
Some(StoreType::I64),
Instruction::I64Store(fix_offset(memarg)),
),
Operator::I32Store8 { memarg } => (
Some(StoreType::I32),
Instruction::I32Store8(fix_offset(memarg)),
),
Operator::I32Store16 { memarg } => (
Some(StoreType::I32),
Instruction::I32Store16(fix_offset(memarg)),
),
Operator::I64Store8 { memarg } => (
Some(StoreType::I64),
Instruction::I64Store8(fix_offset(memarg)),
),
Operator::I64Store16 { memarg } => (
Some(StoreType::I64),
Instruction::I64Store16(fix_offset(memarg)),
),
Operator::I64Store32 { memarg } => (
Some(StoreType::I64),
Instruction::I64Store32(fix_offset(memarg)),
),
_ => {
bail!(
"Unsupported relocation instruction: {instr:?}",
instr = ctx.instruction
);
}
};
log::trace!(
"Replacing func[{name}:{range:?}] {ix:?} with {store:?} ix",
name = ctx.function_name,
range = self.range(),
);
let save_value = store.as_ref().map(|store_type| {
Instruction::GlobalSet(ctx.global_tmps.get(store_type).unwrap().as_raw_index() as u32)
});
let restore_value = store.as_ref().map(|store_type| {
Instruction::GlobalGet(ctx.global_tmps.get(store_type).unwrap().as_raw_index() as u32)
});
if let Some(v) = save_value {
v.encode(ctx.writer)
} Instruction::GlobalGet(got_global_index.as_raw_index() as u32).encode(ctx.writer);
Instruction::I32Add.encode(ctx.writer); if let Some(v) = restore_value {
v.encode(ctx.writer)
} ix.encode(ctx.writer);
Ok(())
}
fn check_whitelisted_code_relocation(entry: &wasmparser::RelocationEntry) -> Result<()> {
match entry.ty {
RelocationType::MemoryAddrLeb64
| RelocationType::MemoryAddrSleb64
| RelocationType::MemoryAddrI64
| RelocationType::MemoryAddrRelSleb64
| RelocationType::MemoryAddrTlsSleb64
| RelocationType::TableIndexSleb64
| RelocationType::TableIndexI64
| RelocationType::FunctionOffsetI64
| RelocationType::TableIndexRelSleb64 => {
bail!("U64 memory pointers is currently not supported")
}
RelocationType::MemoryAddrTlsSleb
| RelocationType::MemoryAddrRelSleb
| RelocationType::MemoryAddrLocrelI32 => {
bail!("Relocation memory pointers is currently not supported")
}
RelocationType::FunctionOffsetI32
| RelocationType::SectionOffsetI32
| RelocationType::TableIndexRelSleb => {
bail!("Unsupported relocation type {entry:?}");
}
_ => {}
}
Ok(())
}
}
impl CustomModify for ConstantExtractionEntry {
type Context<'any, 'src>
= ModifyContext<'any, 'src>
where
'src: 'any;
fn try_from_entry(
entry: &wasmparser::RelocationEntry,
context: &RelocationContext,
) -> Result<Option<Self>> {
Self::check_whitelisted_code_relocation(entry)?;
Ok(match entry.ty {
RelocationType::MemoryAddrLeb
| RelocationType::MemoryAddrSleb
| RelocationType::TableIndexSleb if context.dyn_relocate =>{
Some(Self {
entry: *entry,
})
}
RelocationType::TableIndexI32 | RelocationType::MemoryAddrI32
=> {
panic!("BUG: Relocation type {entry:?} not supported")
}
_ => return Ok(None),
})
}
fn range(&self) -> Range<usize> {
self.entry.relocation_range()
}
fn try_apply(&self, ctx: ModifyContext<'_, '_>) -> Result<()> {
match self.entry.ty {
RelocationType::MemoryAddrLeb => {
let symbol_offset = ctx
.relocation_state
.get_entry_symbol_op::<DataSymbolTag>(&self.entry)?;
self.replace_memory_offset_with_global_get(symbol_offset, ctx)?
}
RelocationType::MemoryAddrSleb => {
let symbol_offset = ctx
.relocation_state
.get_entry_symbol_op::<DataSymbolTag>(&self.entry)?;
self.replace_const_get_with_global_get(symbol_offset, ctx)?
}
RelocationType::TableIndexSleb => {
let symbol_offset = ctx
.relocation_state
.get_entry_symbol_op::<FunctionIndexTag>(&self.entry)?;
self.replace_const_get_with_global_get(symbol_offset, ctx)?
}
_ => {
bail!("Unsupported relocation type")
}
};
Ok(())
}
}