snakeoil_wasm/
python_stack.rs

1use wasm_gen;
2use wasm_gen::{FuncCode, Imm};
3
4use crate::{heap, Builtin, Symbol, PYTHON_STACK_POINTER};
5
6// FIXME: check for stack overflow
7// FIXME: create func that check_stack_overflow
8
9pub fn generate_push() -> wasm_gen::Func {
10    wasm_gen::Func {
11        sig: wasm_gen::FuncType {
12            params: vec![wasm_gen::I32], // symbol value
13            results: vec![],
14        },
15        locals: vec![],
16        code: vec![
17            FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(4)),
18            FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::PythonStackGrow as i32)),
19            FuncCode::new1(wasm_gen::LOCAL_GET, Imm::I32(0)),
20            FuncCode::new2(wasm_gen::I32_STORE, Imm::I64(0x0), Imm::I64(0x0)),
21        ],
22    }
23}
24
25pub fn push(symbol: Symbol) -> Vec<FuncCode> {
26    match symbol {
27        Symbol::FuncElem(_, _) => {
28            panic!();
29        }
30        Symbol::Data(offset) => {
31            let mut code = vec![];
32
33            // tag the value
34            code.append(&mut vec![
35                FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(offset as i32)),
36                FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(heap::TAG_DATA as i32)),
37                FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::BoxValue as i32)),
38            ]);
39            // push on the stack
40            code.append(&mut vec![FuncCode::new1(
41                wasm_gen::CALL,
42                Imm::I32(Builtin::PythonStackPush as i32),
43            )]);
44
45            code
46        }
47        Symbol::HeapObject(offset) => vec![
48            FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(offset as i32)),
49            FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::PythonStackPush as i32)),
50        ],
51        Symbol::WasmTopOfStack => vec![FuncCode::new1(
52            wasm_gen::CALL,
53            Imm::I32(Builtin::PythonStackPush as i32),
54        )],
55        Symbol::Global(idx) => vec![
56            FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(idx as i32)),
57            FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::PythonStackPush as i32)),
58        ],
59        Symbol::None => vec![
60            FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(33)),
61            FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::PythonStackPush as i32)),
62        ],
63        Symbol::Intrinsic(code) => code,
64        Symbol::Int32(n) => vec![
65            FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(n)),
66            FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::PythonStackPush as i32)),
67        ],
68        e => unimplemented!("pushing symbol {:?}", e),
69    }
70}
71
72/**
73 * The stack grows.
74 *   |----------------|----------->
75 * start        ptr (old sp)    new sp
76 */
77pub fn generate_grow() -> wasm_gen::Func {
78    let code = vec![
79        // save for the next instruction to get the ptr
80        FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
81        FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
82        FuncCode::new1(wasm_gen::LOCAL_GET, Imm::I32(0)),
83        FuncCode::new0(wasm_gen::I32_ADD),
84        FuncCode::new1(wasm_gen::GLOBAL_SET, Imm::I32(PYTHON_STACK_POINTER)),
85    ];
86    wasm_gen::Func {
87        sig: wasm_gen::FuncType {
88            params: vec![wasm_gen::I32],  // size in bytes
89            results: vec![wasm_gen::I32], // ptr
90        },
91        locals: vec![],
92        code,
93    }
94}
95
96// Elements on the stack
97pub fn generate_stack_size() -> wasm_gen::Func {
98    let code = vec![
99        FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
100        FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(4)), // alignement
101        FuncCode::new0(wasm_gen::I32_DIV_S),
102    ];
103    wasm_gen::Func {
104        sig: wasm_gen::FuncType {
105            params: vec![wasm_gen::I32],
106            results: vec![wasm_gen::I32], // count
107        },
108        locals: vec![],
109        code,
110    }
111}
112
113/**
114 *   |----------------|-----------<
115 * start        new sp (old ptr)
116 *
117 * We return the old ptr that still points to the object, as long as nobody
118 * allocated at the same time.
119 */
120pub fn generate_shrink() -> wasm_gen::Func {
121    let code = vec![
122        FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
123        FuncCode::new1(wasm_gen::LOCAL_GET, Imm::I32(0)),
124        FuncCode::new0(wasm_gen::I32_SUB),
125        FuncCode::new1(wasm_gen::LOCAL_TEE, Imm::I32(1)),
126        // check if sp >= 0
127        FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(0)),
128        FuncCode::new0(wasm_gen::I32_GE_S),
129        FuncCode::new0(wasm_gen::I32_EQZ),
130        FuncCode::new_control(wasm_gen::IF, wasm_gen::NONE),
131        // {
132        FuncCode::new1(
133            wasm_gen::CALL,
134            Imm::I32(Builtin::TrapPythonStackUnderflow as i32),
135        ),
136        // }
137        FuncCode::new0(wasm_gen::END),
138        // store
139        FuncCode::new1(wasm_gen::LOCAL_GET, Imm::I32(1)),
140        FuncCode::new1(wasm_gen::GLOBAL_SET, Imm::I32(PYTHON_STACK_POINTER)),
141    ];
142
143    wasm_gen::Func {
144        sig: wasm_gen::FuncType {
145            params: vec![wasm_gen::I32], // size in bytes
146            results: vec![],
147        },
148        locals: vec![
149            (1, wasm_gen::I32), // scratch to hold the sp
150        ],
151        code,
152    }
153}
154
155pub fn generate_pop() -> wasm_gen::Func {
156    let mut code = vec![];
157    code.append(&mut vec![
158        FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(4)),
159        FuncCode::new1(wasm_gen::CALL, Imm::I32(Builtin::PythonStackShrink as i32)),
160        FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
161        FuncCode::new2(wasm_gen::I32_LOAD, Imm::I64(0x0), Imm::I64(0x0)),
162    ]);
163
164    wasm_gen::Func {
165        sig: wasm_gen::FuncType {
166            params: vec![],
167            results: vec![wasm_gen::I32],
168        },
169        locals: vec![],
170        code,
171    }
172}
173
174// Peak at the last n'th element on the stack
175pub fn generate_peak_last_n() -> wasm_gen::Func {
176    wasm_gen::Func {
177        sig: wasm_gen::FuncType {
178            params: vec![wasm_gen::I32],  // n
179            results: vec![wasm_gen::I32], // object
180        },
181        locals: vec![],
182        code: vec![
183            FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
184            FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(4)), // alignement
185            FuncCode::new1(wasm_gen::LOCAL_GET, Imm::I32(0)),
186            FuncCode::new0(wasm_gen::I32_MUL),
187            FuncCode::new0(wasm_gen::I32_SUB),
188            FuncCode::new2(wasm_gen::I32_LOAD, Imm::I64(0x0), Imm::I64(0x0)),
189        ],
190    }
191}
192
193// A pop but without removing the stack element
194pub fn generate_peak_last() -> wasm_gen::Func {
195    wasm_gen::Func {
196        sig: wasm_gen::FuncType {
197            params: vec![],
198            results: vec![wasm_gen::I32], // object
199        },
200        locals: vec![],
201        code: vec![
202            FuncCode::new1(wasm_gen::GLOBAL_GET, Imm::I32(PYTHON_STACK_POINTER)),
203            FuncCode::new1(wasm_gen::I32_CONST, Imm::I32(4)), // alignement
204            FuncCode::new0(wasm_gen::I32_SUB),
205            FuncCode::new2(wasm_gen::I32_LOAD, Imm::I64(0x0), Imm::I64(0x0)),
206        ],
207    }
208}
209
210// For debugging purposes; dump Python's stack
211pub fn dump() -> Vec<FuncCode> {
212    vec![
213        FuncCode::new1(wasm_gen::CALL, Imm::I64(0)), // lib.dump_python_stack
214        FuncCode::new0(wasm_gen::UNREACHABLE),
215    ]
216}
217
218#[cfg(test)]
219mod tests {
220    use std::collections::HashMap;
221    use wasmi::RuntimeValue;
222
223    use super::*;
224    use crate::{build_builtins, build_globals, build_imports, build_types, test_runner};
225    use wasm_gen::WasmCodeGen;
226
227    fn test_module() -> WasmCodeGen {
228        let mut wasm_module = WasmCodeGen::new();
229        wasm_module.add_table(wasm_gen::TableElemType::Funcref, 10, 10);
230        let mem = wasm_module.add_memory(10, 64 * 100);
231        wasm_module.add_export("mem".to_string(), mem, wasm_gen::ExportType::Mem);
232
233        // reserve space for the Python stack at the start of the memory
234        let mut end_of_data_offset = 8;
235
236        build_types(&mut wasm_module);
237        build_globals(&mut wasm_module);
238        build_imports(
239            &mut wasm_module,
240            &mut end_of_data_offset,
241            &mut HashMap::new(),
242        );
243        build_builtins(&mut wasm_module);
244
245        wasm_module.add_export(
246            "push_data".to_string(),
247            Builtin::PythonStackPush as usize,
248            wasm_gen::ExportType::Func,
249        );
250        wasm_module.add_export(
251            "pop".to_string(),
252            Builtin::PythonStackPop as usize,
253            wasm_gen::ExportType::Func,
254        );
255        wasm_module.add_export(
256            "size".to_string(),
257            Builtin::PythonStackSize as usize,
258            wasm_gen::ExportType::Func,
259        );
260
261        wasm_module
262    }
263
264    #[test]
265    fn test_stack() {
266        let instance = test_runner::instantiate(test_module());
267
268        let v1 = RuntimeValue::I32(1);
269        test_runner::call(&instance, "push_data", &[v1]);
270        let v2 = RuntimeValue::I32(2);
271        test_runner::call(&instance, "push_data", &[v2]);
272
273        let r1 = test_runner::call(&instance, "pop", &[]);
274        assert_eq!(r1.unwrap(), v2);
275        let r2 = test_runner::call(&instance, "pop", &[]);
276        assert_eq!(r2.unwrap(), v1);
277    }
278
279    #[test]
280    fn test_stack_size() {
281        let instance = test_runner::instantiate(test_module());
282
283        let size0 = test_runner::call(&instance, "size", &[]);
284        assert_eq!(size0.unwrap(), RuntimeValue::I32(0));
285
286        test_runner::call(&instance, "push_data", &[RuntimeValue::I32(1)]);
287        let size1 = test_runner::call(&instance, "size", &[]);
288        assert_eq!(size1.unwrap(), RuntimeValue::I32(1));
289
290        test_runner::call(&instance, "push_data", &[RuntimeValue::I32(2)]);
291        let size2 = test_runner::call(&instance, "size", &[]);
292        assert_eq!(size2.unwrap(), RuntimeValue::I32(2));
293    }
294
295    #[test]
296    fn test_pop_stack_underflow() {
297        let instance = test_runner::instantiate(test_module());
298
299        test_runner::call(&instance, "push_data", &[RuntimeValue::I32(7)]);
300        test_runner::call(&instance, "pop", &[]);
301        let trap = test_runner::call_trap(&instance, "pop", &[]);
302
303        match trap.kind() {
304            wasmi::TrapKind::Unreachable => { /* ok */ }
305            _ => panic!(),
306        };
307    }
308
309    #[test]
310    fn test_push_stack_overflow() {
311        let instance = test_runner::instantiate(test_module());
312
313        test_runner::call(&instance, "push_data", &[RuntimeValue::I32(7)]);
314        test_runner::call(&instance, "push_data", &[RuntimeValue::I32(8)]);
315        let trap = test_runner::call_trap(&instance, "push_data", &[RuntimeValue::I32(9)]);
316
317        match trap.kind() {
318            wasmi::TrapKind::Unreachable => { /* ok */ }
319            _ => panic!(),
320        };
321    }
322}