summaryrefslogtreecommitdiffstats
path: root/intl/icu_capi/js/package/lib/diplomat-runtime.js
blob: 75317d4a746332899eb5ed430f23b79fdd790303 (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
export function readString(wasm, ptr, len) {
  const buf = new Uint8Array(wasm.memory.buffer, ptr, len);
  return (new TextDecoder("utf-8")).decode(buf)
}

export function withWriteable(wasm, callback) {
  const writeable = wasm.diplomat_buffer_writeable_create(0);
  try {
    callback(writeable);
    const outStringPtr = wasm.diplomat_buffer_writeable_get_bytes(writeable);
    const outStringLen = wasm.diplomat_buffer_writeable_len(writeable);
    return readString(wasm, outStringPtr, outStringLen);
  } finally {
    wasm.diplomat_buffer_writeable_destroy(writeable);
  }
}

export class FFIError extends Error {
  constructor(error_value) {
    super("Error over FFI");
    this.error_value = error_value; // (2)
  }
}

export function extractCodePoint(str, param) {
  const cp = str.codePointAt?.(0);
  if ((!cp && cp !== 0) || [...str]?.length != 1) {
    throw new TypeError(`Expected single-character string for char parameter ${param}, found ${str}`);
  }
  return cp;
}

// Get the pointer returned by an FFI function
//
// It's tempting to call `(new Uint32Array(wasm.memory.buffer, FFI_func(), 1))[0]`.
// However, there's a chance that `wasm.memory.buffer` will be resized between
// the time it's accessed and the time it's used, invalidating the view.
// This function ensures that the view into wasm memory is fresh.
//
// This is used for methods that return multiple types into a wasm buffer, where
// one of those types is another ptr. Call this method to get access to the returned
// ptr, so the return buffer can be freed.
export function ptrRead(wasm, ptr) {
  return (new Uint32Array(wasm.memory.buffer, ptr, 1))[0];
}

// Get the flag of a result type.
export function resultFlag(wasm, ptr, offset) {
  return (new Uint8Array(wasm.memory.buffer, ptr + offset, 1))[0];
}

// Get the discriminant of a Rust enum.
export function enumDiscriminant(wasm, ptr) {
  return (new Int32Array(wasm.memory.buffer, ptr, 1))[0]
}

// A wrapper around a slice of WASM memory that can be freed manually or
// automatically by the garbage collector.
//
// This type is necessary for Rust functions that take a `&str` or `&[T]`, since
// they can create an edge to this object if they borrow from the str/slice,
// or we can manually free the WASM memory if they don't.
export class DiplomatBuf {
  static str = (wasm, string) => {
    var utf8_len = 0;
    for (const codepoint_string of string) {
      let codepoint = codepoint_string.codePointAt(0);
      if (codepoint < 0x80) {
        utf8_len += 1
      } else if (codepoint < 0x800) {
        utf8_len += 2
      } else if (codepoint < 0x10000) {
        utf8_len += 3
      } else {
        utf8_len += 4
      }
    }
    return new DiplomatBuf(wasm, utf8_len, 1, buf => {
      const result = (new TextEncoder()).encodeInto(string, buf);
      console.assert(string.length == result.read && utf8_len == result.written, "UTF-8 write error");
  })
}

  static slice = (wasm, slice, align) => {
    // If the slice is not a Uint8Array, we have to convert to one, as that's the only
    // thing we can write into the wasm buffer.
    const bytes = slice.constructor.name == "Uint8Array" ? slice : new Uint8Array(slice);
    return new DiplomatBuf(wasm, bytes.length, align, buf => buf.set(bytes));
  }

  constructor(wasm, size, align, encodeCallback) {
    const ptr = wasm.diplomat_alloc(size, align);
    encodeCallback(new Uint8Array(wasm.memory.buffer, ptr, size));

    this.ptr = ptr;
    this.size = size;
    this.free = () => {
      const successfully_unregistered = DiplomatBuf_finalizer.unregister(this);
      if (successfully_unregistered) {
        wasm.diplomat_free(this.ptr, this.size, align);
      } else {
        console.error(`Failed to unregister DiplomatBuf at ${ptr}, this is a bug. Either it was never registered (leak), it was already unregistered (failed attempt to double free), or the unregister token was unrecognized (fallback to GC).`);
      }
    }

    DiplomatBuf_finalizer.register(this, { wasm, ptr, size, align }, this);
  }

  leak = () => {
    const successfully_unregistered = DiplomatBuf_finalizer.unregister(this);
      if (successfully_unregistered) {
        // leak
      } else {
        console.error(`Failed to unregister DiplomatBuf at ${this.ptr}, this is a bug. Either it was never registered (leak), it was already unregistered (failed attempt to double free), or the unregister token was unrecognized (fallback to GC).`);
      }
  }
}

const DiplomatBuf_finalizer = new FinalizationRegistry(({ wasm, ptr, size, align }) => {
  wasm.diplomat_free(ptr, size, align);
});