mirror of
https://github.com/nim-lang/Nim.git
synced 2026-05-24 21:59:52 +00:00
SSO: better switch to enable it (#25772)
This commit is contained in:
@@ -230,11 +230,11 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
|
||||
of tyString, tySequence:
|
||||
let atyp = skipTypes(a.t, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and atyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
skipTypes(a.t, abstractVar + abstractInst).kind == tyString:
|
||||
let strPtr = if atyp.kind in {tyVar} and not compileToCpp(p.module): ra
|
||||
else: addrLoc(p.config, a)
|
||||
@@ -296,11 +296,11 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
of tyString, tySequence:
|
||||
let ntyp = skipTypes(n.typ, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
skipTypes(n.typ, abstractVar + abstractInst).kind == tyString:
|
||||
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
let ra = a.rdLoc
|
||||
@@ -335,7 +335,7 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
let ra = a.rdLoc
|
||||
var t = TLoc(snippet: cDeref(ra))
|
||||
let lt = lenExpr(p, t)
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
|
||||
result.addArgumentSeparator()
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrLen"), t.snippet))
|
||||
@@ -370,7 +370,7 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
|
||||
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
|
||||
var a = initLocExpr(p, n[0])
|
||||
let tmp = withTmpIfNeeded(p, a, needsTmp)
|
||||
let ra = if p.config.isDefined("nimsso"): byRefLoc(p, tmp) else: tmp.rdLoc
|
||||
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
|
||||
result.addCall(cgsymValue(p.module, "nimToCStringConv"), ra)
|
||||
|
||||
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =
|
||||
|
||||
@@ -322,7 +322,7 @@ proc genOpenArrayConv(p: BProc; d: TLoc; a: TLoc; flags: TAssignmentFlags) =
|
||||
bra)
|
||||
let rd = d.rdLoc
|
||||
let la = lenExpr(p, a)
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
|
||||
cCall(cgsymValue(p.module, "nimStrData"), bra))
|
||||
@@ -963,7 +963,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc cowBracket(p: BProc; n: PNode) =
|
||||
if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions and
|
||||
not p.config.isDefined("nimsso"):
|
||||
not p.config.usesSso():
|
||||
let strCandidate = n[0]
|
||||
if strCandidate.typ.skipTypes(abstractInst).kind == tyString:
|
||||
var a: TLoc = initLocExpr(p, strCandidate)
|
||||
@@ -989,7 +989,7 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) =
|
||||
# bug #19497
|
||||
d.lode = e
|
||||
else:
|
||||
let ssoStrSub = p.config.isDefined("nimsso") and e[0].kind == nkBracketExpr and
|
||||
let ssoStrSub = p.config.usesSso() and e[0].kind == nkBracketExpr and
|
||||
e[0][0].typ.skipTypes(abstractVar).kind == tyString
|
||||
var a: TLoc = initLocExpr(p, e[0], if ssoStrSub: {lfEnforceDeref, lfPrepareForMutation} else: {})
|
||||
if e[0].kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv} and not ignoreConv(e[0]):
|
||||
@@ -1318,7 +1318,7 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) =
|
||||
if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
|
||||
a.snippet = cDeref(a.snippet)
|
||||
|
||||
if p.config.isDefined("nimsso") and ty.kind == tyString:
|
||||
if p.config.usesSso() and ty.kind == tyString:
|
||||
let bra = byRefLoc(p, a)
|
||||
if lfPrepareForMutation in d.flags:
|
||||
# Use nimStrAtMutV3 to get a mutable reference (char*) to the element.
|
||||
@@ -2150,7 +2150,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
|
||||
putIntoDest(p, b, e, ra & cArgumentSeparator & ra & "Len_0", a.storage)
|
||||
of tyString, tySequence:
|
||||
let la = lenExpr(p, a)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
skipTypes(a.t, abstractVarRange).kind == tyString:
|
||||
let bra = byRefLoc(p, a)
|
||||
putIntoDest(p, b, e,
|
||||
@@ -2743,7 +2743,7 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
|
||||
var a: TLoc = initLocExpr(p, n[0])
|
||||
let arg = if p.config.isDefined("nimsso"): byRefLoc(p, a) else: rdLoc(a)
|
||||
let arg = if p.config.usesSso(): byRefLoc(p, a) else: rdLoc(a)
|
||||
putIntoDest(p, d, n,
|
||||
cgCall(p, "nimToCStringConv", arg),
|
||||
a.storage)
|
||||
@@ -2822,7 +2822,7 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
|
||||
var src: TLoc = initLocExpr(p, n[2])
|
||||
let destVal = rdLoc(a)
|
||||
let srcVal = rdLoc(src)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
n[1].typ.skipTypes(abstractVar).kind == tyString:
|
||||
# SmallString: destroy dst then struct-copy src; no .p field aliasing needed
|
||||
genStmts(p, n[3])
|
||||
@@ -2871,7 +2871,7 @@ proc genDestroy(p: BProc; n: PNode) =
|
||||
case t.kind
|
||||
of tyString:
|
||||
var a: TLoc = initLocExpr(p, arg)
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
# SmallString: delegate to nimDestroyStrV1 (rc-based, handles static strings)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimDestroyStrV1"), rdLoc(a))
|
||||
else:
|
||||
@@ -4243,7 +4243,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul
|
||||
genConstObjConstr(p, n, isConst, result)
|
||||
of tyString, tyCstring:
|
||||
if optSeqDestructors in p.config.globalOptions and n.kind != nkNilLit and ty == tyString:
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
genStringLiteralV3Const(p.module, n, isConst, result)
|
||||
else:
|
||||
genStringLiteralV2Const(p.module, n, isConst, result)
|
||||
|
||||
@@ -22,7 +22,7 @@ template detectVersion(field, corename) =
|
||||
result = 1
|
||||
|
||||
proc detectStrVersion(m: BModule): int =
|
||||
if m.g.config.isDefined("nimsso") and
|
||||
if m.g.config.usesSso() and
|
||||
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
|
||||
result = 3
|
||||
else:
|
||||
|
||||
@@ -1940,7 +1940,7 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
|
||||
elif optFieldCheck in p.options and isDiscriminantField(e[0]):
|
||||
genLineDir(p, e)
|
||||
asgnFieldDiscriminant(p, e)
|
||||
elif p.config.isDefined("nimsso") and e[0].kind == nkBracketExpr and
|
||||
elif p.config.usesSso() and e[0].kind == nkBracketExpr and
|
||||
e[0][0].typ.skipTypes(abstractVar).kind == tyString:
|
||||
# nimsso: s[i] = c → nimStrPutV3(&s, i, c) (handles COW internally)
|
||||
genLineDir(p, e)
|
||||
|
||||
@@ -389,7 +389,7 @@ proc lenField(p: BProc, val: Rope): Rope {.inline.} =
|
||||
|
||||
proc lenExpr(p: BProc; a: TLoc): Rope =
|
||||
if optSeqDestructors in p.config.globalOptions:
|
||||
if p.config.isDefined("nimsso") and a.lode != nil and a.t != nil and
|
||||
if p.config.usesSso() and a.lode != nil and a.t != nil and
|
||||
a.t.skipTypes(abstractInst).kind == tyString:
|
||||
result = cCall(cgsymValue(p.module, "nimStrLen"), rdLoc(a))
|
||||
else:
|
||||
@@ -534,7 +534,7 @@ proc resetLoc(p: BProc, loc: var TLoc) =
|
||||
|
||||
let atyp = skipTypes(loc.t, abstractInst)
|
||||
let rl = rdLoc(loc)
|
||||
if typ.kind == tyString and p.config.isDefined("nimsso"):
|
||||
if typ.kind == tyString and p.config.usesSso():
|
||||
# SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed)
|
||||
if atyp.kind in {tyVar, tyLent}:
|
||||
p.s(cpsStmts).addAssignment(derefField(rl, "bytes"), cIntValue(0))
|
||||
@@ -592,7 +592,7 @@ proc constructLoc(p: BProc, loc: var TLoc, isTemp = false) =
|
||||
let typ = loc.t
|
||||
if optSeqDestructors in p.config.globalOptions and skipTypes(typ, abstractInst + {tyStatic}).kind in {tyString, tySequence}:
|
||||
let rl = rdLoc(loc)
|
||||
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.isDefined("nimsso"):
|
||||
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.usesSso():
|
||||
# SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed)
|
||||
p.s(cpsStmts).addFieldAssignment(rl, "bytes", cIntValue(0))
|
||||
p.s(cpsStmts).addFieldAssignment(rl, "more", NimNil)
|
||||
|
||||
@@ -250,6 +250,7 @@ const
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
|
||||
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
|
||||
|
||||
template warningOptionNoop(switch: string) =
|
||||
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
|
||||
@@ -306,6 +307,13 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errInvalidExceptionSystem % arg)
|
||||
of "strings":
|
||||
case arg.normalize
|
||||
of "default": result = conf.selectedStrings == stringDefault
|
||||
of "sso": result = conf.selectedStrings == stringSso
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "experimental":
|
||||
try:
|
||||
result = conf.features.contains parseEnum[Feature](arg)
|
||||
@@ -750,6 +758,17 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "mm":
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "strings":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "default":
|
||||
conf.selectedStrings = stringDefault
|
||||
of "sso":
|
||||
conf.selectedStrings = stringSso
|
||||
defineSymbol(conf.symbols, "nimsso")
|
||||
else:
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "warnings", "w":
|
||||
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
|
||||
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
|
||||
|
||||
@@ -732,7 +732,7 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedAsgn, attachedDeepCopy, attachedDup:
|
||||
body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
|
||||
of attachedSink:
|
||||
if c.g.config.isDefined("nimsso"):
|
||||
if c.g.config.usesSso():
|
||||
# SmallString: destroy old dst, then bit-copy src (no rc increment — this is a move).
|
||||
# No .p aliasing check needed; rc-based destroy handles COW sharing correctly.
|
||||
doAssert t.destructor != nil
|
||||
|
||||
@@ -121,6 +121,11 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
|
||||
initOrcDefines(conf)
|
||||
|
||||
if conf.selectedStrings == stringSso and
|
||||
conf.selectedGC notin {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
rawMessage(conf, errGenerated,
|
||||
"--strings:sso requires --mm:arc, --mm:orc, --mm:yrc, or --mm:atomicArc")
|
||||
|
||||
mainCommand(graph)
|
||||
if conf.hasHint(hintGCStats): echo(GC_getStatistics())
|
||||
#echo(GC_getStatistics())
|
||||
|
||||
@@ -267,6 +267,10 @@ type
|
||||
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccBcc, ccVcc,
|
||||
ccTcc, ccEnv, ccIcl, ccIcc, ccClangCl, ccHipcc, ccNvcc
|
||||
|
||||
StringsMode* = enum
|
||||
stringDefault = "default"
|
||||
stringSso = "sso"
|
||||
|
||||
ExceptionSystem* = enum
|
||||
excNone, # no exception system selected yet
|
||||
excSetjmp, # setjmp based exception handling
|
||||
@@ -366,6 +370,7 @@ type
|
||||
implicitCmd*: bool # whether some flag triggered an implicit `command`
|
||||
selectedGC*: TGCMode # the selected GC (+)
|
||||
exc*: ExceptionSystem
|
||||
selectedStrings*: StringsMode
|
||||
hintProcessingDots*: bool # true for dots, false for filenames
|
||||
verbosity*: int # how verbose the compiler is
|
||||
numberOfProcessors*: int # number of processors
|
||||
@@ -698,6 +703,7 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
|
||||
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
|
||||
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
|
||||
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
|
||||
|
||||
template compilationCachePresent*(conf: ConfigRef): untyped =
|
||||
false
|
||||
|
||||
@@ -259,7 +259,7 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int =
|
||||
result = s.readDataStrImpl(s, buffer, slice)
|
||||
else:
|
||||
# fallback
|
||||
result = s.readData(beginStore(buffer, slice.b + 1 - slice.a, slice.a), slice.b + 1 - slice.a)
|
||||
result = s.readData(beginStore(buffer, buffer.len, slice.a), slice.b + 1 - slice.a)
|
||||
endStore(buffer)
|
||||
|
||||
template jsOrVmBlock(caseJsOrVm, caseElse: untyped): untyped =
|
||||
@@ -1226,7 +1226,7 @@ else: # after 1.3 or JS not defined
|
||||
jsOrVmBlock:
|
||||
buffer[slice.a..<slice.a+result] = s.data[s.pos..<s.pos+result]
|
||||
do:
|
||||
copyMem(beginStore(buffer, result, slice.a), readRawData(s.data, s.pos), result)
|
||||
copyMem(beginStore(buffer, buffer.len, slice.a), readRawData(s.data, s.pos), result)
|
||||
endStore(buffer)
|
||||
inc(s.pos, result)
|
||||
else:
|
||||
@@ -1267,16 +1267,16 @@ else: # after 1.3 or JS not defined
|
||||
var s = StringStream(s)
|
||||
if bufLen <= 0:
|
||||
return
|
||||
if s.pos + bufLen > s.data.len:
|
||||
setLen(s.data, s.pos + bufLen)
|
||||
when defined(js):
|
||||
if s.pos + bufLen > s.data.len:
|
||||
setLen(s.data, s.pos + bufLen)
|
||||
try:
|
||||
s.data[s.pos..<s.pos+bufLen] = cast[ptr string](buffer)[][0..<bufLen]
|
||||
except:
|
||||
raise newException(Defect, "could not write to string stream, " &
|
||||
"did you use a non-string buffer pointer?", getCurrentException())
|
||||
elif not defined(nimscript):
|
||||
copyMem(beginStore(s.data, bufLen, s.pos), buffer, bufLen)
|
||||
copyMem(beginStore(s.data, s.pos + bufLen, s.pos), buffer, bufLen)
|
||||
endStore(s.data)
|
||||
inc(s.pos, bufLen)
|
||||
|
||||
@@ -1346,7 +1346,7 @@ proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
|
||||
|
||||
proc fsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
|
||||
let len = slice.b + 1 - slice.a
|
||||
result = readBuffer(FileStream(s).f, beginStore(buffer, len, slice.a), len)
|
||||
result = readBuffer(FileStream(s).f, beginStore(buffer, buffer.len, slice.a), len)
|
||||
endStore(buffer)
|
||||
|
||||
proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
|
||||
|
||||
@@ -18,12 +18,12 @@ proc addCstringN(result: var string, buf: cstring; buflen: int) =
|
||||
# no nimvm support needed, so it doesn't need to be fast here either
|
||||
let oldLen = result.len
|
||||
let newLen = oldLen + buflen
|
||||
result.setLen newLen
|
||||
{.cast(noSideEffect).}:
|
||||
when declared(completeStore):
|
||||
c_memcpy(beginStore(result, buflen, oldLen), buf, buflen.csize_t)
|
||||
when declared(beginStore):
|
||||
c_memcpy(beginStore(result, newLen, oldLen), buf, buflen.csize_t)
|
||||
endStore(result)
|
||||
else:
|
||||
result.setLen newLen
|
||||
discard c_memcpy(result[oldLen].addr, buf, buflen.csize_t)
|
||||
|
||||
import std/private/[dragonbox, schubfach]
|
||||
|
||||
@@ -84,7 +84,7 @@ func setSlice*(s: var string, slice: Slice[int]) =
|
||||
when not declared(moveMem):
|
||||
impl()
|
||||
else:
|
||||
let p = beginStore(s, last - first + 1)
|
||||
let p = beginStore(s, s.len)
|
||||
moveMem(p, addr p[first], last - first + 1)
|
||||
endStore(s)
|
||||
s.setLen(last - first + 1)
|
||||
|
||||
@@ -485,7 +485,7 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
|
||||
while true:
|
||||
# fixes #9634; this pattern may need to be abstracted as a template if reused;
|
||||
# likely other io procs need this for correctness.
|
||||
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, sp, pos)), sp.cint, f) != nil
|
||||
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, pos + sp, pos)), sp.cint, f) != nil
|
||||
endStore(line)
|
||||
if fgetsSuccess: break
|
||||
when not defined(nimscript):
|
||||
|
||||
@@ -1703,7 +1703,8 @@ when not (notJSnotNims and defined(nimSeqsV2)):
|
||||
# Needed so modules imported by system (e.g. syncio) can reference these without guards.
|
||||
when notJSnotNims:
|
||||
# mm:refc: string = ptr NimStringDesc with data: UncheckedArray[char]
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
{.cast(noSideEffect).}: s.setLen(newLen)
|
||||
let ns = cast[NimString](s)
|
||||
if ns == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
|
||||
@@ -1714,7 +1715,7 @@ when not (notJSnotNims and defined(nimSeqsV2)):
|
||||
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
|
||||
else:
|
||||
# JS/nimscript: callers are guarded by whenNotVmJsNims/when not defined(js)
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} = discard
|
||||
template readRawData*(s: string; start = 0): ptr UncheckedArray[char] = nil
|
||||
|
||||
|
||||
@@ -236,13 +236,17 @@ func capacity*(self: string): int {.inline.} =
|
||||
let str = cast[ptr NimStringV2](unsafeAddr self)
|
||||
result = if str.p != nil: str.p.cap and not strlitFlag else: 0
|
||||
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Returns a writable pointer for bulk write of `ensuredLen` bytes starting at `start`.
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Sets s.len to `newLen` (new bytes are uninitialized), ensures unique
|
||||
## ownership, and returns a pointer to s[start] for bulk writing.
|
||||
## Call `endStore(s)` afterwards for portability.
|
||||
{.cast(noSideEffect).}: prepareMutation(s)
|
||||
let str = cast[ptr NimStringV2](unsafeAddr s)
|
||||
if str.p == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr str.p.data[start])
|
||||
## To keep the current length, pass `s.len`.
|
||||
{.cast(noSideEffect).}:
|
||||
let p = cast[ptr NimStringV2](addr s)
|
||||
setLengthStrV2Uninit(p[], newLen)
|
||||
prepareMutation(s)
|
||||
if p.p == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr p.p.data[start])
|
||||
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## No-op for non-SSO strings; call after bulk writes via `beginStore`.
|
||||
|
||||
@@ -504,18 +504,57 @@ proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
|
||||
let slen = ssLen(s)
|
||||
let curLen = if slen > PayloadSize: s.more.fullLen else: slen
|
||||
if newLen == curLen: return
|
||||
if newLen <= 0:
|
||||
# Pattern 's.setLen 0' is common for avoiding allocations; do NOT free the buffer.
|
||||
if newLen < curLen:
|
||||
# Shrinking:
|
||||
if slen > PayloadSize:
|
||||
if slen == HeapSlen and s.more.rc == 1:
|
||||
s.more.fullLen = 0
|
||||
s.more.data[0] = '\0'
|
||||
# Unique heap block: keep the buffer allocated to avoid alloc/dealloc
|
||||
# ping-pong when callers shrink then grow (e.g. setLen(0) + add loops).
|
||||
s.more.fullLen = newLen
|
||||
s.more.data[newLen] = '\0'
|
||||
else:
|
||||
# shared or static block: detach and go back to empty inline
|
||||
nimDestroyStrV1(s)
|
||||
s.bytes = 0 # slen=0, all inline chars zeroed
|
||||
# shared or static block: detach and go back to inline
|
||||
if newLen <= 0:
|
||||
nimDestroyStrV1(s)
|
||||
s.bytes = 0
|
||||
else:
|
||||
let old = s.more
|
||||
let inl = inlinePtr(s)
|
||||
copyMem(inl, addr old.data[0], newLen)
|
||||
inl[newLen] = '\0'
|
||||
if slen == HeapSlen and atomicSubFetch(old.rc, 1) == 0:
|
||||
dealloc(old)
|
||||
if newLen < AlwaysAvail:
|
||||
when system.cpuEndian == littleEndian:
|
||||
let keepBits = (newLen + 1) * 8
|
||||
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
|
||||
s.bytes = (s.bytes and charMask) or uint(newLen)
|
||||
else:
|
||||
let discardBits = (AlwaysAvail - newLen) * 8
|
||||
let slenBit = 8 * (sizeof(uint) - 1)
|
||||
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
|
||||
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
|
||||
else:
|
||||
setSSLen(s, newLen)
|
||||
else:
|
||||
s.bytes = 0 # slen=0, all inline chars zeroed (SWAR safe)
|
||||
# inline/medium shrink
|
||||
if newLen <= 0:
|
||||
s.bytes = 0
|
||||
else:
|
||||
let inl = inlinePtr(s)
|
||||
inl[newLen] = '\0'
|
||||
if newLen < AlwaysAvail:
|
||||
when system.cpuEndian == littleEndian:
|
||||
let keepBits = (newLen + 1) * 8
|
||||
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
|
||||
s.bytes = (s.bytes and charMask) or uint(newLen)
|
||||
else:
|
||||
let discardBits = (AlwaysAvail - newLen) * 8
|
||||
let slenBit = 8 * (sizeof(uint) - 1)
|
||||
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
|
||||
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
|
||||
else:
|
||||
setSSLen(s, newLen)
|
||||
return
|
||||
if slen <= PayloadSize:
|
||||
if newLen <= PayloadSize:
|
||||
@@ -564,34 +603,11 @@ proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
|
||||
s.more = p
|
||||
setSSLen(s, HeapSlen)
|
||||
else:
|
||||
# currently long
|
||||
if newLen <= PayloadSize:
|
||||
# shrink back to inline/medium
|
||||
let old = s.more
|
||||
let inl = inlinePtr(s)
|
||||
copyMem(inl, addr old.data[0], newLen)
|
||||
inl[newLen] = '\0'
|
||||
if slen == HeapSlen and atomicSubFetch(old.rc, 1) == 0:
|
||||
dealloc(old)
|
||||
# Zero padding bytes in `bytes` for SWAR invariant
|
||||
if newLen < AlwaysAvail:
|
||||
when system.cpuEndian == littleEndian:
|
||||
let keepBits = (newLen + 1) * 8
|
||||
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
|
||||
s.bytes = (s.bytes and charMask) or uint(newLen)
|
||||
else:
|
||||
let discardBits = (AlwaysAvail - newLen) * 8
|
||||
let slenBit = 8 * (sizeof(uint) - 1)
|
||||
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
|
||||
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
|
||||
else:
|
||||
setSSLen(s, newLen)
|
||||
else:
|
||||
# long -> long
|
||||
ensureUniqueLong(s, curLen, newLen) # sets fullLen = newLen
|
||||
if newLen > curLen:
|
||||
zeroMem(addr s.more.data[curLen], newLen - curLen)
|
||||
s.more.data[newLen] = '\0'
|
||||
# currently long: grow within the heap buffer (shrinking already returned above)
|
||||
ensureUniqueLong(s, curLen, newLen) # sets fullLen = newLen
|
||||
if zeroing and newLen > curLen:
|
||||
zeroMem(addr s.more.data[curLen], newLen - curLen)
|
||||
s.more.data[newLen] = '\0'
|
||||
|
||||
proc setLengthStrV2(s: var SmallString; newLen: int) {.compilerRtl.} =
|
||||
## Sets the length of `s` to `newLen`, zeroing new bytes on growth.
|
||||
@@ -705,18 +721,37 @@ proc completeStore(s: var SmallString) {.compilerproc, inline.} =
|
||||
proc completeStore*(s: var string) {.inline.} =
|
||||
completeStore(cast[ptr SmallString](addr s)[])
|
||||
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Prepares `s` for a bulk write of `ensuredLen` bytes starting at `start`.
|
||||
## The caller must ensure `s.len >= start + ensuredLen` (e.g. via `newString` or `setLen`).
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Sets s.len to `newLen` (new bytes are uninitialized), ensures unique
|
||||
## ownership, and returns a pointer to s[start] for bulk writing.
|
||||
## Call `endStore(s)` afterwards to sync the inline cache.
|
||||
## To keep the current length, pass `s.len`.
|
||||
{.cast(noSideEffect).}:
|
||||
let ss = cast[ptr SmallString](addr s)
|
||||
let slen = ssLen(ss[])
|
||||
if slen > PayloadSize:
|
||||
ensureUniqueLong(ss[], ss[].more.fullLen, ss[].more.fullLen)
|
||||
let curLen = if slen > PayloadSize: ss[].more.fullLen else: slen
|
||||
if newLen <= PayloadSize and slen <= PayloadSize:
|
||||
# Stay inline/medium.
|
||||
if newLen != curLen:
|
||||
setSSLen(ss[], newLen)
|
||||
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(start))
|
||||
elif slen <= PayloadSize:
|
||||
# Inline/medium → long.
|
||||
let newCap = resize(newLen)
|
||||
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
|
||||
p.rc = 1
|
||||
p.fullLen = newLen
|
||||
p.capImpl = newCap
|
||||
copyMem(addr p.data[0], inlinePtr(ss[]), curLen)
|
||||
p.data[newLen] = '\0'
|
||||
ss[].more = p
|
||||
setSSLen(ss[], HeapSlen)
|
||||
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
|
||||
else:
|
||||
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(start))
|
||||
# Already long: resize within heap (no transition back to inline).
|
||||
ensureUniqueLong(ss[], curLen, newLen)
|
||||
ss[].more.data[newLen] = '\0'
|
||||
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
|
||||
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Syncs the inline cache after bulk writes via `beginStore`. No-op for short/medium strings.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
matrix: "--backend:c --mm:refc; --backend:c --mm:orc; --backend:c --mm:orc -d:nimsso; --backend:cpp --mm:refc; --backend:cpp --mm:orc; --backend:js --mm:refc; --backend:js --mm:orc"
|
||||
matrix: "--backend:c --mm:refc; --backend:c --mm:orc; --backend:c --mm:orc --strings:sso; --backend:cpp --mm:refc; --backend:cpp --mm:orc; --backend:js --mm:refc; --backend:js --mm:orc"
|
||||
"""
|
||||
|
||||
from std/sequtils import toSeq, map
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
matrix: "-d:nimsso"
|
||||
matrix: "--strings:sso --mm:orc"
|
||||
targets: "c cpp"
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user