Skip to main content

nifty_codegen/compile/body/
download.rs

1//! `download(…)` and `format_bytes(…)`, which hand their work to the runtime.
2
3use std::mem::{offset_of, size_of};
4
5use cranelift_codegen::ir::types::{I8, I32};
6use cranelift_codegen::ir::{InstBuilder, MemFlags, Value};
7use cranelift_module::Module;
8use nifty_check::hir::{Download, DownloadCommand, Expr, PlaceStep, Type};
9use nifty_rt::{DownloadBinding, DownloadDesc};
10use nifty_syntax::Span;
11
12use super::{Body, Result, offset};
13
14impl<M: Module> Body<'_, '_, '_, M> {
15    /// Asks for a download once the event finishes. The places are worked out after the
16    /// address, folder and settings, as in the interpreter, so each borrowed value is held
17    /// across everything worked out after it.
18    pub(super) fn download(&mut self, download: &Download, span: Span) -> Result<()> {
19        let settings = [
20            &download.folder,
21            &download.name,
22            &download.connections,
23            &download.headers,
24            &download.retries,
25        ];
26        let indexes = download
27            .bindings
28            .iter()
29            .flat_map(|(_, place)| &place.path)
30            .filter_map(|step| match step {
31                PlaceStep::Index(index) => Some(index),
32                PlaceStep::Field(_) => None,
33            });
34        let after = |setting: usize| settings[setting..].iter().copied().chain(indexes.clone());
35        let url = self.expr(&download.url)?;
36        let url = self.hold_across(url, &Type::String, after(0))?;
37        let folder = self.expr(&download.folder)?;
38        let folder = self.hold_across(folder, &Type::String, after(1))?;
39        let name = self.expr(&download.name)?;
40        let name = self.hold_across(name, &Type::String, after(2))?;
41        let connections = self.int(&download.connections)?;
42        let headers = self.expr(&download.headers)?;
43        let headers = self.hold_across(headers, &download.headers.ty, after(4))?;
44        let retries = self.int(&download.retries)?;
45        let mut places = Vec::with_capacity(download.bindings.len());
46        for (field, place) in &download.bindings {
47            let path = self.path(place)?;
48            let Some(desc) = self.place_desc(&path)? else {
49                return self.fail_now(self.shared.runtime.fail_download_into_variable, span);
50            };
51            places.push((field.code(), desc));
52        }
53        let bindings = self.download_bindings(&places)?;
54        let desc = self.stack(size_of::<DownloadDesc>())?;
55        let len = self.usize(places.len())?;
56        for (value, at) in [
57            (url.value, offset_of!(DownloadDesc, url)),
58            (folder.value, offset_of!(DownloadDesc, folder)),
59            (name.value, offset_of!(DownloadDesc, name)),
60            (connections, offset_of!(DownloadDesc, connections)),
61            (headers.value, offset_of!(DownloadDesc, headers)),
62            (retries, offset_of!(DownloadDesc, retries)),
63            (bindings, offset_of!(DownloadDesc, bindings)),
64            (len, offset_of!(DownloadDesc, len)),
65        ] {
66            let at = offset(at)?;
67            self.b.ins().store(MemFlags::trusted(), value, desc, at);
68        }
69        let span = self.span(span)?;
70        let start = self.shared.runtime.download;
71        self.call_checked(start, &[self.context, desc, span]);
72        self.discard(url, &Type::String)?;
73        self.discard(folder, &Type::String)?;
74        self.discard(name, &Type::String)?;
75        self.discard(headers, &download.headers.ty)
76    }
77
78    /// Each field's code and place, as an array of `nifty_rt::DownloadBinding`s on the
79    /// stack, and its address.
80    fn download_bindings(&mut self, places: &[(u8, Value)]) -> Result<Value> {
81        let size = size_of::<DownloadBinding>();
82        let array = self.stack(places.len().max(1) * size)?;
83        for (index, &(field, place)) in places.iter().enumerate() {
84            let field = self.b.ins().iconst(I8, i64::from(field));
85            let field_at = offset(index * size + offset_of!(DownloadBinding, field))?;
86            let place_at = offset(index * size + offset_of!(DownloadBinding, place))?;
87            self.b
88                .ins()
89                .store(MemFlags::trusted(), field, array, field_at);
90            self.b
91                .ins()
92                .store(MemFlags::trusted(), place, array, place_at);
93        }
94        Ok(array)
95    }
96
97    /// `cancel_download(id)` and the like: asks for it, done once the event finishes.
98    pub(super) fn download_command(&mut self, command: DownloadCommand, id: &Expr) -> Result<()> {
99        let id_operand = self.expr(id)?;
100        let command = self.b.ins().iconst(I32, i64::from(command as u32));
101        let ask = self.shared.runtime.download_command;
102        self.call(ask, &[self.context, command, id_operand.value]);
103        self.discard(id_operand, &Type::String)
104    }
105
106    /// `format_bytes(n)`: an owned string.
107    pub(super) fn format_bytes(&mut self, bytes: &Expr) -> Result<Value> {
108        let bytes = self.int(bytes)?;
109        let format = self.shared.runtime.format_bytes;
110        Ok(self.call1(format, &[bytes]))
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn a_bindings_field_is_stored_as_one_byte() {
120        assert_eq!(size_of::<nifty_check::hir::DownloadField>(), 1);
121    }
122}