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
319563790
    pub(crate) fn entry() -> AssemblyOffset {
38
319563790
        AssemblyOffset(0)
39
319563790
    }
40

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

            
47
    /// Make a new assembler with a temporary buffer but no executable buffer.
48
    #[inline(always)]
49
7930
    pub(crate) fn new() -> Self {
50
7930
        Self {
51
7930
            buffer: ArrayVec::new(),
52
7930
            target: None,
53
7930
            phantom: PhantomData,
54
7930
        }
55
7930
    }
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
7930
    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
7930
        let mut mut_buf = MutableBuffer::new(self.buffer.len())?;
77
7930
        mut_buf.set_len(self.buffer.len());
78
7930
        mut_buf[..].copy_from_slice(&self.buffer);
79
7930
        Ok(Executable {
80
7930
            buffer: mut_buf.make_exec()?,
81
        })
82
7930
    }
83
}
84

            
85
impl std::fmt::Debug for Executable {
86
    /// Debug an [`Executable`] by hex-dumping its contents.
87
260
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88
260
        write!(
89
260
            f,
90
260
            "Executable[{}; {} ]",
91
260
            std::env::consts::ARCH,
92
260
            hex::encode(&self.buffer[..])
93
260
        )
94
260
    }
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
126880
    fn local_label(&mut self, name: &'static str) {
103
126880
        debug_assert_eq!(name, "target");
104
126880
        self.target = Some(self.offset());
105
126880
    }
106

            
107
    #[inline(always)]
108
126880
    fn backward_relocation(
109
126880
        &mut self,
110
126880
        name: &'static str,
111
126880
        target_offset: isize,
112
126880
        field_offset: u8,
113
126880
        ref_offset: u8,
114
126880
        kind: R,
115
126880
    ) {
116
126880
        debug_assert_eq!(name, "target");
117
126880
        let target = self
118
126880
            .target
119
126880
            .expect("generated programs always have a target before branch");
120
126880
        // Apply the relocation immediately without storing it.
121
126880
        let loc = PatchLoc::new(self.offset(), target_offset, field_offset, ref_offset, kind);
122
126880
        let buf = &mut self.buffer[loc.range(0)];
123
126880
        loc.patch(buf, 0, target.0)
124
126880
            .expect("program relocations are always in range");
125
126880
    }
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
6593169
    fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
183
20336930
        for byte in iter {
184
13743761
            self.buffer.push(*byte);
185
13743761
        }
186
6593169
    }
187
}
188

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

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

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