Skip to main content

nifty_codegen/compile/
mod.rs

1//! Compiles a checked program: its functions, its states' starting values, and every
2//! dynamic part of its windows and views, then emits the `nifty_rt::ProgramDesc` that
3//! describes them to the runtime.
4
5mod body;
6mod menu;
7mod runtime;
8mod types;
9mod ui;
10
11use std::collections::HashMap;
12use std::mem::{offset_of, size_of};
13
14use cranelift_codegen::ir::types::{I8, I64};
15use cranelift_codegen::ir::{InstBuilder, MemFlags, StackSlotData, StackSlotKind};
16use cranelift_module::{DataDescription, DataId, FuncId, Module};
17use nifty_check::hir::{FnDef, Program, StateDef};
18use nifty_rt::{ABI_VERSION, FrameDesc, ProgramDesc, SourceLocation, SpanDesc, StateDesc, Str};
19use nifty_syntax::Span;
20
21use self::body::{Body, Operand, Returns};
22use self::runtime::Runtime;
23use self::types::Types;
24use crate::CodegenError;
25use crate::emit::{Blob, Emitter, Target};
26
27type Result<T> = std::result::Result<T, CodegenError>;
28
29/// What every function being compiled consults besides the module.
30struct Shared<'p> {
31    program: &'p Program,
32    runtime: Runtime,
33    types: Types,
34    /// The compiled Nifty functions, by function id.
35    functions: Vec<FuncId>,
36    spans: Spans,
37    /// The bytes of each piece of text in the program, emitted once.
38    texts: HashMap<String, DataId>,
39}
40
41impl Shared<'_> {
42    /// The data holding `text`'s bytes.
43    fn text<M: Module>(&mut self, module: &mut M, text: &str) -> Result<DataId> {
44        if let Some(id) = self.texts.get(text) {
45            return Ok(*id);
46        }
47        let id = module.declare_anonymous_data(false, false)?;
48        let mut description = DataDescription::new();
49        description.define(text.as_bytes().into());
50        module.define_data(id, &description)?;
51        self.texts.insert(text.to_owned(), id);
52        Ok(id)
53    }
54}
55
56/// The spans failures point at, numbered in the order code first uses them.
57#[derive(Debug, Default)]
58struct Spans {
59    ids: HashMap<Span, u32>,
60    spans: Vec<Span>,
61}
62
63impl Spans {
64    fn id(&mut self, span: Span) -> Result<u32> {
65        if let Some(id) = self.ids.get(&span) {
66            return Ok(*id);
67        }
68        let id = u32::try_from(self.spans.len())
69            .map_err(|_| CodegenError::new("the program has too many places that can fail"))?;
70        self.ids.insert(span, id);
71        self.spans.push(span);
72        Ok(id)
73    }
74}
75
76/// Compiles `program` into `emitter`, and returns its `nifty_rt::ProgramDesc`. `locate`
77/// says where each span is, for the errors the app may report.
78///
79/// # Errors
80/// When the program has errors, or Cranelift can't compile it.
81pub fn compile<M: Module>(
82    emitter: &mut Emitter<M>,
83    program: &Program,
84    locate: &dyn Fn(Span) -> SourceLocation,
85) -> Result<DataId> {
86    let runtime = Runtime::import(emitter)?;
87    let functions = program
88        .functions
89        .iter()
90        .map(|function| emitter.declare_function(&function_signature(emitter, function)))
91        .collect::<Result<_>>()?;
92    let mut shared = Shared {
93        program,
94        runtime,
95        types: Types::default(),
96        functions,
97        spans: Spans::default(),
98        texts: HashMap::new(),
99    };
100    for (index, function) in program.functions.iter().enumerate() {
101        let id = shared.functions[index];
102        compile_function(emitter, &mut shared, function, id)?;
103    }
104    let states = program
105        .states
106        .iter()
107        .map(|state| compile_state(emitter, &mut shared, state))
108        .collect::<Result<Vec<_>>>()?;
109    let windows = ui::windows(emitter, &mut shared)?;
110    let views = ui::views(emitter, &mut shared)?;
111    let (menus, menu_locals) = menu::menu_bar(emitter, &mut shared)?;
112    let state_list = state_list(emitter, &mut shared, &states)?;
113    let names = function_names(emitter, &mut shared)?;
114    let spans = span_list(emitter, &mut shared, locate)?;
115    shared.types.emit(emitter, &shared.runtime, program)?;
116
117    let mut desc = Blob::zeroed(size_of::<ProgramDesc>());
118    desc.put_u32(offset_of!(ProgramDesc, abi_version), ABI_VERSION);
119    for (field, list) in [
120        (offset_of!(ProgramDesc, states), state_list),
121        (offset_of!(ProgramDesc, windows), windows),
122        (offset_of!(ProgramDesc, views), views),
123        (offset_of!(ProgramDesc, functions), names),
124        (offset_of!(ProgramDesc, spans), spans),
125        (offset_of!(ProgramDesc, menus), menus),
126        (
127            offset_of!(ProgramDesc, menu_frame) + offset_of!(FrameDesc, locals),
128            menu_locals,
129        ),
130    ] {
131        list.put(&mut desc, field);
132    }
133    emitter.data(desc)
134}
135
136/// An array emitted as data, ready to be pointed at by a `nifty_rt::Slice`.
137#[derive(Clone, Copy, Debug)]
138struct List {
139    data: Option<DataId>,
140    len: usize,
141}
142
143impl List {
144    const EMPTY: Self = Self { data: None, len: 0 };
145
146    /// Emits `len` items of `size` bytes each, which `write` lays out in a blob at the
147    /// offset it's given.
148    fn emit<M: Module>(
149        emitter: &mut Emitter<M>,
150        len: usize,
151        size: usize,
152        mut write: impl FnMut(&mut Blob, usize, usize) -> Result<()>,
153    ) -> Result<Self> {
154        if len == 0 {
155            return Ok(Self::EMPTY);
156        }
157        let mut blob = Blob::zeroed(len * size);
158        for index in 0..len {
159            write(&mut blob, index, index * size)?;
160        }
161        Ok(Self {
162            data: Some(emitter.data(blob)?),
163            len,
164        })
165    }
166
167    fn put(self, blob: &mut Blob, offset: usize) {
168        blob.put_slice(offset, self.data.map(Target::Data), self.len);
169    }
170}
171
172/// A `Str` field at `offset` holding `text`.
173fn put_str<M: Module>(
174    emitter: &mut Emitter<M>,
175    shared: &mut Shared<'_>,
176    blob: &mut Blob,
177    offset: usize,
178    text: &str,
179) -> Result<()> {
180    let bytes = (!text.is_empty())
181        .then(|| shared.text(emitter.module_mut(), text))
182        .transpose()?;
183    blob.put_slice(offset, bytes.map(Target::Data), text.len());
184    Ok(())
185}
186
187/// Nifty functions take the context, then each argument as a slot, then where to write
188/// their result, and return whether they succeeded.
189fn function_signature<M: Module>(
190    emitter: &Emitter<M>,
191    function: &FnDef,
192) -> cranelift_codegen::ir::Signature {
193    let mut params = vec![I64];
194    params.extend(std::iter::repeat_n(I64, function.params));
195    params.push(I64);
196    emitter.signature(&params, &[I8])
197}
198
199/// Arguments come in owned, into the first locals; a function releases its locals when
200/// it returns.
201fn compile_function<M: Module>(
202    emitter: &mut Emitter<M>,
203    shared: &mut Shared<'_>,
204    function: &FnDef,
205    id: FuncId,
206) -> Result<()> {
207    let signature = function_signature(emitter, function);
208    emitter.define_function(id, signature, |b, module| {
209        let entry = b
210            .current_block()
211            .ok_or_else(|| CodegenError::new("a function has no entry block"))?;
212        let params = b.block_params(entry).to_vec();
213        let (context, out) = (params[0], params[params.len() - 1]);
214        let frame_size = u32::try_from(function.locals.len() * 8)
215            .map_err(|_| CodegenError::new(format!("`{}` has too many locals", function.name)))?;
216        let slot = b.create_sized_stack_slot(StackSlotData::new(
217            StackSlotKind::ExplicitSlot,
218            frame_size.max(8),
219            3,
220        ));
221        let frame = b.ins().stack_addr(I64, slot, 0);
222        let zero = b.ins().iconst(I64, 0);
223        for local in 0..function.locals.len() {
224            let value = params.get(local + 1).filter(|_| local < function.params);
225            let at =
226                i32::try_from(local * 8).map_err(|_| CodegenError::new("a frame is too large"))?;
227            b.ins().store(
228                MemFlags::trusted(),
229                value.copied().unwrap_or(zero),
230                frame,
231                at,
232            );
233        }
234        let mut body = Body::new(b, module, shared, context, frame, &function.locals);
235        body.returns = Some(Returns { out });
236        body.block(&function.body)?;
237        body.release_locals()?;
238        body.finish();
239        Ok(())
240    })
241}
242
243/// A state's starting value, written to `out`.
244fn compile_state<M: Module>(
245    emitter: &mut Emitter<M>,
246    shared: &mut Shared<'_>,
247    state: &StateDef,
248) -> Result<FuncId> {
249    let signature = emitter.signature(&[I64, I64], &[I8]);
250    emitter.function(signature, |b, module| {
251        let entry = b
252            .current_block()
253            .ok_or_else(|| CodegenError::new("a function has no entry block"))?;
254        let (context, out) = (b.block_params(entry)[0], b.block_params(entry)[1]);
255        let frame = b.ins().iconst(I64, 0);
256        let mut body = Body::new(b, module, shared, context, frame, &[]);
257        let operand: Operand = body.expr(&state.init)?;
258        let value = body.own(operand, &state.ty)?;
259        body.b.ins().store(MemFlags::trusted(), value, out, 0);
260        body.finish();
261        Ok(())
262    })
263}
264
265fn state_list<M: Module>(
266    emitter: &mut Emitter<M>,
267    shared: &mut Shared<'_>,
268    inits: &[FuncId],
269) -> Result<List> {
270    let types = shared
271        .program
272        .states
273        .iter()
274        .map(|state| {
275            shared
276                .types
277                .desc(emitter.module_mut(), &shared.runtime, &state.ty)
278        })
279        .collect::<Result<Vec<_>>>()?;
280    List::emit(
281        emitter,
282        inits.len(),
283        size_of::<StateDesc>(),
284        |blob, index, at| {
285            blob.put_pointer(at + offset_of!(StateDesc, ty), Target::Data(types[index]));
286            blob.put_pointer(
287                at + offset_of!(StateDesc, init),
288                Target::Function(inits[index]),
289            );
290            Ok(())
291        },
292    )
293}
294
295fn function_names<M: Module>(emitter: &mut Emitter<M>, shared: &mut Shared<'_>) -> Result<List> {
296    let names = shared
297        .program
298        .functions
299        .iter()
300        .map(|function| shared.text(emitter.module_mut(), &function.name))
301        .collect::<Result<Vec<_>>>()?;
302    let functions = &shared.program.functions;
303    List::emit(emitter, names.len(), size_of::<Str>(), |blob, index, at| {
304        blob.put_slice(
305            at,
306            Some(Target::Data(names[index])),
307            functions[index].name.len(),
308        );
309        Ok(())
310    })
311}
312
313fn span_list<M: Module>(
314    emitter: &mut Emitter<M>,
315    shared: &mut Shared<'_>,
316    locate: &dyn Fn(Span) -> SourceLocation,
317) -> Result<List> {
318    let locations: Vec<SourceLocation> = shared
319        .spans
320        .spans
321        .iter()
322        .map(|span| locate(*span))
323        .collect();
324    let files = locations
325        .iter()
326        .map(|location| shared.text(emitter.module_mut(), &location.file))
327        .collect::<Result<Vec<_>>>()?;
328    List::emit(
329        emitter,
330        locations.len(),
331        size_of::<SpanDesc>(),
332        |blob, index, at| {
333            let location = &locations[index];
334            blob.put_slice(
335                at + offset_of!(SpanDesc, file),
336                Some(Target::Data(files[index])),
337                location.file.len(),
338            );
339            blob.put_u32(at + offset_of!(SpanDesc, line), location.line);
340            blob.put_u32(at + offset_of!(SpanDesc, column), location.column);
341            Ok(())
342        },
343    )
344}