implemented 'push quirky' switch for fine grained control over the ex… (#22318)

* implemented 'push quirky' switch for fine grained control over the exception handling overhead

* documentation
This commit is contained in:
Andreas Rumpf
2023-07-23 13:39:58 +02:00
committed by GitHub
parent 62869a5c68
commit be1844541c
10 changed files with 102 additions and 31 deletions

View File

@@ -458,6 +458,9 @@
- `=wasMoved` can now be overridden by users.
- There is a new pragma called [quirky](https://nim-lang.github.io/Nim/manual_experimental.html#quirky-routines) that can be used to affect the code
generation of goto based exception handling. It can improve the produced code size but its effects can be subtle so use it with care.
- Tuple unpacking for variables is now treated as syntax sugar that directly
expands into multiple assignments. Along with this, tuple unpacking for
variables can now be nested.

View File

@@ -315,7 +315,7 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) =
var targetProc = p
var valueAsRope = ""
potentialValueInit(p, v, value, valueAsRope)
if sfGlobal in v.flags:
if sfGlobal in v.flags:
if v.flags * {sfImportc, sfExportc} == {sfImportc} and
value.kind == nkEmpty and
v.loc.flags * {lfHeader, lfNoDecl} != {}:
@@ -1050,7 +1050,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
expr(p, t[0], d)
endBlock(p)
# First pass: handle Nim based exceptions:
# First pass: handle Nim based exceptions:
lineCg(p, cpsStmts, "catch (#Exception* T$1_) {$n", [etmp+1])
genRestoreFrameAfterException(p)
# an unhandled exception happened!
@@ -1308,7 +1308,8 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type"
if optTinyRtti in p.config.globalOptions:
let checkFor = $getObjDepth(t[i][j].typ)
appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$1, $2, $3)", [memberName, checkFor, $genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config)))])
appcg(p.module, orExpr, "#isObjDisplayCheck(#nimBorrowCurrentException()->$1, $2, $3)",
[memberName, checkFor, $genDisplayElem(MD5Digest(hashType(t[i][j].typ, p.config)))])
else:
let checkFor = genTypeInfoV1(p.module, t[i][j].typ, t[i][j].info)
appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor])

View File

@@ -619,7 +619,7 @@ proc treatGlobalDifferentlyForHCR(m: BModule, s: PSym): bool =
# and s.owner.kind == skModule # owner isn't always a module (global pragma on local var)
# and s.loc.k == locGlobalVar # loc isn't always initialized when this proc is used
proc genGlobalVarDecl(p: BProc, n: PNode; td, value: Rope; decl: var Rope) =
proc genGlobalVarDecl(p: BProc, n: PNode; td, value: Rope; decl: var Rope) =
let s = n.sym
if s.constraint.isNil:
if s.kind in {skLet, skVar, skField, skForVar} and s.alignment > 0:
@@ -640,7 +640,7 @@ proc genGlobalVarDecl(p: BProc, n: PNode; td, value: Rope; decl: var Rope) =
else:
decl = runtimeFormat(s.cgDeclFrmt & ";$n", [td, s.loc.r])
proc genCppVarForCtor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope)
proc genCppVarForCtor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope)
proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode) =
let s = vn.sym
@@ -701,7 +701,7 @@ proc assignGlobalVar(p: BProc, n: PNode; value: Rope) =
decl.addf(" $1 = $2;$n", [s.loc.r, value])
else:
decl.addf(" $1;$n", [s.loc.r])
p.module.s[cfsVars].add(decl)
if p.withinLoop > 0 and value == "":
# fixes tests/run/tzeroarray:
@@ -1134,7 +1134,7 @@ proc getProcTypeCast(m: BModule, prc: PSym): Rope =
proc genProcBody(p: BProc; procBody: PNode) =
genStmts(p, procBody) # modifies p.locals, p.init, etc.
if {nimErrorFlagAccessed, nimErrorFlagDeclared} * p.flags == {nimErrorFlagAccessed}:
if {nimErrorFlagAccessed, nimErrorFlagDeclared, nimErrorFlagDisabled} * p.flags == {nimErrorFlagAccessed}:
p.flags.incl nimErrorFlagDeclared
p.blocks[0].sections[cpsLocals].add(ropecg(p.module, "NIM_BOOL* nimErr_;$n", []))
p.blocks[0].sections[cpsInit].add(ropecg(p.module, "nimErr_ = #nimErrorFlag();$n", []))
@@ -1178,7 +1178,7 @@ proc genProcAux*(m: BModule, prc: PSym) =
initLocalVar(p, res, immediateAsgn=false)
returnStmt = ropecg(p.module, "\treturn $1;$n", [rdLoc(res.loc)])
elif sfConstructor in prc.flags:
fillLoc(resNode.sym.loc, locParam, resNode, "this", OnHeap)
fillLoc(resNode.sym.loc, locParam, resNode, "this", OnHeap)
else:
fillResult(p.config, resNode, prc.typ)
assignParam(p, res, prc.typ[0])

