Skip to content

Commit c29c1b7

Browse files
author
Jacob
committed
syscall/js: allocate arg slices on stack for small numbers of args
The existing implementation causes unnecessary heap allocations for javascript syscalls: Call, Invoke, and New. The new change seeks to hint the Go compiler to allocate arg slices with length <=16 to the stack. Fixes #39740
1 parent 7e98944 commit c29c1b7

File tree

1 file changed

+30
-6
lines changed

1 file changed

+30
-6
lines changed

src/syscall/js/js.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ func ValueOf(x any) Value {
211211
}
212212

213213
//go:wasmimport gojs syscall/js.stringVal
214+
//go:noescape
214215
func stringVal(x string) ref
215216

216217
// Type represents the JavaScript type of a Value.
@@ -295,6 +296,7 @@ func (v Value) Get(p string) Value {
295296
}
296297

297298
//go:wasmimport gojs syscall/js.valueGet
299+
//go:noescape
298300
func valueGet(v ref, p string) ref
299301

300302
// Set sets the JavaScript property p of value v to ValueOf(x).
@@ -310,6 +312,7 @@ func (v Value) Set(p string, x any) {
310312
}
311313

312314
//go:wasmimport gojs syscall/js.valueSet
315+
//go:noescape
313316
func valueSet(v ref, p string, x ref)
314317

315318
// Delete deletes the JavaScript property p of value v.
@@ -323,6 +326,7 @@ func (v Value) Delete(p string) {
323326
}
324327

325328
//go:wasmimport gojs syscall/js.valueDelete
329+
//go:noescape
326330
func valueDelete(v ref, p string)
327331

328332
// Index returns JavaScript index i of value v.
@@ -354,15 +358,29 @@ func (v Value) SetIndex(i int, x any) {
354358
//go:wasmimport gojs syscall/js.valueSetIndex
355359
func valueSetIndex(v ref, i int, x ref)
356360

357-
func makeArgs(args []any) ([]Value, []ref) {
358-
argVals := make([]Value, len(args))
359-
argRefs := make([]ref, len(args))
361+
func makeArgs(args []any) (argVals []Value, argRefs []ref) {
362+
// value chosen for being power of two, and enough to handle all web APIs
363+
// in particular, note that WebGL2's texImage2D takes up to 10 arguments
364+
const maxStackArgs = 16
365+
if len(args) <= maxStackArgs {
366+
// as long as makeArgs is inlined, these will be stack-allocated
367+
argVals = make([]Value, len(args), maxStackArgs)
368+
argRefs = make([]ref, len(args), maxStackArgs)
369+
} else {
370+
// allocates on the heap, but exceeding maxStackArgs should be rare
371+
argVals = make([]Value, len(args))
372+
argRefs = make([]ref, len(args))
373+
}
374+
return
375+
}
376+
377+
func storeArgs(args []any, argValsDst []Value, argRefsDst []ref) {
378+
// would go in makeArgs if the combined func was simple enough to inline
360379
for i, arg := range args {
361380
v := ValueOf(arg)
362-
argVals[i] = v
363-
argRefs[i] = v.ref
381+
argValsDst[i] = v
382+
argRefsDst[i] = v.ref
364383
}
365-
return argVals, argRefs
366384
}
367385

368386
// Length returns the JavaScript property "length" of v.
@@ -384,6 +402,7 @@ func valueLength(v ref) int
384402
// The arguments get mapped to JavaScript values according to the ValueOf function.
385403
func (v Value) Call(m string, args ...any) Value {
386404
argVals, argRefs := makeArgs(args)
405+
storeArgs(args, argVals, argRefs)
387406
res, ok := valueCall(v.ref, m, argRefs)
388407
runtime.KeepAlive(v)
389408
runtime.KeepAlive(argVals)
@@ -401,13 +420,15 @@ func (v Value) Call(m string, args ...any) Value {
401420

402421
//go:wasmimport gojs syscall/js.valueCall
403422
//go:nosplit
423+
//go:noescape
404424
func valueCall(v ref, m string, args []ref) (ref, bool)
405425

406426
// Invoke does a JavaScript call of the value v with the given arguments.
407427
// It panics if v is not a JavaScript function.
408428
// The arguments get mapped to JavaScript values according to the ValueOf function.
409429
func (v Value) Invoke(args ...any) Value {
410430
argVals, argRefs := makeArgs(args)
431+
storeArgs(args, argVals, argRefs)
411432
res, ok := valueInvoke(v.ref, argRefs)
412433
runtime.KeepAlive(v)
413434
runtime.KeepAlive(argVals)
@@ -421,13 +442,15 @@ func (v Value) Invoke(args ...any) Value {
421442
}
422443

423444
//go:wasmimport gojs syscall/js.valueInvoke
445+
//go:noescape
424446
func valueInvoke(v ref, args []ref) (ref, bool)
425447

426448
// New uses JavaScript's "new" operator with value v as constructor and the given arguments.
427449
// It panics if v is not a JavaScript function.
428450
// The arguments get mapped to JavaScript values according to the ValueOf function.
429451
func (v Value) New(args ...any) Value {
430452
argVals, argRefs := makeArgs(args)
453+
storeArgs(args, argVals, argRefs)
431454
res, ok := valueNew(v.ref, argRefs)
432455
runtime.KeepAlive(v)
433456
runtime.KeepAlive(argVals)
@@ -597,4 +620,5 @@ func CopyBytesToJS(dst Value, src []byte) int {
597620
}
598621

599622
//go:wasmimport gojs syscall/js.copyBytesToJS
623+
//go:noescape
600624
func copyBytesToJS(dst ref, src []byte) (int, bool)

0 commit comments

Comments
 (0)