Skip to content

Commit 8e4b2e9

Browse files
committed
fs_mem: implement efficient file.data resize logic
1 parent 88d17b6 commit 8e4b2e9

File tree

1 file changed

+49
-21
lines changed

1 file changed

+49
-21
lines changed

src/fs_mem.ts

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,46 @@ import { debug } from "./debug.js";
22
import * as wasi from "./wasi_defs.js";
33
import { Fd, Inode } from "./fd.js";
44

5+
function dataResize(data: Uint8Array, newDataSize: number): Uint8Array {
6+
// reuse same data if not actually resizing
7+
if (data.byteLength === newDataSize) {
8+
return data;
9+
}
10+
11+
// prefer using
12+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/resize
13+
// when applicable; can be used to shrink/grow
14+
if (
15+
data.buffer instanceof ArrayBuffer &&
16+
data.buffer.resizable &&
17+
newDataSize <= data.buffer.maxByteLength
18+
) {
19+
data.buffer.resize(newDataSize);
20+
return data;
21+
}
22+
23+
// shrinking: create a new resizable ArrayBuffer and copy a subset
24+
// of old data onto it
25+
if (data.byteLength > newDataSize) {
26+
const newBuffer = new ArrayBuffer(newDataSize, {
27+
maxByteLength: newDataSize,
28+
}),
29+
newData = new Uint8Array(newBuffer);
30+
newData.set(new Uint8Array(data.buffer, 0, newDataSize));
31+
return newData;
32+
}
33+
34+
// growing: create a new resizable ArrayBuffer with exponential
35+
// growth of maxByteLength, to avoid O(n^2) overhead of repeatedly
36+
// concatenating buffers when doing a lot of small writes at the end
37+
const newBuffer = new ArrayBuffer(newDataSize, {
38+
maxByteLength: Math.max(newDataSize, data.buffer.maxByteLength * 2),
39+
}),
40+
newData = new Uint8Array(newBuffer);
41+
newData.set(data);
42+
return newData;
43+
}
44+
545
export class OpenFile extends Fd {
646
file: File;
747
file_pos: bigint = 0n;
@@ -12,13 +52,11 @@ export class OpenFile extends Fd {
1252
}
1353

1454
fd_allocate(offset: bigint, len: bigint): number {
15-
if (this.file.size > offset + len) {
55+
if (this.file.size >= offset + len) {
1656
// already big enough
1757
} else {
1858
// extend
19-
const new_data = new Uint8Array(Number(offset + len));
20-
new_data.set(this.file.data, 0);
21-
this.file.data = new_data;
59+
this.file.data = dataResize(this.file.data, Number(offset + len));
2260
}
2361
return wasi.ERRNO_SUCCESS;
2462
}
@@ -28,17 +66,7 @@ export class OpenFile extends Fd {
2866
}
2967

3068
fd_filestat_set_size(size: bigint): number {
31-
if (this.file.size > size) {
32-
// truncate
33-
this.file.data = new Uint8Array(
34-
this.file.data.buffer.slice(0, Number(size)),
35-
);
36-
} else {
37-
// extend
38-
const new_data = new Uint8Array(Number(size));
39-
new_data.set(this.file.data, 0);
40-
this.file.data = new_data;
41-
}
69+
this.file.data = dataResize(this.file.data, Number(size));
4270
return wasi.ERRNO_SUCCESS;
4371
}
4472

@@ -91,11 +119,10 @@ export class OpenFile extends Fd {
91119
if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten: 0 };
92120

93121
if (this.file_pos + BigInt(data.byteLength) > this.file.size) {
94-
const old = this.file.data;
95-
this.file.data = new Uint8Array(
122+
this.file.data = dataResize(
123+
this.file.data,
96124
Number(this.file_pos + BigInt(data.byteLength)),
97125
);
98-
this.file.data.set(old);
99126
}
100127

101128
this.file.data.set(data, Number(this.file_pos));
@@ -107,9 +134,10 @@ export class OpenFile extends Fd {
107134
if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten: 0 };
108135

109136
if (offset + BigInt(data.byteLength) > this.file.size) {
110-
const old = this.file.data;
111-
this.file.data = new Uint8Array(Number(offset + BigInt(data.byteLength)));
112-
this.file.data.set(old);
137+
this.file.data = dataResize(
138+
this.file.data,
139+
Number(offset + BigInt(data.byteLength)),
140+
);
113141
}
114142

115143
this.file.data.set(data, Number(offset));

0 commit comments

Comments
 (0)