use crate::{CompiledFuncEnv, CompiledFunction, RelocationTarget};
use anyhow::Result;
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::LibCall;
use cranelift_codegen::isa::unwind::{systemv, UnwindInfo};
use cranelift_codegen::TextSectionBuilder;
use cranelift_control::ControlPlane;
use gimli::write::{Address, EhFrame, EndianVec, FrameTable, Writer};
use gimli::RunTimeEndian;
use object::write::{Object, SectionId, StandardSegment, Symbol, SymbolId, SymbolSection};
use object::{Architecture, SectionKind, SymbolFlags, SymbolKind, SymbolScope};
use std::collections::HashMap;
use std::ops::Range;
use wasmtime_environ::{Compiler, FuncIndex};
const TEXT_SECTION_NAME: &[u8] = b".text";
pub struct ModuleTextBuilder<'a> {
compiler: &'a dyn Compiler,
obj: &'a mut Object<'static>,
text_section: SectionId,
unwind_info: UnwindInfoBuilder<'a>,
text: Box<dyn TextSectionBuilder>,
libcall_symbols: HashMap<LibCall, SymbolId>,
ctrl_plane: ControlPlane,
}
impl<'a> ModuleTextBuilder<'a> {
pub fn new(
obj: &'a mut Object<'static>,
compiler: &'a dyn Compiler,
text: Box<dyn TextSectionBuilder>,
) -> Self {
let text_section = obj.add_section(
obj.segment_name(StandardSegment::Text).to_vec(),
TEXT_SECTION_NAME.to_vec(),
SectionKind::Text,
);
Self {
compiler,
obj,
text_section,
unwind_info: Default::default(),
text,
libcall_symbols: HashMap::default(),
ctrl_plane: ControlPlane::default(),
}
}
pub fn append_func(
&mut self,
name: &str,
compiled_func: &'a CompiledFunction<impl CompiledFuncEnv>,
resolve_reloc_target: impl Fn(FuncIndex) -> usize,
) -> (SymbolId, Range<u64>) {
let body = compiled_func.buffer.data();
let alignment = compiled_func.alignment;
let body_len = body.len() as u64;
let off = self
.text
.append(true, &body, alignment, &mut self.ctrl_plane);
let symbol_id = self.obj.add_symbol(Symbol {
name: name.as_bytes().to_vec(),
value: off,
size: body_len,
kind: SymbolKind::Text,
scope: SymbolScope::Compilation,
weak: false,
section: SymbolSection::Section(self.text_section),
flags: SymbolFlags::None,
});
if let Some(info) = compiled_func.unwind_info() {
self.unwind_info.push(off, body_len, info);
}
for r in compiled_func.relocations() {
match r.reloc_target {
RelocationTarget::UserFunc(index) => {
let target = resolve_reloc_target(index);
if self
.text
.resolve_reloc(off + u64::from(r.offset), r.reloc, r.addend, target)
{
continue;
}
panic!(
"unresolved relocation could not be processed against \
{index:?}: {r:?}"
);
}
RelocationTarget::LibCall(call) => {
let symbol = *self.libcall_symbols.entry(call).or_insert_with(|| {
self.obj.add_symbol(Symbol {
name: libcall_name(call).as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
})
});
let (encoding, kind, size) = match r.reloc {
Reloc::Abs8 => (
object::RelocationEncoding::Generic,
object::RelocationKind::Absolute,
8,
),
other => unimplemented!("unimplemented relocation kind {other:?}"),
};
self.obj
.add_relocation(
self.text_section,
object::write::Relocation {
symbol,
size,
kind,
encoding,
offset: off + u64::from(r.offset),
addend: r.addend,
},
)
.unwrap();
}
};
}
(symbol_id, off..off + body_len)
}
pub fn force_veneers(&mut self) {
self.text.force_veneers();
}
pub fn append_padding(&mut self, padding: usize) {
if padding == 0 {
return;
}
self.text
.append(false, &vec![0; padding], 1, &mut self.ctrl_plane);
}
pub fn finish(mut self) {
let text = self.text.finish(&mut self.ctrl_plane);
self.obj
.section_mut(self.text_section)
.set_data(text, self.compiler.page_size_align());
self.unwind_info
.append_section(self.compiler, self.obj, self.text_section);
}
}
#[derive(Default)]
struct UnwindInfoBuilder<'a> {
windows_xdata: Vec<u8>,
windows_pdata: Vec<RUNTIME_FUNCTION>,
systemv_unwind_info: Vec<(u64, &'a systemv::UnwindInfo)>,
}
#[allow(non_camel_case_types)]
struct RUNTIME_FUNCTION {
begin: u32,
end: u32,
unwind_address: u32,
}
impl<'a> UnwindInfoBuilder<'a> {
fn push(&mut self, function_offset: u64, function_len: u64, info: &'a UnwindInfo) {
match info {
UnwindInfo::WindowsX64(info) => {
let unwind_size = info.emit_size();
let mut unwind_info = vec![0; unwind_size];
info.emit(&mut unwind_info);
while self.windows_xdata.len() % 4 != 0 {
self.windows_xdata.push(0x00);
}
let unwind_address = self.windows_xdata.len();
self.windows_xdata.extend_from_slice(&unwind_info);
self.windows_pdata.push(RUNTIME_FUNCTION {
begin: u32::try_from(function_offset).unwrap(),
end: u32::try_from(function_offset + function_len).unwrap(),
unwind_address: u32::try_from(unwind_address).unwrap(),
});
}
UnwindInfo::SystemV(info) => {
self.systemv_unwind_info.push((function_offset, info));
}
_ => panic!("some unwind info isn't handled here"),
}
}
fn append_section(
&self,
compiler: &dyn Compiler,
obj: &mut Object<'_>,
text_section: SectionId,
) {
let text_section_size =
obj.append_section_data(text_section, &[], compiler.page_size_align());
if self.windows_xdata.len() > 0 {
assert!(self.systemv_unwind_info.len() == 0);
let segment = obj.segment_name(StandardSegment::Data).to_vec();
let xdata_id = obj.add_section(segment, b".xdata".to_vec(), SectionKind::ReadOnlyData);
let segment = obj.segment_name(StandardSegment::Data).to_vec();
let pdata_id = obj.add_section(segment, b".pdata".to_vec(), SectionKind::ReadOnlyData);
self.write_windows_unwind_info(obj, xdata_id, pdata_id, text_section_size);
}
if self.systemv_unwind_info.len() > 0 {
let segment = obj.segment_name(StandardSegment::Data).to_vec();
let section_id =
obj.add_section(segment, b".eh_frame".to_vec(), SectionKind::ReadOnlyData);
self.write_systemv_unwind_info(compiler, obj, section_id, text_section_size)
}
}
fn write_windows_unwind_info(
&self,
obj: &mut Object<'_>,
xdata_id: SectionId,
pdata_id: SectionId,
text_section_size: u64,
) {
assert_eq!(obj.architecture(), Architecture::X86_64);
obj.append_section_data(xdata_id, &self.windows_xdata, 4);
let mut pdata = Vec::with_capacity(self.windows_pdata.len() * 3 * 4);
for info in self.windows_pdata.iter() {
pdata.extend_from_slice(&info.begin.to_le_bytes());
pdata.extend_from_slice(&info.end.to_le_bytes());
let address = text_section_size + u64::from(info.unwind_address);
let address = u32::try_from(address).unwrap();
pdata.extend_from_slice(&address.to_le_bytes());
}
obj.append_section_data(pdata_id, &pdata, 4);
}
fn write_systemv_unwind_info(
&self,
compiler: &dyn Compiler,
obj: &mut Object<'_>,
section_id: SectionId,
text_section_size: u64,
) {
let mut cie = compiler
.create_systemv_cie()
.expect("must be able to create a CIE for system-v unwind info");
let mut table = FrameTable::default();
cie.fde_address_encoding = gimli::constants::DW_EH_PE_pcrel;
let cie_id = table.add_cie(cie);
for (text_section_off, unwind_info) in self.systemv_unwind_info.iter() {
let backwards_off = text_section_size - text_section_off;
let actual_offset = -i64::try_from(backwards_off).unwrap();
let fde = unwind_info.to_fde(Address::Constant(actual_offset as u64));
table.add_fde(cie_id, fde);
}
let endian = match compiler.triple().endianness().unwrap() {
target_lexicon::Endianness::Little => RunTimeEndian::Little,
target_lexicon::Endianness::Big => RunTimeEndian::Big,
};
let mut eh_frame = EhFrame(MyVec(EndianVec::new(endian)));
table.write_eh_frame(&mut eh_frame).unwrap();
let mut endian_vec = (eh_frame.0).0;
endian_vec.write_u32(0).unwrap();
obj.append_section_data(section_id, endian_vec.slice(), 1);
use gimli::constants;
use gimli::write::Error;
struct MyVec(EndianVec<RunTimeEndian>);
impl Writer for MyVec {
type Endian = RunTimeEndian;
fn endian(&self) -> RunTimeEndian {
self.0.endian()
}
fn len(&self) -> usize {
self.0.len()
}
fn write(&mut self, buf: &[u8]) -> Result<(), Error> {
self.0.write(buf)
}
fn write_at(&mut self, pos: usize, buf: &[u8]) -> Result<(), Error> {
self.0.write_at(pos, buf)
}
fn write_eh_pointer(
&mut self,
address: Address,
eh_pe: constants::DwEhPe,
size: u8,
) -> Result<(), Error> {
let val = match address {
Address::Constant(val) => val,
Address::Symbol { .. } => unreachable!(),
};
assert_eq!(eh_pe.application(), constants::DW_EH_PE_pcrel);
let offset = self.len() as u64;
let val = val.wrapping_sub(offset);
self.write_eh_pointer_data(val, eh_pe.format(), size)
}
}
}
}
fn libcall_name(call: LibCall) -> &'static str {
use wasmtime_environ::obj::LibCall as LC;
let other = match call {
LibCall::FloorF32 => LC::FloorF32,
LibCall::FloorF64 => LC::FloorF64,
LibCall::NearestF32 => LC::NearestF32,
LibCall::NearestF64 => LC::NearestF64,
LibCall::CeilF32 => LC::CeilF32,
LibCall::CeilF64 => LC::CeilF64,
LibCall::TruncF32 => LC::TruncF32,
LibCall::TruncF64 => LC::TruncF64,
LibCall::FmaF32 => LC::FmaF32,
LibCall::FmaF64 => LC::FmaF64,
LibCall::X86Pshufb => LC::X86Pshufb,
_ => panic!("unknown libcall to give a name to: {call:?}"),
};
other.symbol()
}