Skip to content

Commit 4113230

Browse files
committed
fix!: napi_adjust_external_memory no longer grow wasm memory (#207)
1 parent 61c497e commit 4113230

5 files changed

Lines changed: 55 additions & 42 deletions

File tree

packages/emnapi/src/core/memory.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable @typescript-eslint/indent */
22

33
import { emnapiCtx } from 'emnapi:shared'
4-
import { wasmMemory } from 'emscripten:runtime'
54
import { from64, makeSetValue } from 'emscripten:parse-tools'
65
import { $CHECK_ENV, $CHECK_ARG } from '../macro'
76
import { $emnapiSetValueI64 as emnapiSetValueI64 } from '../util'
@@ -16,24 +15,14 @@ export function napi_adjust_external_memory (
1615
const envObject = emnapiCtx.envStore.get(env)!
1716
$CHECK_ARG!(envObject, adjusted_value)
1817

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

3221
from64('adjusted_value')
3322
if (emnapiCtx.feature.supportBigInt) {
34-
makeSetValue('adjusted_value', 0, 'wasmMemory.buffer.byteLength', 'i64')
23+
makeSetValue('adjusted_value', 0, 'adjusted_memory', 'i64')
3524
} else {
36-
emnapiSetValueI64(adjusted_value, wasmMemory.buffer.byteLength)
25+
emnapiSetValueI64(adjusted_value, Number(adjusted_memory))
3726
}
3827

3928
return envObject.clearLastError()

packages/emnapi/src/emscripten/memory.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
import { wasmMemory } from 'emscripten:runtime'
1+
/* eslint-disable @typescript-eslint/indent */
22
import { emnapiCtx } from 'emnapi:shared'
33
import { from64, makeSetValue } from 'emscripten:parse-tools'
44
import { $emnapiSetValueI64 as emnapiSetValueI64 } from '../util'
55
import { $CHECK_ENV } from '../macro'
66

7-
/* eslint-disable @typescript-eslint/indent */
8-
declare function _emscripten_resize_heap (requested: number): boolean
9-
107
/**
11-
* @__deps emscripten_resize_heap
128
* @__sig ipjp
139
*/
1410
export function napi_adjust_external_memory (
@@ -20,38 +16,28 @@ export function napi_adjust_external_memory (
2016
$CHECK_ENV!(env)
2117
const envObject = emnapiCtx.envStore.get(env)!
2218

23-
let change_in_bytes: number
19+
let change_in_bytes: bigint
2420

2521
// #if WASM_BIGINT
2622
if (!high) return envObject.setLastError(napi_status.napi_invalid_arg)
27-
change_in_bytes = Number(low)
23+
change_in_bytes = BigInt(low)
2824
// #else
2925
if (!adjusted_value) return envObject.setLastError(napi_status.napi_invalid_arg)
30-
change_in_bytes = (low >>> 0) + (high * Math.pow(2, 32))
26+
change_in_bytes = BigInt(low >>> 0) + (BigInt(high) << BigInt(32))
3127
// #endif
3228

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-
}
29+
const adjusted_memory = emnapiCtx.adjustAmountOfExternalAllocatedMemory(change_in_bytes)
4430

4531
// #if WASM_BIGINT
4632
from64('high')
4733
if (emnapiCtx.feature.supportBigInt) {
48-
makeSetValue('high', 0, 'wasmMemory.buffer.byteLength', 'i64')
34+
makeSetValue('high', 0, 'adjusted_memory', 'i64')
4935
} else {
50-
emnapiSetValueI64(high, wasmMemory.buffer.byteLength)
36+
emnapiSetValueI64(high, Number(adjusted_memory))
5137
}
5238
// #else
5339
from64('adjusted_value')
54-
makeSetValue('adjusted_value', 0, 'wasmMemory.buffer.byteLength', 'i64')
40+
makeSetValue('adjusted_value', 0, 'adjusted_memory', 'i64')
5541
// #endif
5642

5743
return envObject.clearLastError()

packages/runtime/src/Context.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { Reference, ReferenceWithData, ReferenceWithFinalizer, type ReferenceOwn
2424
import { type IDeferrdValue, Deferred } from './Deferred'
2525
import { Store } from './Store'
2626
import { TrackedFinalizer } from './TrackedFinalizer'
27+
import { ExternalMemory } from './ExternalMemory'
2728

2829
export type CleanupHookCallbackFunction = number | ((arg: number) => void)
2930

@@ -110,6 +111,10 @@ class NodejsWaitingRequestCounter {
110111
}
111112
}
112113

114+
export interface ContextOptions {
115+
onExternalMemoryChange?: (current: bigint, old: bigint, delta: bigint) => any
116+
}
117+
113118
export class Context {
114119
private _isStopping = false
115120
private _canCallIntoJs = true
@@ -122,6 +127,7 @@ export class Context {
122127
public handleStore = new HandleStore()
123128
private readonly refCounter?: NodejsWaitingRequestCounter
124129
private readonly cleanupQueue: CleanupQueue
130+
private readonly _externalMemory: ExternalMemory
125131

126132
public feature = {
127133
supportReflect,
@@ -135,8 +141,9 @@ export class Context {
135141
MessageChannel: _MessageChannel
136142
}
137143

138-
public constructor () {
144+
public constructor (options?: ContextOptions) {
139145
this.cleanupQueue = new CleanupQueue()
146+
this._externalMemory = new ExternalMemory(options?.onExternalMemoryChange)
140147
if (typeof process === 'object' && process !== null && typeof process.once === 'function') {
141148
this.refCounter = new NodejsWaitingRequestCounter()
142149
process.once('beforeExit', () => {
@@ -231,7 +238,11 @@ export class Context {
231238
return Deferred.create(this, value)
232239
}
233240

234-
createEnv (
241+
public adjustAmountOfExternalAllocatedMemory (changeInBytes: number | bigint): bigint {
242+
return this._externalMemory.adjust(changeInBytes)
243+
}
244+
245+
public createEnv (
235246
filename: string,
236247
moduleApiVersion: number,
237248
makeDynCall_vppp: (cb: Ptr) => (a: Ptr, b: Ptr, c: Ptr) => void,
@@ -331,8 +342,8 @@ export class Context {
331342

332343
let defaultContext: Context
333344

334-
export function createContext (): Context {
335-
return new Context()
345+
export function createContext (options?: ContextOptions): Context {
346+
return new Context(options)
336347
}
337348

338349
export function getDefaultContext (): Context {
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/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { createContext, getDefaultContext, Context, type CleanupHookCallbackFunction } from './Context'
1+
export { createContext, getDefaultContext, Context, type CleanupHookCallbackFunction, type ContextOptions } from './Context'
22
export { Deferred, type IDeferrdValue } from './Deferred'
33
export { Env, NodeEnv, type IReferenceBinding } from './env'
44
export { EmnapiError, NotSupportWeakRefError, NotSupportBufferError } from './errors'

0 commit comments

Comments
 (0)