js: replace push.apply with for loop for string add [backport] (#25267)

While `a.push.apply(a, b)` is better for performance than the previous
`a = a.concat(b)` due to the fact that it doesn't create a new array,
there is a pretty big problem with it: depending on the JS engine, if
the second array is too long, it can [cause a
crash](https://tanaikech.github.io/2020/04/20/limitation-of-array.prototype.push.apply-under-v8-for-google-apps-script/)
due to the function `push` taking too many arguments. This has
unfortunately been what the codegen produces since 1.4.0 (commit
707367e1ca).

So string addition is now moved to a compilerproc that just uses a `for`
loop. From what I can tell this is the most compatible and the fastest.
Only potential problem compared to `concat` etc is with aliasing, i.e.
adding an array to itself, but I'm guessing it's enough that the length
from before the iteration is used, since it can only grow. The test
checks for aliased nim strings but I don't know if there's an extra
protection for them.

(cherry picked from commit 839cbeb371)
This commit is contained in:
metagn
2025-11-07 15:19:50 +03:00
committed by narimiran
parent be6e195585
commit 605180fcfa
3 changed files with 37 additions and 7 deletions

View File

@@ -2333,8 +2333,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
r.res = "if (null != $1) { if (null == $2) $2 = $3; else $2 += $3; }" %
[b, lhs.rdLoc, tmp]
else:
let (a, tmp) = maybeMakeTemp(p, n[1], lhs)
r.res = "$1.push.apply($3, $2);" % [a, rhs.rdLoc, tmp]
useMagic(p, "nimAddStrStr")
r.res = "nimAddStrStr($1, $2);" % [lhs.rdLoc, rhs.rdLoc]
r.kind = resExpr
of mAppendSeqElem:
var x, y: TCompRes = default(TCompRes)

View File

@@ -687,6 +687,14 @@ proc isObj(obj, subclass: PNimType): bool {.compilerproc.} =
proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} =
{.emit: "`x`.push(`c`);".}
proc nimAddStrStr(x, y: string) {.compilerproc, asmNoStackFrame.} =
{.emit: """
var L = `y`.length;
for (var i = 0; i < L; ++i) {
`x`.push(`y`[i]);
}
""".}
{.pop.}
proc tenToThePowerOf(b: int): BiggestFloat =

View File

@@ -1,11 +1,33 @@
discard """
output: "DabcD"
targets: "c cpp js"
output: '''
DabcD
(8192, 8, 1024)
'''
"""
const
x = "abc"
import std/assertions
var v = "D" & x & "D"
block:
const
x = "abc"
echo v
var v = "D" & x & "D"
doAssert v == "DabcD"
echo v
block: # test large additions
var a = "abcdefgh"
let initialLen = a.len
let times = 10
for i in 1..times:
let start = a.len
a.add(a)
doAssert a.len == 2 * start
let multiplier = 1 shl times
doAssert a.len == initialLen * multiplier
echo (a.len, initialLen, multiplier)
for i in 1 ..< multiplier:
for j in 0 ..< initialLen:
doAssert a[j] == a[i * initialLen + j]