Skip to content

Commit c21f51b

Browse files
authored
fix!: napi_adjust_external_memory no longer grow wasm memory (#207)
1 parent a7316ec commit c21f51b

5 files changed

Lines changed: 51 additions & 35 deletions

File tree

packages/emnapi/src/core/memory.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { emnapiCtx, emnapiEnv } from 'emnapi:shared'
2-
import { wasmMemory } from 'emscripten:runtime'
32
import { from64, makeSetValue } from 'emscripten:parse-tools'
43
import { $CHECK_ENV, $CHECK_ARG } from '../macro'
54
import { $emnapiSetValueI64 as emnapiSetValueI64 } from '../util'
@@ -14,24 +13,13 @@ export function napi_adjust_external_memory (
1413
const envObject = emnapiEnv
1514
$CHECK_ARG!(envObject, adjusted_value)
1615

17-
const change_in_bytes_number = Number(change_in_bytes)
18-
19-
if (change_in_bytes_number < 0) {
20-
return envObject.setLastError(napi_status.napi_invalid_arg)
21-
}
22-
23-
const old_size = wasmMemory.buffer.byteLength
24-
let new_size = old_size + change_in_bytes_number
25-
new_size = new_size + ((65536 - new_size % 65536) % 65536)
26-
if (wasmMemory.grow((new_size - old_size + 65535) >> 16) === -1) {
27-
return envObject.setLastError(napi_status.napi_generic_failure)
28-
}
16+
let adjusted_memory = emnapiCtx.adjustAmountOfExternalAllocatedMemory(change_in_bytes)
2917

3018
from64('adjusted_value')
3119
if (emnapiCtx.features.BigInt) {
32-
makeSetValue('adjusted_value', 0, 'wasmMemory.buffer.byteLength', 'i64')
20+
makeSetValue('adjusted_value', 0, 'adjusted_memory', 'i64')
3321
} else {
34-
emnapiSetValueI64(adjusted_value, wasmMemory.buffer.byteLength)
22+
emnapiSetValueI64(adjusted_value, Number(adjusted_memory))
3523
}
3624

3725
return envObject.clearLastError()

packages/emnapi/src/emscripten/memory.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { wasmMemory } from 'emscripten:runtime'
21
import { emnapiCtx, emnapiEnv } from 'emnapi:shared'
32
import { from64, makeSetValue } from 'emscripten:parse-tools'
43
import { $emnapiSetValueI64 as emnapiSetValueI64 } from '../util'
54
import { $CHECK_ENV } from '../macro'
65

76
/* eslint-disable @stylistic/indent */
8-
declare function _emscripten_resize_heap (requested: number): boolean
97

108
/**
11-
* @__deps emscripten_resize_heap
129
* @__sig ipjp
1310
*/
1411
export function napi_adjust_external_memory (
@@ -20,38 +17,28 @@ export function napi_adjust_external_memory (
2017
$CHECK_ENV!(env)
2118
const envObject = emnapiEnv
2219

23-
let change_in_bytes: number
20+
let change_in_bytes: bigint
2421

2522
// #if WASM_BIGINT
2623
if (!high) return envObject.setLastError(napi_status.napi_invalid_arg)
27-
change_in_bytes = Number(low)
24+
change_in_bytes = BigInt(low)
2825
// #else
2926
if (!adjusted_value) return envObject.setLastError(napi_status.napi_invalid_arg)
30-
change_in_bytes = (low >>> 0) + (high * Math.pow(2, 32))
27+
change_in_bytes = BigInt(low >>> 0) + (BigInt(high) << BigInt(32))
3128
// #endif
3229

33-
if (change_in_bytes < 0) {
34-
return envObject.setLastError(napi_status.napi_invalid_arg)
35-
}
36-
37-
if (change_in_bytes > 0) {
38-
const old_size = wasmMemory.buffer.byteLength
39-
const new_size = old_size + change_in_bytes
40-
if (!_emscripten_resize_heap(new_size)) {
41-
return envObject.setLastError(napi_status.napi_generic_failure)
42-
}
43-
}
30+
const adjusted_memory = emnapiCtx.adjustAmountOfExternalAllocatedMemory(change_in_bytes)
4431

4532
// #if WASM_BIGINT
4633
from64('high')
4734
if (emnapiCtx.features.BigInt) {
48-
makeSetValue('high', 0, 'wasmMemory.buffer.byteLength', 'i64')
35+
makeSetValue('high', 0, 'adjusted_memory', 'i64')
4936
} else {
50-
emnapiSetValueI64(high, wasmMemory.buffer.byteLength)
37+
emnapiSetValueI64(high, Number(adjusted_memory))
5138
}
5239
// #else
5340
from64('adjusted_value')
54-
makeSetValue('adjusted_value', 0, 'wasmMemory.buffer.byteLength', 'i64')
41+
makeSetValue('adjusted_value', 0, 'adjusted_memory', 'i64')
5542
// #endif
5643

5744
return envObject.clearLastError()

packages/runtime/src/Context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ export class Context {
249249
return this.isolate.createResolver<T>()
250250
}
251251

252+
public adjustAmountOfExternalAllocatedMemory (changeInBytes: number | bigint): bigint {
253+
return this.isolate.adjustAmountOfExternalAllocatedMemory(changeInBytes)
254+
}
255+
252256
public createEnv (
253257
filename: string,
254258
moduleApiVersion: number,
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const kMaxReasonableBytes = BigInt(1) << BigInt(60)
2+
const kMinReasonableBytes = -kMaxReasonableBytes
3+
4+
export class ExternalMemory {
5+
total: bigint
6+
onChange: ((current: bigint, old: bigint, delta: bigint) => any) | null
7+
8+
constructor (onChange?: (current: bigint, old: bigint, delta: bigint) => any) {
9+
this.total = BigInt(0)
10+
this.onChange = onChange ?? null
11+
}
12+
13+
adjust (changeInBytes: number | bigint): bigint {
14+
changeInBytes = BigInt(changeInBytes)
15+
if (!(kMinReasonableBytes <= changeInBytes && changeInBytes < kMaxReasonableBytes)) {
16+
throw new RangeError(`changeInBytes ${changeInBytes} is out of reasonable range`)
17+
}
18+
const old = this.total
19+
this.total += changeInBytes
20+
const amount = this.total
21+
const onChange = this.onChange
22+
if (changeInBytes) {
23+
onChange?.(amount, old, changeInBytes)
24+
}
25+
return amount
26+
}
27+
}

packages/runtime/src/Isolate.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import { detectFeatures, type Resolver, type Features } from './util'
88
import type { HandleScope, ICallbackInfo } from './HandleScope'
99
import { External, getExternalValue, isExternal } from './External'
1010
import { deletePrivate, getPrivate, hasPrivate, Private, setPrivate } from './Private'
11+
import { ExternalMemory } from './ExternalMemory'
1112

1213
export interface IsolateOptions {
1314
features?: Partial<Features>
15+
onExternalMemoryChange?: (current: bigint, old: bigint, delta: bigint) => any
1416
}
1517

1618
export class Isolate {
1719
private _lastException
1820
private _globalThis: typeof globalThis
1921
private _scopeStore: ScopeStore
2022
private _handleStore: HandleStore
23+
private _externalMemory: ExternalMemory
2124
/** @internal */
2225
public globalHandleStore: PersistentStore
2326
public readonly features: Features
@@ -29,6 +32,7 @@ export class Isolate {
2932
this._handleStore = new HandleStore(this.features)
3033
this.globalHandleStore = new PersistentStore()
3134
this._lastException = new Persistent<any>(this)
35+
this._externalMemory = new ExternalMemory(options?.onExternalMemoryChange)
3236
}
3337

3438
//#region Local handles
@@ -125,6 +129,12 @@ export class Isolate {
125129
}
126130
//#endregion
127131

132+
//#region External Memory
133+
public adjustAmountOfExternalAllocatedMemory (changeInBytes: number | bigint): bigint {
134+
return this._externalMemory.adjust(changeInBytes)
135+
}
136+
//#endregion
137+
128138
//#region Templates
129139
public createSignature (template: FunctionTemplate): Signature {
130140
return new Signature(template)

0 commit comments

Comments
 (0)