Files
Nim/tests/system/treadrawdatastable.nim
Andreas Rumpf 9d7c0cc683 SSO: add readRawDataStable across all string implementations (#25909)
Companion to readRawData whose pointer stays valid across moves/copies
of the string. Under --strings:sso it promotes a small inline string to
its heap representation; under refc/v2 the data is already heap-resident
so it aliases readRawData.

Uniform `var string` signature on every backend so code can prepare for
--strings:sso without `when declared`.
2026-06-13 19:27:22 +02:00

95 lines
3.7 KiB
Nim

discard """
matrix: "--mm:refc; --mm:orc; --mm:orc --strings:sso; --backend:cpp --mm:orc; --backend:js --mm:orc"
output: "OK"
"""
# Tests for `readRawDataStable` and the SSO static-long-string promotion path.
# `readRawDataStable` is available under every string implementation (refc / v2 /
# v3-sso / js) with the same signature, so the code below compiles unchanged on
# all backends -- the point being that users can prepare for `--strings:sso`
# without `when declared` hacks.
import std/assertions
const hasNativeSso = defined(nimsso) and
(defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc) or defined(gcYrc))
type
Reader = object
buf: string
p: ptr UncheckedArray[char]
proc openFromBuffer(buf: sink string): Reader =
# `result` (and thus `buf`) is moved into the caller on return. A plain
# `readRawData` pointer into a small SSO string would dangle after that move;
# `readRawDataStable` pins the buffer to a stable address first.
result = Reader(buf: buf)
result.p = readRawDataStable(result.buf)
proc testStable() =
when not defined(js): # raw pointers are a degenerate nil no-op on the JS backend
block: # short buffer (kept inline under SSO) survives the move
var r = openFromBuffer("hello")
doAssert r.buf == "hello"
doAssert r.p[0] == 'h'
doAssert r.p[4] == 'o'
# Stable pointer == the live buffer's raw data after the move.
doAssert cast[uint](r.p) == cast[uint](readRawData(r.buf))
block: # medium buffer (len 12: inline overlay under SSO)
var r = openFromBuffer("hello world!")
doAssert r.p[11] == '!'
block: # already-long buffer: returned as-is (already heap-resident)
var r = openFromBuffer("this is a fairly long string buffer")
doAssert r.p[0] == 't'
doAssert r.p[34] == 'r'
block: # empty string: API is callable (the data pointer is implementation-defined)
var e = ""
discard readRawDataStable(e)
else:
# On JS the API exists and is callable (returns nil) so call sites are portable.
var s = "hello"
discard readRawDataStable(s)
proc testStaticLongPromotion() =
# Regression for the static-long -> heap promotion: when a string literal
# longer than the inline payload (PayloadSize = 14 under SSO) is first
# mutated, the new heap block must be filled from the full static payload,
# not from the 7-byte inline hot-prefix cache. Reading from the cache copied
# 7 valid chars and then ran off into the `more` pointer bytes -- the bug that
# corrupted .nif index files on Windows bootstrap (see Nimony tstatic_long_add).
# The assertion holds on every backend; only SSO ever risked the corruption.
var content = "(.nif27)\n(index\n" # len 16
let expected = "(.nif27)\n(index\n"
content.add 'X' # triggers static-long -> heap promotion
doAssert content.len == 17
doAssert content == expected & "X"
for i in 0 ..< expected.len:
doAssert content[i] == expected[i]
when hasNativeSso:
# A few SSO-tier-boundary sanity checks (short / medium / long, COW, shrink).
proc testSsoTiers() =
var a = "(.nif27)\n(index\n" # static long
let b = "(.nif27)\n(index\n"
doAssert a == b
a.add 'Z'
doAssert a == "(.nif27)\n(index\nZ"
var c = "abcdefghijklmnop" # static long, len 16
var d = c # COW share
d[0] = 'X'
doAssert c == "abcdefghijklmnop" # original untouched
doAssert d == "Xbcdefghijklmnop"
var e = "abcdefghijklmnop"
e.setLen 3 # shrink below the inline cache size
doAssert e == "abc"
doAssert e.len == 3
else:
proc testSsoTiers() = discard
testStable()
testStaticLongPromotion()
testSsoTiers()
echo "OK"