char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport wasmstreaming extension to typescript067c81e

main
6.0 KiB218 linesraw
1import {
2  ArrayBufferPrototypeGetResizable as resizable,
3  Map,
4  MapPrototypeDelete as mapDelete,
5  MapPrototypeGet as mapGet,
6  MapPrototypeSet as mapSet,
7  ObjectDefineProperty as define,
8  ReflectApply as apply,
9  TypedArrayPrototypeGetBuffer as buffer,
10  TypedArrayPrototypeGetTag as tag,
11  TypeError,
12  Uint8Array,
13} from "ext:core/primordials";
14import {
15  op_wasm_streaming_abort as abort,
16  op_wasm_streaming_finish as finish,
17  op_wasm_streaming_register as register,
18  op_wasm_streaming_url as setUrl,
19  op_wasm_streaming_write as write,
20} from "ext:core/ops";
21import { markPromiseHandled, safePromise } from "ext:core/promises";
22import { onRuntimeStart } from "ext:core/bootstrap";
23import { messageState, usable } from "ext:http_types/body.js";
24import { getHeader } from "ext:http_types/headers.js";
25import { extractMime } from "ext:http_types/mime.js";
26import {
27  read,
28  readerCancel,
29  release,
30  streamReader,
31} from "ext:webstreams/streams.js";
32import type {
33  ReadableStreamDefaultReader,
34} from "ext:webstreams/streams.js";
35import type { ResponseState } from "ext:http_types/body.js";
36import type {
37  CompileOptions,
38  CompileStreaming,
39  Imports,
40  InstantiateStreaming,
41  InstantiatedSource,
42  WasmModule,
43  WasmNamespace,
44} from "./types.js";
45
46declare const WebAssembly: WasmNamespace;
47
48let wasm: WasmNamespace;
49let nativeCompileStreaming: CompileStreaming;
50let nativeInstantiateStreaming: InstantiateStreaming;
51const pending = new Map<unknown, unknown>();
52const maxToken = 9_007_199_254_740_991;
53let nextToken = 0;
54
55function setLength<T extends Function>(fn: T, name: string, length: number): T {
56  define(fn, "name", { value: name });
57  define(fn, "length", { value: length });
58  return fn;
59}
60
61async function consume(id: number, source: unknown): Promise<void> {
62  let reader: ReadableStreamDefaultReader<unknown> | undefined;
63  let body: ResponseState["body"];
64  try {
65    const state: ResponseState = messageState(source, "response");
66    body = state.body;
67    setUrl([id, state.url]);
68    usable(body);
69    if (body.stream === null) {
70      finish(id);
71      return;
72    }
73    body.used = true;
74    reader = streamReader(body.stream);
75    while (true) {
76      const result = await safePromise(read(reader));
77      if (result.done) break;
78      if (tag(result.value) !== "Uint8Array") {
79        throw new TypeError(
80          "Response body stream must contain Uint8Array chunks",
81        );
82      }
83      let bytes = result.value as Uint8Array<ArrayBufferLike>, copy: boolean;
84      try {
85        copy = resizable(buffer(bytes));
86      } catch {
87        copy = true;
88      }
89      if (copy) bytes = new Uint8Array(bytes);
90      write(id, bytes as Uint8Array<ArrayBuffer>);
91    }
92    finish(id);
93  } catch (error) {
94    if (reader !== undefined) {
95      try {
96        markPromiseHandled(readerCancel(reader, error));
97      } catch (error) {
98        void error;
99      }
100    }
101    try {
102      abort(id, error);
103    } catch (error) {
104      void error;
105    }
106  } finally {
107    if (reader !== undefined) {
108      try {
109        release(reader);
110      } catch (error) {
111        void error;
112      }
113    }
114  }
115}
116
117function onNativeStart(token: unknown, id: number): void {
118  const response = mapGet(pending, token);
119  mapDelete(pending, token);
120  if (response === undefined) {
121    abort(id, new TypeError("Missing WebAssembly streaming response"));
122    return;
123  }
124  consume(id, response);
125}
126
127interface StreamingState {
128  active: boolean;
129  token: string | undefined;
130}
131
132async function validatedSource(
133  source: unknown,
134  state: StreamingState,
135): Promise<string> {
136  const response = await safePromise(source);
137  if (!state.active) return "avril-wasm-stream-inactive";
138  const responseState = messageState(response, "response");
139  if (responseState.status < 200 || responseState.status > 299) {
140    throw new TypeError("WebAssembly response status is not successful");
141  }
142  const mime = extractMime(getHeader(responseState.headers, "content-type"));
143  if (mime?.essence !== "application/wasm") {
144    throw new TypeError("WebAssembly response has an invalid MIME type");
145  }
146  if (nextToken >= maxToken) {
147    throw new TypeError("WebAssembly streaming token space exhausted");
148  }
149  const token = `avril-wasm-stream-${++nextToken}`;
150  state.token = token;
151  mapSet(pending, token, response);
152  return token;
153}
154
155async function streamingCall<T>(
156  source: unknown,
157  invoke: (sourcePromise: unknown) => T,
158): Promise<Awaited<T>> {
159  const state: StreamingState = { active: true, token: undefined };
160  const sourcePromise = validatedSource(source, state);
161  markPromiseHandled(sourcePromise);
162  try {
163    return await safePromise(invoke(safePromise(sourcePromise)));
164  } finally {
165    state.active = false;
166    if (state.token !== undefined) mapDelete(pending, state.token);
167  }
168}
169const methods: {
170  compileStreaming: CompileStreaming;
171  instantiateStreaming: InstantiateStreaming;
172} = {
173  compileStreaming(source: unknown, options?: CompileOptions) {
174    return streamingCall(
175      source,
176      (sourcePromise) =>
177        apply(nativeCompileStreaming, wasm, [sourcePromise, options]) as Promise<
178          WasmModule
179        >,
180    );
181  },
182  instantiateStreaming(
183    source: unknown,
184    imports?: Imports,
185    options?: CompileOptions,
186  ) {
187    return streamingCall(
188      source,
189      (sourcePromise) =>
190        apply(nativeInstantiateStreaming, wasm, [
191          sourcePromise,
192          imports,
193          options,
194        ]) as Promise<InstantiatedSource>,
195    );
196  },
197};
198const { compileStreaming, instantiateStreaming } = methods;
199
200onRuntimeStart(() => {
201  // V8 exposes WebAssembly only in live contexts, not snapshot-building contexts.
202  wasm = WebAssembly;
203  nativeCompileStreaming = wasm.compileStreaming;
204  nativeInstantiateStreaming = wasm.instantiateStreaming;
205  register(onNativeStart);
206  define(wasm, "compileStreaming", {
207    value: setLength(compileStreaming, "compileStreaming", 1),
208    writable: true,
209    enumerable: true,
210    configurable: true,
211  });
212  define(wasm, "instantiateStreaming", {
213    value: setLength(instantiateStreaming, "instantiateStreaming", 2),
214    writable: true,
215    enumerable: true,
216    configurable: true,
217  });
218});