summaryrefslogtreecommitdiffstats
path: root/third_party/rust/spirv-cross-internal/src/emscripten.rs
blob: f134fbd88c37f7bf4d9ae038449256af5aac0e27 (plain)
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
//! Utilities for interacting with the generated Emscripten module.
//! Most functionality is generalized, but some functionality is specific to SPIRV-Cross.

use crate::{bindings, ErrorCode};
use js_sys::{global, Object, Reflect, Uint32Array, Uint8Array};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    // Raw Emscripten bindings
    #[wasm_bindgen(js_namespace = sc_internal)]
    fn _malloc(size: u32) -> u32;

    #[wasm_bindgen(js_namespace = sc_internal)]
    fn _free(offset: u32);
}

pub fn get_module() -> Module {
    const MODULE_NAME: &'static str = "sc_internal";
    let module = Reflect::get(&global(), &JsValue::from_str(MODULE_NAME))
        .unwrap()
        .into();
    Module { module }
}

const U32_SIZE: u32 = std::mem::size_of::<u32>() as u32;

fn get_value<T>(object: &Object, key: &str) -> T
where
    T: std::convert::From<wasm_bindgen::JsValue>,
{
    Reflect::get(object, &JsValue::from_str(key))
        .unwrap()
        .into()
}

/// An Emscripten pointer.
/// Internally stores an offset to a location on the Emscripten `u8` heap.
#[derive(Clone, Copy)]
pub struct Pointer {
    offset: u32,
}

impl Pointer {
    pub fn from_offset(offset: u32) -> Self {
        Pointer { offset }
    }

    pub fn as_offset(&self) -> u32 {
        self.offset
    }
}

pub struct Module {
    module: Object,
}

impl Module {
    /// Allocate memory on the heap.
    pub unsafe fn allocate(&self, byte_len: u32) -> Pointer {
        Pointer {
            offset: _malloc(byte_len),
        }
    }

    pub unsafe fn free(&self, pointer: Pointer) {
        _free(pointer.as_offset())
    }

    // Read a `u32` value from the heap.
    pub unsafe fn get_u32(&self, pointer: Pointer) -> u32 {
        let offset = &JsValue::from_f64((pointer.offset / U32_SIZE) as f64);
        // TODO: Remove Reflect
        Reflect::get(&self.heap_u32(), offset)
            .unwrap()
            .as_f64()
            .unwrap() as u32
    }

    /// Set memory on the heap to `bytes`.
    pub unsafe fn set_from_u8_typed_array(&self, pointer: Pointer, bytes: Uint8Array) {
        let buffer: JsValue = self.heap_u8().buffer().into();
        let memory =
            Uint8Array::new_with_byte_offset_and_length(&buffer, pointer.offset, bytes.length());
        memory.set(&bytes, 0);
    }

    /// Set memory on the heap to `bytes`.
    pub unsafe fn set_from_u8_slice(&self, pointer: Pointer, bytes: &[u8]) {
        self.set_from_u8_typed_array(pointer, Uint8Array::view(bytes));
    }

    fn heap_u8(&self) -> Uint8Array {
        const HEAP_U8: &'static str = "HEAPU8";
        get_value(&self.module, HEAP_U8)
    }

    fn heap_u32(&self) -> Uint32Array {
        const HEAP_U32: &'static str = "HEAPU32";
        get_value(&self.module, HEAP_U32)
    }

    /// Clones all bytes from the heap into a `Vec<u8>` while `should_continue` returns `true`.
    /// Optionally include the last byte (i.e. to support peeking the final byte for nul-terminated strings).
    pub unsafe fn read_bytes_into_vec_while<F>(
        &self,
        pointer: Pointer,
        should_continue: F,
        include_last_byte: bool,
    ) -> Vec<u8>
    where
        F: Fn(u8, usize) -> bool,
    {
        let mut bytes = Vec::new();
        let heap = &self.heap_u8();
        let start_offset = pointer.offset as usize;
        loop {
            let bytes_read = bytes.len();
            let offset = &JsValue::from_f64((start_offset + bytes_read) as f64);
            let byte = Reflect::get(heap, offset).unwrap().as_f64().unwrap() as u8;
            if should_continue(byte, bytes_read) {
                bytes.push(byte);
                continue;
            }
            if include_last_byte {
                bytes.push(byte);
            }
            break;
        }
        bytes
    }

    /// Clones all bytes from the heap into the pointer provided while `should_continue` returns `true`.
    /// Optionally include the last byte (i.e. to support peeking the final byte for nul-terminated strings).
    /// Assumes the memory at the pointer is large enough to hold all bytes read (based on when `should_continue` terminates).
    pub unsafe fn read_bytes_into_pointer_while<F>(
        &self,
        pointer: Pointer,
        should_continue: F,
        include_last_byte: bool,
        into_pointer: *mut u8,
    ) where
        F: Fn(u8, usize) -> bool,
    {
        let heap = &self.heap_u8();
        let start_offset = pointer.offset as usize;
        let mut bytes_read = 0;
        loop {
            let offset = &JsValue::from_f64((start_offset + bytes_read) as f64);
            let byte = Reflect::get(heap, offset).unwrap().as_f64().unwrap() as u8;
            if should_continue(byte, bytes_read) {
                *into_pointer.offset(bytes_read as isize) = byte;
                bytes_read += 1;
                continue;
            }
            if include_last_byte {
                *into_pointer.offset(bytes_read as isize) = byte;
            }
            break;
        }
    }
}