1
//! Shared utility layer for all architecture-specific compilers
2

            
3
use crate::compiler::Executable;
4
use crate::CompilerError;
5
use arrayvec::ArrayVec;
6
use dynasmrt::mmap::MutableBuffer;
7
use dynasmrt::{
8
    components::PatchLoc, relocations::Relocation, AssemblyOffset, DynamicLabel, DynasmApi,
9
    DynasmLabelApi,
10
};
11
use std::marker::PhantomData;
12

            
13
pub(crate) use dynasmrt::mmap::ExecutableBuffer;
14

            
15
/// Our own simple replacement for [`dynasmrt::Assembler`]
16
///
17
/// The default assembler in [`dynasmrt`] has a ton of features we don't need,
18
/// but more importantly it will panic if it can't make its memory region
19
/// executable. This is a no-go for us, since there is a fallback available.
20
///
21
/// Our needs are simple: a single un-named label, no relocations, and
22
/// no modification after finalization. However, we do need to handle runtime
23
/// mmap errors thoroughly.
24
#[derive(Debug, Clone)]
25
pub(crate) struct Assembler<R: Relocation, const S: usize> {
26
    /// Temporary fixed capacity buffer for assembling code
27
    buffer: ArrayVec<u8, S>,
28
    /// Address of the last "target" label, if any
29
    target: Option<AssemblyOffset>,
30
    /// Relocations are applied immediately and not stored.
31
    phantom: PhantomData<R>,
32
}
33

            
34
impl<R: Relocation, const S: usize> Assembler<R, S> {
35
    /// Return the entry point as an [`AssemblyOffset`].
36
    #[inline(always)]
37
280232862
    pub(crate) fn entry() -> AssemblyOffset {
38
280232862
        AssemblyOffset(0)
39
280232862
    }
40

            
41
    /// Size of the code stored so far, in bytes
42
    #[inline(always)]
43
7141758
    pub(crate) fn len(&self) -> usize {
44
7141758
        self.buffer.len()
45
7141758
    }
46

            
47
    /// Make a new assembler with a temporary buffer but no executable buffer.
48
    #[inline(always)]
49
6954
    pub(crate) fn new() -> Self {
50
6954
        Self {
51
6954
            buffer: ArrayVec::new(),
52
6954
            target: None,
53
6954
            phantom: PhantomData,
54
6954
        }
55
6954
    }
56

            
57
    /// Return a new [`Executable`] with the code that's been written so far.
58
    ///
59
    /// This may fail if we can't allocate some memory, fill it, and mark
60
    /// it as executable. For example, a Linux platform with policy to restrict
61
    /// `mprotect` will show runtime errors at this point.
62
    ///
63
    /// Performance note: Semantically it makes more sense to consume the
64
    /// `Assembler` instance here, passing it by value. This can result in a
65
    /// memcpy that doesn't optimize out, which is a dramatic increase to
66
    /// the memory bandwidth required in compilation. We avoid that extra
67
    /// copy by only passing a reference.
68
    #[inline(always)]
69
6954
    pub(crate) fn finalize(&self) -> Result<Executable, CompilerError> {
70
        // We never execute code from the buffer until it's complete, and we use
71
        // a freshly mmap'ed buffer for each program. Because of this, we don't
72
        // need to explicitly clear the icache even on platforms that would
73
        // normally want this. If we reuse buffers in the future, this will need
74
        // architecture-specific support for icache clearing when a new program
75
        // is finalized into a buffer we previously ran.
76
6954
        let mut mut_buf = MutableBuffer::new(self.buffer.len())?;
77
6954
        mut_buf.set_len(self.buffer.len());
78
6954
        mut_buf[..].copy_from_slice(&self.buffer);
79
6954
        Ok(Executable {
80
6954
            buffer: mut_buf.make_exec()?,
81
        })
82
6954
    }
83
}
84

            
85
impl std::fmt::Debug for Executable {
86
    /// Debug an [`Executable`] by hex-dumping its contents.
87
228
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88
228
        write!(
89
228
            f,
90
228
            "Executable[{}; {} ]",
91
228
            std::env::consts::ARCH,
92
228
            hex::encode(&self.buffer[..])
93
228
        )
94
228
    }