View File

@@ -193,15 +193,15 @@ proc initBlock*(): TBlock =
result.sections[i] = newRopeAppender()
proc newProc*(prc: PSym, module: BModule): BProc =
new(result)
result.prc = prc
result.module = module
result.options = if prc != nil: prc.options
else: module.config.options
result.blocks = @[initBlock()]
result.nestedTryStmts = @[]
result.finallySafePoints = @[]
result.sigConflicts = initCountTable[string]()
result = BProc(
prc: prc,
module: module,
options: if prc != nil: prc.options
else: module.config.options,
blocks: @[initBlock()],
sigConflicts: initCountTable[string]())
if optQuirky in result.options:
result.flags = {nimErrorFlagDisabled}
proc newModuleList*(g: ModuleGraph): BModuleList =
BModuleList(typeInfoMarker: initTable[SigHash, tuple[str: Rope, owner: int32]](),

View File

@@ -156,3 +156,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasChecksums")
defineSymbol("nimHasSendable")
defineSymbol("nimAllowNonVarDestructor")
defineSymbol("nimHasQuirky")

View File

@@ -49,6 +49,7 @@ type # please make sure we have under 32 options
optSinkInference # 'sink T' inference
optCursorInference
optImportHidden
optQuirky
TOptions* = set[TOption]
TGlobalOption* = enum

View File

@@ -34,7 +34,7 @@ const
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
wGensym, wInject, wRaises, wEffectsOf, wTags, wForbids, wLocks, wDelegator, wGcSafe,
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
wRequires, wEnsures, wEnforceNoRaises, wSystemRaisesDefect, wVirtual}
wRequires, wEnsures, wEnforceNoRaises, wSystemRaisesDefect, wVirtual, wQuirky}
converterPragmas* = procPragmas
methodPragmas* = procPragmas+{wBase}-{wImportCpp}
templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
@@ -405,6 +405,7 @@ proc pragmaToOptions*(w: TSpecialWord): TOptions {.inline.} =
of wImplicitStatic: {optImplicitStatic}
of wPatterns, wTrMacros: {optTrMacros}
of wSinkInference: {optSinkInference}
of wQuirky: {optQuirky}
else: {}
proc processExperimental(c: PContext; n: PNode) =
@@ -1273,12 +1274,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
pragmaProposition(c, it)
of wEnsures:
pragmaEnsures(c, it)
of wEnforceNoRaises:
of wEnforceNoRaises, wQuirky:
sym.flags.incl sfNeverRaises
of wSystemRaisesDefect:
sym.flags.incl sfSystemRaisesDefect
of wVirtual:
processVirtual(c, it, sym)
processVirtual(c, it, sym)
else: invalidPragma(c, it)
elif comesFromPush and whichKeyword(ident) != wInvalid:

View File

@@ -89,6 +89,7 @@ type
wGuard = "guard", wLocks = "locks", wPartial = "partial", wExplain = "explain",
wLiftLocals = "liftlocals", wEnforceNoRaises = "enforceNoRaises", wSystemRaisesDefect = "systemRaisesDefect",
wRedefine = "redefine", wCallsite = "callsite",
wQuirky = "quirky",
wAuto = "auto", wBool = "bool", wCatch = "catch", wChar = "char",
wClass = "class", wCompl = "compl", wConstCast = "const_cast", wDefault = "default",

View File

