facet-postcard 0.46.1

Postcard binary format for facet with Tier-0 and Tier-2 JIT deserialization
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
//! Postcard JIT helper functions for Tier-2 format JIT.
//!
//! # Implementation Status
//!
//! **These helpers are implemented but NOT currently used by the JIT.**
//!
//! The `JitFormat` implementation in [`super::format`] uses **inline Cranelift IR**
//! instead of calling these extern functions. This is intentional for now:
//!
//! - **Inline IR**: Faster (no call overhead), but harder to debug and maintain.

#![allow(clippy::missing_safety_doc)] // Safety docs are in function comments
//! - **Helpers**: Easier to test and debug, but adds function call overhead.
//!
//! The helpers here serve as:
//! 1. **Reference implementation**: Correct postcard parsing logic in readable Rust
//! 2. **Unit tests**: The `#[cfg(test)]` module validates parsing behavior
//! 3. **Future fallback**: Could be used if inline IR becomes too complex
//!
//! # Why Keep Both?
//!
//! During development, having both allows:
//! - Testing parsing logic without running JIT (via unit tests below)
//! - Comparing inline IR behavior against known-good Rust implementation
//! - Debugging by temporarily swapping to helper calls
//!
//! Long-term, we may:
//! - Delete helpers if inline IR is stable and well-tested
//! - Or switch to helpers if inline IR maintenance cost is too high
//!
//! # Helper ABI
//!
//! These extern "C" functions implement postcard parsing operations for direct
//! byte-level parsing by JIT-compiled code.

use super::jit_debug;
use crate::DEFAULT_MAX_COLLECTION_ELEMENTS;

// =============================================================================
// Return Types
// =============================================================================

/// Return type for postcard_jit_seq_begin.
#[repr(C)]
pub struct PostcardJitPosError {
    /// New position after parsing
    pub new_pos: usize,
    /// Error code (0 = success, negative = error)
    pub error: i32,
}

/// Return type for postcard_jit_seq_is_end.
///
/// To fit in 2 return registers, we pack `is_end` into the high bit of `new_pos`.
#[repr(C)]
pub struct PostcardJitPosEndError {
    /// Packed: `(is_end << 63) | pos` (pos is unchanged for postcard)
    pub packed_pos_end: usize,
    /// Error code (0 = success, negative = error)
    pub error: i32,
}

impl PostcardJitPosEndError {
    /// Create with explicit values
    pub fn new(pos: usize, is_end: bool, error: i32) -> Self {
        let packed_pos_end = if is_end { pos | (1usize << 63) } else { pos };
        Self {
            packed_pos_end,
            error,
        }
    }

    /// Extract pos from packed value
    #[allow(dead_code)]
    pub fn pos(&self) -> usize {
        self.packed_pos_end & 0x7FFFFFFFFFFFFFFF
    }

    /// Extract is_end from packed value
    #[allow(dead_code)]
    pub fn is_end(&self) -> bool {
        (self.packed_pos_end >> 63) != 0
    }
}

/// Return type for postcard_jit_parse_bool.
#[repr(C)]
pub struct PostcardJitPosValueError {
    /// Packed: `(value << 63) | new_pos`
    pub packed_pos_value: usize,
    /// Error code (0 = success, negative = error)
    pub error: i32,
}

impl PostcardJitPosValueError {
    /// Create with explicit values
    pub fn new(new_pos: usize, value: bool, error: i32) -> Self {
        let packed_pos_value = if value {
            new_pos | (1usize << 63)
        } else {
            new_pos
        };
        Self {
            packed_pos_value,
            error,
        }
    }

    /// Extract new_pos from packed value
    #[allow(dead_code)]
    pub fn new_pos(&self) -> usize {
        self.packed_pos_value & 0x7FFFFFFFFFFFFFFF
    }

    /// Extract value from packed value
    #[allow(dead_code)]
    pub fn value(&self) -> bool {
        (self.packed_pos_value >> 63) != 0
    }
}