95
}
96

            
97
// Reluctantly implement just enough of [`DynasmLabelApi`] for our single backward label.
98
impl<R: Relocation, const S: usize> DynasmLabelApi for Assembler<R, S> {
99
    type Relocation = R;
100

            
101
    #[inline(always)]
102
111264
    fn local_label(&mut self, name: &'static str) {
103
111264
        debug_assert_eq!(name, "target");
104
111264
        self.target = Some(self.offset());
105
111264
    }
106

            
107
    #[inline(always)]
108
111264
    fn backward_relocation(
109
111264
        &mut self,
110
111264
        name: &'static str,
111
111264
        target_offset: isize,
112
111264
        field_offset: u8,
113
111264
        ref_offset: u8,
114
111264
        kind: R,
115
111264
    ) {
116
111264
        debug_assert_eq!(name, "target");
117
111264
        let target = self
118
111264
            .target
119
111264
            .expect("generated programs always have a target before branch");
120
111264
        // Apply the relocation immediately without storing it.
121
111264
        let loc = PatchLoc::new(self.offset(), target_offset, field_offset, ref_offset, kind);
122
111264
        let buf = &mut self.buffer[loc.range(0)];
123
111264
        loc.patch(buf, 0, target.0)
124
111264
            .expect("program relocations are always in range");
125
111264
    }
126

            
127
    fn global_label(&mut self, _name: &'static str) {
128
        unreachable!();
129
    }
130

            
131
    fn dynamic_label(&mut self, _id: DynamicLabel) {
132
        unreachable!();
133
    }
134

            
135
    fn bare_relocation(&mut self, _target: usize, _field_offset: u8, _ref_offset: u8, _kind: R) {
136
        unreachable!();
137
    }
138

            
139
    fn global_relocation(
140
        &mut self,
141
        _name: &'static str,
142
        _target_offset: isize,
143
        _field_offset: u8,
144
        _ref_offset: u8,
145
        _kind: R,
146
    ) {
147
        unreachable!();
148
    }
149

            
150
    fn dynamic_relocation(
151
        &mut self,
152
        _id: DynamicLabel,
153
        _target_offset: isize,
154
        _field_offset: u8,
155
        _ref_offset: u8,
156
        _kind: R,
157
    ) {
158
        unreachable!();
159
    }
160

            
161
    fn forward_relocation(
162
        &mut self,
163
        _name: &'static str,
164
        _target_offset: isize,
165
        _field_offset: u8,
166
        _ref_offset: u8,
167
        _kind: R,
168
    ) {
169
        unreachable!();
170
    }
171
}
172

            
173
impl<R: Relocation, const S: usize> Extend<u8> for Assembler<R, S> {
174
    #[inline(always)]
175
    fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
176
        self.buffer.extend(iter);
177
    }
178
}
179

            
180
impl<'a, R: Relocation, const S: usize> Extend<&'a u8> for Assembler<R, S> {
181
    #[inline(always)]
182
5784673
    fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
183
17848778
        for byte in iter {
184
12064105
            self.buffer.push(*byte);
185
12064105
        }
186
5784673
    }
187
}
188

            
189
impl<R: Relocation, const S: usize> DynasmApi for Assembler<R, S> {
190
    #[inline(always)]
191
226432
    fn offset(&self) -> AssemblyOffset {
192
226432
        AssemblyOffset(self.buffer.len())
193
226432
    }
194

            
195
    #[inline(always)]
196
8566310
    fn push(&mut self, byte: u8) {
197
8566310
        self.buffer.push(byte);
198
8566310
    }
199

            
200
    fn align(&mut self, _alignment: usize, _with: u8) {
201
        unreachable!();
202
    }
203
}