shopify_function_provider/
log.rs

1use std::ptr;
2
3use crate::{decorate_for_target, Context};
4
5static mut LOG_RET_AREA: [usize; 5] = [0; 5];
6// One more byte so we can check if we're truncating.
7const CAPACITY: usize = 1001;
8
9// A kind of ring buffer implementation. Since all reads are guaranteed to
10// start after all writes have finished, we can simplify the
11// implementation by only using a single offset for reads and writes.
12#[derive(Debug)]
13pub(crate) struct Logs {
14    buffer: [u8; CAPACITY],
15    offset: usize,
16    len: usize,
17}
18
19impl Default for Logs {
20    fn default() -> Self {
21        Self {
22            buffer: [0; CAPACITY],
23            offset: 0,
24            len: 0,
25        }
26    }
27}
28
29impl Logs {
30    fn append(&mut self, mut len: usize) -> (usize, *const u8, usize, *const u8, usize) {
31        let mut source_offset = 0;
32        let dst_offset1 = unsafe { self.buffer.as_ptr().add(self.offset) };
33        let len1;
34        let mut dst_offset2 = ptr::null();
35        let mut len2 = 0;
36
37        // Need to strip off start of incoming buffer if the incoming buffer exceeds capacity.
38        if len > CAPACITY {
39            source_offset = len - CAPACITY;
40            len = CAPACITY;
41        }
42
43        let space_to_end = CAPACITY - self.offset;
44        if len <= space_to_end {
45            // Incoming buffer fits in one block.
46            len1 = len;
47            self.len = (self.len + len).min(CAPACITY);
48        } else {
49            // Incoming data wrap will wrap around.
50            len1 = space_to_end;
51            dst_offset2 = self.buffer.as_ptr();
52            len2 = len - space_to_end;
53            self.len = CAPACITY;
54        }
55
56        self.offset = (self.offset + len) % CAPACITY;
57
58        (source_offset, dst_offset1, len1, dst_offset2, len2)
59    }
60
61    #[cfg(target_family = "wasm")]
62    pub(crate) fn read_ptrs(&self) -> (*const u8, usize, *const u8, usize) {
63        // _After_ filling the buffer, the read offset will _always_ be the
64        // same as the write offset.
65        let read_offset = if self.len < CAPACITY { 0 } else { self.offset };
66
67        if read_offset == 0 {
68            (self.buffer.as_ptr(), self.len, ptr::null(), 0)
69        } else {
70            let data_to_end = CAPACITY - read_offset;
71            (
72                unsafe { self.buffer.as_ptr().add(self.offset) },
73                data_to_end,
74                self.buffer.as_ptr(),
75                self.len - data_to_end,
76            )
77        }
78    }
79}
80
81impl Context {
82    fn allocate_log(&mut self, len: usize) -> (usize, *const u8, usize, *const u8, usize) {
83        self.logs.append(len)
84    }
85}
86
87decorate_for_target! {
88    fn shopify_function_log_new_utf8_str(len: usize) -> *const usize {
89        Context::with_mut(|context| {
90            let (src_offset, ptr1, len1, ptr2, len2) = context.allocate_log(len);
91            #[allow(static_mut_refs)] // This is _technically_ safe given this is single threaded.
92            unsafe {
93                LOG_RET_AREA[0] = src_offset;
94                LOG_RET_AREA[1] = ptr1 as usize;
95                LOG_RET_AREA[2] = len1;
96                LOG_RET_AREA[3] = ptr2 as usize;
97                LOG_RET_AREA[4] = len2;
98                LOG_RET_AREA.as_ptr()
99            }
100        })
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_append_fits_in_buffer() {
110        let mut logs = Logs::default();
111        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(100);
112
113        assert_eq!(source_offset, 0);
114        assert_eq!(logs.len, 100);
115        assert_eq!(logs.offset, 100);
116        assert_eq!(ptr1, logs.buffer.as_ptr());
117        assert_eq!(len1, 100);
118        assert_eq!(len2, 0);
119        assert!(ptr2.is_null());
120    }
121
122    #[test]
123    fn test_append_exceeds_capacity() {
124        let mut logs = Logs::default();
125        let large_len = CAPACITY + 100;
126
127        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(large_len);
128
129        assert_eq!(source_offset, 100);
130        assert_eq!(logs.len, CAPACITY);
131        assert_eq!(logs.offset, 0);
132        assert_eq!(ptr1, logs.buffer.as_ptr());
133        assert_eq!(len1, CAPACITY);
134        assert_eq!(len2, 0);
135        assert!(ptr2.is_null());
136    }
137
138    #[test]
139    fn test_append_zero_length() {
140        let mut logs = Logs::default();
141        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(0);
142
143        assert_eq!(source_offset, 0);
144        assert_eq!(logs.len, 0);
145        assert_eq!(logs.offset, 0);
146        assert_eq!(ptr1, logs.buffer.as_ptr());
147        assert_eq!(len1, 0);
148        assert_eq!(len2, 0);
149        assert!(ptr2.is_null());
150    }
151
152    #[test]
153    fn test_append_exact_capacity() {
154        let mut logs = Logs::default();
155        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(CAPACITY);
156
157        assert_eq!(source_offset, 0);
158        assert_eq!(logs.len, CAPACITY);
159        assert_eq!(logs.offset, 0);
160        assert_eq!(ptr1, logs.buffer.as_ptr());
161        assert_eq!(len1, CAPACITY);
162        assert_eq!(len2, 0);
163        assert!(ptr2.is_null());
164    }
165
166    #[test]
167    fn test_append_multiple_operations() {
168        let mut logs = Logs::default();
169
170        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(300);
171        assert_eq!(source_offset, 0);
172        assert_eq!(logs.len, 300);
173        assert_eq!(logs.offset, 300);
174        assert_eq!(ptr1, logs.buffer.as_ptr());
175        assert_eq!(len1, 300);
176        assert_eq!(ptr2, ptr::null());
177        assert_eq!(len2, 0);
178
179        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(200);
180        assert_eq!(source_offset, 0);
181        assert_eq!(logs.len, 500);
182        assert_eq!(logs.offset, 500);
183        assert_eq!(ptr1, unsafe { logs.buffer.as_ptr().add(300) });
184        assert_eq!(len1, 200);
185        assert_eq!(ptr2, ptr::null());
186        assert_eq!(len2, 0);
187
188        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(600); // Total would be 1100, exceeds capacity (1001)
189        assert_eq!(source_offset, 0);
190        assert_eq!(logs.len, CAPACITY);
191        assert_eq!(logs.offset, 99); // (500 + 600) % CAPACITY
192        assert_eq!(ptr1, unsafe { logs.buffer.as_ptr().add(500) });
193        assert_eq!(len1, 501);
194        assert_eq!(ptr2, logs.buffer.as_ptr());
195        assert_eq!(len2, 99);
196
197        let (source_offset, ptr1, len1, ptr2, len2) = logs.append(100); // Total would be 1200
198        assert_eq!(source_offset, 0);
199        assert_eq!(logs.len, CAPACITY);
200        assert_eq!(logs.offset, 199); // (500 + 600 + 100) % CAPACITY
201        assert_eq!(ptr1, unsafe { logs.buffer.as_ptr().add(99) });
202        assert_eq!(len1, 100);
203        assert_eq!(ptr2, ptr::null());
204        assert_eq!(len2, 0);
205    }
206}