/// Return type for varint decoding.
#[repr(C)]
pub struct PostcardJitVarintResult {
    /// New position after parsing the varint
    pub new_pos: usize,
    /// Decoded value
    pub value: u64,
    /// Error code (0 = success, negative = error)
    pub error: i32,
}

// =============================================================================
// Error Codes
// =============================================================================

/// Postcard JIT error codes
pub mod error {
    /// Unexpected end of input
    pub const UNEXPECTED_EOF: i32 = -100;
    /// Invalid boolean value (not 0 or 1)
    pub const INVALID_BOOL: i32 = -101;
    /// Varint overflow (too many continuation bytes)
    pub const VARINT_OVERFLOW: i32 = -102;
    /// Sequence underflow (decrement when remaining is 0)
    pub const SEQ_UNDERFLOW: i32 = -103;
    /// Collection length exceeds configured safety limit
    pub const COLLECTION_TOO_LARGE: i32 = -109;
    /// Unsupported operation
    pub const UNSUPPORTED: i32 = -1;
}

// =============================================================================
// Helper Functions
// =============================================================================

/// Read a varint (LEB128) from the input.
///
/// Postcard uses unsigned LEB128 for lengths and unsigned integers.
/// Each byte has 7 data bits (0-6) and 1 continuation bit (7).
/// If bit 7 is set, more bytes follow.
///
/// Returns: (new_pos, value, error_code).
#[unsafe(no_mangle)]
pub unsafe extern "C" fn postcard_jit_read_varint(
    input: *const u8,
    len: usize,
    pos: usize,
) -> PostcardJitVarintResult {
    jit_debug!("[postcard_jit_read_varint] pos={}, len={}", pos, len);

    let mut result: u64 = 0;
    let mut shift: u32 = 0;
    let mut p = pos;

    loop {
        if p >= len {
            jit_debug!("[postcard_jit_read_varint] EOF at pos={}", p);
            return PostcardJitVarintResult {
                new_pos: p,
                value: 0,
                error: error::UNEXPECTED_EOF,
            };
        }

        let byte = unsafe { *input.add(p) };
        p += 1;

        // Extract 7 data bits and add to result
        let data = (byte & 0x7F) as u64;

        // Check for overflow before shifting
        if shift >= 64 {
            jit_debug!("[postcard_jit_read_varint] overflow at shift={}", shift);
            return PostcardJitVarintResult {
                new_pos: p,
                value: 0,
                error: error::VARINT_OVERFLOW,
            };
        }

        result |= data << shift;
        shift += 7;

        // If continuation bit is clear, we're done
        if (byte & 0x80) == 0 {
            jit_debug!(
                "[postcard_jit_read_varint] done: value={}, new_pos={}",
                result,
                p
            );
            return PostcardJitVarintResult {
                new_pos: p,
                value: result,
                error: 0,
            };
        }
    }
}

/// Parse the start of a postcard sequence (read length varint).
///
/// Postcard sequences are length-prefixed: `[varint_length][elements...]`
/// This function reads the length and stores it in the state pointer.
///
/// Returns: (new_pos, error_code).
#[unsafe(no_mangle)]
pub unsafe extern "C" fn postcard_jit_seq_begin(
    input: *const u8,
    len: usize,
    pos: usize,
    state_ptr: *mut u64,
) -> PostcardJitPosError {
    jit_debug!(
        "[postcard_jit_seq_begin] pos={}, len={}, state_ptr={:p}",
        pos,
        len,
        state_ptr
    );

    // Read the length varint
    let result = unsafe { postcard_jit_read_varint(input, len, pos) };

    if result.error != 0 {
        return PostcardJitPosError {
            new_pos: result.new_pos,
            error: result.error,
        };
    }

    if result.value > DEFAULT_MAX_COLLECTION_ELEMENTS {
        return PostcardJitPosError {
            new_pos: result.new_pos,
            error: error::COLLECTION_TOO_LARGE,
        };
    }

    // Store the remaining count in the state
    unsafe {
        *state_ptr = result.value;
    }

    jit_debug!(
        "[postcard_jit_seq_begin] remaining={}, new_pos={}",
        result.value,
        result.new_pos
    );

    PostcardJitPosError {
        new_pos: result.new_pos,
        error: 0,
    }
}

