summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2023-07-23 13:39:58 +0200
committerGitHub <noreply@github.com>2023-07-23 13:39:58 +0200
commitbe1844541c87a132ca076d8a8f741bec01825ba1 (patch)
tree3579a23d29d0dae95d765e7e3e24f888690132bc
parent62869a5c68e4dd91e00ee77b039f0175482ef4fa (diff)
downloadNim-be1844541c87a132ca076d8a8f741bec01825ba1.tar.gz
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
-rw-r--r--changelogs/changelog_2_0_0_details.md3
-rw-r--r--compiler/ccgstmts.nim7
-rw-r--r--compiler/cgen.nim10
-rw-r--r--compiler/cgendata.nim18
-rw-r--r--compiler/condsyms.nim1
-rw-r--r--compiler/options.nim1
-rw-r--r--compiler/pragmas.nim7
-rw-r--r--compiler/wordrecg.nim1
-rw-r--r--doc/manual_experimental.md81
-rw-r--r--doc/nimc.md4
10 files changed, 102 insertions, 31 deletions
diff --git a/changelogs/changelog_2_0_0_details.md b/changelogs/changelog_2_0_0_details.md
index 8f9e7afd0..e3895639a 100644
--- a/changelogs/changelog_2_0_0_details.md
+++ b/changelogs/changelog_2_0_0_details.md
@@ -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.
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index f536a82c1..319738981 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -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])
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 0450625fc..ed149ed0e 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -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])
diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim
index e1309e0fd..4d15cf131 100644
--- a/compiler/cgendata.nim
+++ b/compiler/cgendata.nim
@@ -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]](),
diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim
index 12634248c..c68050449 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -156,3 +156,4 @@ proc initDefines*(symbols: StringTableRef) =
   defineSymbol("nimHasChecksums")
   defineSymbol("nimHasSendable")
   defineSymbol("nimAllowNonVarDestructor")
+  defineSymbol("nimHasQuirky")
diff --git a/compiler/options.nim b/compiler/options.nim
index d3cf71d4f..8286a575d 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -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
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 9e4a0052d..0d95f596c 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -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:
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 21b097075..f784f0a75 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -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",
diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md
index 54354f92b..602ca46a5 100644
--- a/doc/manual_experimental.md
+++ b/doc/manual_experimental.md
@@ -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.
diff --git a/doc/nimc.md b/doc/nimc.md
index 7c42c7c1b..9c6ea7033 100644
--- a/doc/nimc.md
+++ b/doc/nimc.md
@@ -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