@@ -2009,6 +2009,65 @@ The field is within a `case` section of an `object`.
is solid and it is expected that eventually this mode becomes the default in later versions.
Quirky routines
===============
The default code generation strategy of exceptions under the ARC/ORC model is the so called
`--exceptions:goto` implementation. This implementation inserts a check after every call that
can potentially raise an exception. A typical instruction sequence for this on
for a x86 64 bit machine looks like:
```
cmp DWORD PTR [rbx], 0
je .L1
```
This is a memory fetch followed by jump. (An ideal implementation would
use the carry flag and a single instruction like ``jc .L1``.)
This overhead might not be desired and depending on the sematics of the routine may not be required
either.
So it can be disabled via a `.quirky` annotation:
```nim
proc wontRaise(x: int) {.quirky.} =
if x != 0:
# because of `quirky` this will continue even if `write` raised an IO exception:
write x
wontRaise(x-1)
wontRaise 10
```
If the used exception model is not `--exceptions:goto` then the `quirky` pragma has no effect and is
ignored.
The `quirky` pragma can also be be pushed in order to affect a group of routines and whether
the compiler supports the pragma can be checked with `defined(nimHasQuirky)`:
```nim
when defined(nimHasQuirky):
{.push quirky: on.}
proc doRaise() = raise newException(ValueError, "")
proc f(): string = "abc"
proc q(cond: bool) =
if cond:
doRaise()
echo f()
q(true)
when defined(nimHasQuirky):
{.pop.}
```
**Warning**: The `quirky` pragma only affects code generation, no check for validity is performed!
Threading under ARC/ORC
=======================
@@ -2141,13 +2200,13 @@ Here's an example of how to use the virtual pragma:
```nim
proc newCpp*[T](): ptr T {.importcpp: "new '*0()".}
type
type
Foo = object of RootObj
FooPtr = ptr Foo
Boo = object of Foo
BooPtr = ptr Boo
proc salute(self: FooPtr) {.virtual.} =
proc salute(self: FooPtr) {.virtual.} =
echo "hello foo"
proc salute(self: BooPtr) {.virtual.} =
@@ -2177,13 +2236,13 @@ The return type can be referred to as `-> '0`, but this is optional and often no
#include <iostream>
class CppPrinter {
public:
virtual void printConst(char* message) const {
std::cout << "Const Message: " << message << std::endl;
}
virtual void printConstRef(char* message, const int& flag) const {
std::cout << "Const Ref Message: " << message << std::endl;
}
}
};
""".}
@@ -2194,7 +2253,7 @@ type
proc printConst(self: CppPrinter; message:cstring) {.importcpp.}
CppPrinter().printConst(message)
# override is optional.
# override is optional.
proc printConst(self: NimPrinter; message: cstring) {.virtual: "$1('2 #2) const override".} =
echo "NimPrinter: " & $message
@@ -2224,10 +2283,10 @@ proc makeFoo(x: int32): Foo {.constructor.} =
```
It forward declares the constructor in the type definition. When the constructor has parameters, it also generates a default constructor.
It forward declares the constructor in the type definition. When the constructor has parameters, it also generates a default constructor.
Notice, inside the body of the constructor one has access to `this` which is of the type `ptr Foo`. No `result` variable is available.
Like `virtual`, `constructor` also supports a syntax that allows to express C++ constraints.
Like `virtual`, `constructor` also supports a syntax that allows to express C++ constraints.
For example:
@@ -2242,11 +2301,11 @@ struct CppClass {
this->x = inX;
this->y = inY;
}
//CppClass() = default;
//CppClass() = default;
};
""".}
type
type
CppClass* {.importcpp, inheritable.} = object
x: int32
y: int32
@@ -2256,11 +2315,11 @@ proc makeNimClass(x: int32): NimClass {.constructor:"NimClass('1 #1) : CppClass(
this.x = x
# Optional: define the default constructor explicitly
proc makeCppClass(): NimClass {.constructor: "NimClass() : CppClass(0, 0)".} =
proc makeCppClass(): NimClass {.constructor: "NimClass() : CppClass(0, 0)".} =
this.x = 1
```
In the example above `CppClass` has a deleted default constructor. Notice how by using the constructor syntax, one can call the appropiate constructor.
In the example above `CppClass` has a deleted default constructor. Notice how by using the constructor syntax, one can call the appropiate constructor.
Notice when calling a constructor in the section of a global variable initialization, it will be called before `NimMain` meaning Nim is not fully initialized.

View File

@@ -481,9 +481,13 @@ They are:
5. nl_types. No headers for this.
6. As mmap is not supported, the nimAllocPagesViaMalloc option has to be used.
DLL generation
==============
**Note**: The same rules apply to `lib*.so` shared object files on UNIX. For better
readability only the DLL version is decribed here.
Nim supports the generation of DLLs. However, there must be only one
instance of the GC per process/address space. This instance is contained in
``nimrtl.dll``. This means that every generated Nim DLL depends