/// Check if at end of postcard sequence.
///
/// For postcard, "end" is determined by the remaining count in state, not by
/// reading a delimiter byte. If remaining == 0, we're at the end.
///
/// Returns: (packed_pos_end, error_code) where packed = (is_end << 63) | pos.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn postcard_jit_seq_is_end(
    pos: usize,
    state_ptr: *const u64,
) -> PostcardJitPosEndError {
    let remaining = unsafe { *state_ptr };
    jit_debug!(
        "[postcard_jit_seq_is_end] pos={}, remaining={}",
        pos,
        remaining
    );

    let is_end = remaining == 0;
    jit_debug!("[postcard_jit_seq_is_end] -> is_end={}", is_end);

    // Note: pos is unchanged - postcard doesn't consume any bytes for "end"
    PostcardJitPosEndError::new(pos, is_end, 0)
}

/// Advance to next sequence element.
///
/// For postcard, this just decrements the remaining count. No bytes are consumed
/// (the separator is implicit - elements are back-to-back).
///
/// Returns: (new_pos, error_code).
#[unsafe(no_mangle)]
pub unsafe extern "C" fn postcard_jit_seq_next(
    pos: usize,
    state_ptr: *mut u64,
) -> PostcardJitPosError {
    let remaining = unsafe { *state_ptr };
    jit_debug!(
        "[postcard_jit_seq_next] pos={}, remaining={}",
        pos,
        remaining
    );

    if remaining == 0 {
        // This shouldn't happen if the protocol is followed correctly
        return PostcardJitPosError {
            new_pos: pos,
            error: error::SEQ_UNDERFLOW,
        };
    }

    // Decrement remaining count
    unsafe {
        *state_ptr = remaining - 1;
    }

    jit_debug!("[postcard_jit_seq_next] -> new_remaining={}", remaining - 1);

    // Position unchanged - no bytes consumed
    PostcardJitPosError {
        new_pos: pos,
        error: 0,
    }
}

/// Parse a postcard boolean.
///
/// Postcard bools are single bytes: 0 = false, 1 = true.
/// Any other value is an error.
///
/// Returns: (packed_pos_value, error_code) where packed = (value << 63) | new_pos.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn postcard_jit_parse_bool(
    input: *const u8,
    len: usize,
    pos: usize,
) -> PostcardJitPosValueError {
    jit_debug!("[postcard_jit_parse_bool] pos={}, len={}", pos, len);

    if pos >= len {
        jit_debug!("[postcard_jit_parse_bool] EOF!");
        return PostcardJitPosValueError::new(pos, false, error::UNEXPECTED_EOF);
    }

    let byte = unsafe { *input.add(pos) };
    jit_debug!("[postcard_jit_parse_bool] byte={}", byte);

    match byte {
        0 => {
            jit_debug!("[postcard_jit_parse_bool] -> false");
            PostcardJitPosValueError::new(pos + 1, false, 0)
        }
        1 => {
            jit_debug!("[postcard_jit_parse_bool] -> true");
            PostcardJitPosValueError::new(pos + 1, true, 0)
        }
        _ => {
            jit_debug!("[postcard_jit_parse_bool] -> invalid!");
            PostcardJitPosValueError::new(pos, false, error::INVALID_BOOL)
        }
    }
}

