1
//! `OutputSerializer` + the single runtime-append output protocol.
2
//!
3
//! Every output entity (debug value, tag, delete) is written at the RUNTIME
4
//! `next_write_pos` the `OutputHeader` carries, then `next_write_pos` and
5
//! `output_entity_count` are advanced at runtime. This is the ONE protocol: a
6
//! compile-time-known write (a program result) and a dynamic-count write (a
7
//! `create-tag` inside a loop) both append, so they never overwrite each other.
8
//!
9
//! [`LOCAL_ENTITY_BASE`] holds the current entity's absolute address
10
//! (`output_base + next_write_pos`); writers store their header + data fields
11
//! relative to it. Tag strings still grow DOWN from the buffer end (the entity
12
//! parser reads them as distance-from-end); debug-value strings grow UP right
13
//! after the value and are the last thing written, so neither disturbs the
14
//! contiguous `ENTITY_HEADER_SIZE + data_size` entity walk.
15

            
16
use std::mem::offset_of;
17

            
18
use scripting_format::{
19
    ENTITY_HEADER_SIZE, EntityHeader, EntityType, MAGIC_OUTP, OUTPUT_HEADER_SIZE, Operation,
20
    OutputHeader,
21
};
22

            
23
use crate::compiler::emit::FunctionEmitter;
24
use crate::compiler::expr::{LOCAL_ENTITY_BASE, LOCAL_TEMP_I32};
25

            
26
#[derive(Clone)]
27
pub struct OutputSerializer {
28
    pub(super) output_start_idx: u32,
29
    pub(super) output_base_local: u32,
30
}
31

            
32
impl OutputSerializer {
33
432911
    pub fn new(output_base_local: u32) -> Self {
34
432911
        Self {
35
432911
            output_start_idx: 0,
36
432911
            output_base_local,
37
432911
        }
38
432911
    }
39

            
40
    /// Writes the `OutputHeader` at the buffer start: zero entities, the
41
    /// strings frontier at the buffer end (set lazily by the first string
42
    /// write), and `next_write_pos` just past the header — where the first
43
    /// entity will append.
44
82093
    pub fn begin_output(&mut self, emit: &mut FunctionEmitter) {
45
82093
        let local = self.output_base_local;
46
82093
        emit.store_i32_dynamic(
47
82093
            local,
48
82093
            offset_of!(OutputHeader, magic) as u32,
49
82093
            MAGIC_OUTP as i32,
50
        );
51
82093
        emit.store_i32_dynamic(
52
82093
            local,
53
82093
            offset_of!(OutputHeader, output_entity_count) as u32,
54
            0,
55
        );
56
82093
        emit.store_i32_dynamic(
57
82093
            local,
58
82093
            offset_of!(OutputHeader, output_start_idx) as u32,
59
82093
            self.output_start_idx as i32,
60
        );
61
82093
        emit.store_i32_dynamic(
62
82093
            local,
63
82093
            offset_of!(OutputHeader, next_write_pos) as u32,
64
82093
            OUTPUT_HEADER_SIZE as i32,
65
        );
66
82093
    }
67

            
68
    /// Loads `LOCAL_ENTITY_BASE = output_base + next_write_pos` — the absolute
69
    /// address of the entity about to be written.
70
69779
    pub(super) fn load_entity_base(&self, emit: &mut FunctionEmitter) {
71
69779
        let local = self.output_base_local;
72
69779
        emit.local_get(local);
73
69779
        emit.local_get(local);
74
69779
        emit.i32_load(offset_of!(OutputHeader, next_write_pos) as u64);
75
69779
        emit.i32_add();
76
69779
        emit.local_set(LOCAL_ENTITY_BASE);
77
69779
    }
78

            
79
    /// Appends an entity header at `next_write_pos` (via [`Self::load_entity_base`]),
80
    /// bumps `output_entity_count` at runtime, and returns the entity-relative
81
    /// offset of the data region (`ENTITY_HEADER_SIZE`). The caller then writes
82
    /// data fields relative to `LOCAL_ENTITY_BASE` and finishes with
83
    /// [`Self::advance_past`]. `next_write_pos` is NOT advanced here.
84
69507
    pub(super) fn append_entity_header(
85
69507
        &mut self,
86
69507
        emit: &mut FunctionEmitter,
87
69507
        entity_type: EntityType,
88
69507
        operation: Operation,
89
69507
        flags: u8,
90
69507
        parent_idx: i32,
91
69507
        data_size: u32,
92
69507
    ) -> u32 {
93
69507
        self.load_entity_base(emit);
94
69507
        let ent = LOCAL_ENTITY_BASE;
95
69507
        emit.store_u8_dynamic(
96
69507
            ent,
97
69507
            offset_of!(EntityHeader, entity_type) as u32,
98
69507
            entity_type as u8,
99
        );
100
69507
        emit.store_u8_dynamic(
101
69507
            ent,
102
69507
            offset_of!(EntityHeader, operation) as u32,
103
69507
            operation as u8,
104
        );
105
69507
        emit.store_u8_dynamic(ent, offset_of!(EntityHeader, flags) as u32, flags);
106
69507
        emit.store_i32_dynamic(ent, offset_of!(EntityHeader, parent_idx) as u32, parent_idx);
107
        // data_offset is relative to output_base: next_write_pos + header size =
108
        // (entity_base - output_base) + ENTITY_HEADER_SIZE.
109
69507
        emit.local_get(ent);
110
69507
        emit.i32_const(offset_of!(EntityHeader, data_offset) as i32);
111
69507
        emit.i32_add();
112
69507
        emit.local_get(ent);
113
69507
        emit.local_get(self.output_base_local);
114
69507
        emit.i32_sub();
115
69507
        emit.i32_const(ENTITY_HEADER_SIZE as i32);
116
69507
        emit.i32_add();
117
69507
        emit.i32_store_raw();
118

            
119
69507
        emit.store_i32_dynamic(
120
69507
            ent,
121
69507
            offset_of!(EntityHeader, data_size) as u32,
122
69507
            data_size as i32,
123
        );
124

            
125
69507
        self.bump_entity_count(emit);
126
69507
        ENTITY_HEADER_SIZE as u32
127
69507
    }
128

            
129
    /// `output_entity_count += 1` at runtime.
130
69779
    pub(super) fn bump_entity_count(&self, emit: &mut FunctionEmitter) {
131
69779
        let base = self.output_base_local;
132
69779
        let count_off = offset_of!(OutputHeader, output_entity_count) as u32;
133
69779
        emit.local_get(base);
134
69779
        emit.i32_const(count_off as i32);
135
69779
        emit.i32_add();
136
69779
        emit.local_get(base);
137
69779
        emit.i32_load(u64::from(count_off));
138
69779
        emit.i32_const(1);
139
69779
        emit.i32_add();
140
69779
        emit.i32_store_raw();
141
69779
    }
142

            
143
    /// Advances `next_write_pos` by a COMPILE-TIME-known byte count (header +
144
    /// fixed data). For an entity that also appended grow-up strings, follow
145
    /// with [`Self::advance_past_runtime`].
146
67535
    pub(super) fn advance_past(&self, emit: &mut FunctionEmitter, bytes: u32) {
147
67535
        let base = self.output_base_local;
148
67535
        let off = offset_of!(OutputHeader, next_write_pos) as u32;
149
67535
        emit.local_get(base);
150
67535
        emit.i32_const(off as i32);
151
67535
        emit.i32_add();
152
67535
        emit.local_get(base);
153
67535
        emit.i32_load(u64::from(off));
154
67535
        emit.i32_const(bytes as i32);
155
67535
        emit.i32_add();
156
67535
        emit.i32_store_raw();
157
67535
    }
158

            
159
    /// Advances `next_write_pos` by a RUNTIME byte count already on the wasm
160
    /// stack (for grow-up string payloads of unknown length).
161
2244
    pub(super) fn advance_past_runtime(&self, emit: &mut FunctionEmitter) {
162
        // stack: [extra_bytes]
163
2244
        let base = self.output_base_local;
164
2244
        let off = offset_of!(OutputHeader, next_write_pos) as u32;
165
2244
        let extra = LOCAL_TEMP_I32;
166
2244
        emit.local_set(extra);
167
2244
        emit.local_get(base);
168
2244
        emit.i32_const(off as i32);
169
2244
        emit.i32_add();
170
2244
        emit.local_get(base);
171
2244
        emit.i32_load(u64::from(off));
172
2244
        emit.local_get(extra);
173
2244
        emit.i32_add();
174
2244
        emit.i32_store_raw();
175
2244
    }
176
}