/// Bulk copy bytes for `Vec<u8>` fast path.
///
/// This is called after bounds checking has been done by the JIT.
/// Simply copies `count` bytes from `src` to `dest`.
///
/// # Safety
/// - `dest` must be valid for writes of `count` bytes
/// - `src` must be valid for reads of `count` bytes
/// - The memory regions must not overlap
#[unsafe(no_mangle)]
pub unsafe extern "C" fn postcard_jit_bulk_copy_u8(dest: *mut u8, src: *const u8, count: usize) {
    jit_debug!(
        "[postcard_jit_bulk_copy_u8] dest={:p}, src={:p}, count={}",
        dest,
        src,
        count
    );
    unsafe {
        core::ptr::copy_nonoverlapping(src, dest, count);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_varint_single_byte() {
        // Values 0-127 encode as single byte
        let input = [0u8];
        let result = unsafe { postcard_jit_read_varint(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, 0);
        assert_eq!(result.value, 0);
        assert_eq!(result.new_pos, 1);

        let input = [127u8];
        let result = unsafe { postcard_jit_read_varint(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, 0);
        assert_eq!(result.value, 127);
        assert_eq!(result.new_pos, 1);
    }

    #[test]
    fn test_varint_multi_byte() {
        // 128 = 0x80 0x01 (continuation bit set on first byte)
        let input = [0x80, 0x01];
        let result = unsafe { postcard_jit_read_varint(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, 0);
        assert_eq!(result.value, 128);
        assert_eq!(result.new_pos, 2);

        // 300 = 0xAC 0x02
        let input = [0xAC, 0x02];
        let result = unsafe { postcard_jit_read_varint(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, 0);
        assert_eq!(result.value, 300);
        assert_eq!(result.new_pos, 2);
    }

    #[test]
    fn test_parse_bool() {
        let input = [0u8];
        let result = unsafe { postcard_jit_parse_bool(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, 0);
        assert!(!result.value());
        assert_eq!(result.new_pos(), 1);

        let input = [1u8];
        let result = unsafe { postcard_jit_parse_bool(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, 0);
        assert!(result.value());
        assert_eq!(result.new_pos(), 1);

        let input = [2u8];
        let result = unsafe { postcard_jit_parse_bool(input.as_ptr(), input.len(), 0) };
        assert_eq!(result.error, error::INVALID_BOOL);
    }

    #[test]
    fn test_seq_operations() {
        // Test sequence: [3, true, false, true] = [0x03, 0x01, 0x00, 0x01]
        let input = [0x03, 0x01, 0x00, 0x01];
        let mut state: u64 = 0;

        // Begin: read length (3)
        let result = unsafe { postcard_jit_seq_begin(input.as_ptr(), input.len(), 0, &mut state) };
        assert_eq!(result.error, 0);
        assert_eq!(result.new_pos, 1); // Past the varint
        assert_eq!(state, 3);

        // Check not at end
        let result = unsafe { postcard_jit_seq_is_end(result.new_pos, &state) };
        assert_eq!(result.error, 0);
        assert!(!result.is_end());

        // Advance (after parsing first element)
        let result = unsafe { postcard_jit_seq_next(result.pos(), &mut state) };
        assert_eq!(result.error, 0);
        assert_eq!(state, 2);

        // Advance twice more
        let result = unsafe { postcard_jit_seq_next(result.new_pos, &mut state) };
        assert_eq!(result.error, 0);
        assert_eq!(state, 1);

        let result = unsafe { postcard_jit_seq_next(result.new_pos, &mut state) };
        assert_eq!(result.error, 0);
        assert_eq!(state, 0);

        // Now at end
        let result = unsafe { postcard_jit_seq_is_end(result.new_pos, &state) };
        assert_eq!(result.error, 0);
        assert!(result.is_end());
    }

    #[test]
    fn test_seq_begin_rejects_oversized_length() {
        let mut encoded = Vec::new();
        let mut value = DEFAULT_MAX_COLLECTION_ELEMENTS + 1;
        loop {
            let mut byte = (value & 0x7F) as u8;
            value >>= 7;
            if value != 0 {
                byte |= 0x80;
            }
            encoded.push(byte);
            if value == 0 {
                break;
            }
        }

        let mut state: u64 = 0;
        let result =
            unsafe { postcard_jit_seq_begin(encoded.as_ptr(), encoded.len(), 0, &mut state) };
        assert_eq!(result.error, error::COLLECTION_TOO_LARGE);
    }
}