Skip to content

Commit

Permalink
Merge branch 'nimworksdevel' into floatEquality
Browse files Browse the repository at this point in the history
  • Loading branch information
Clyybber committed Mar 18, 2024
2 parents 736f7eb + 8d0db1d commit 44fff33
Show file tree
Hide file tree
Showing 33 changed files with 410 additions and 177 deletions.
1 change: 1 addition & 0 deletions compiler/ast/report_enums.nim
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ type
rsemCallingConventionMismatch
rsemHasSideEffects
rsemCantPassProcvar
rsemHookCannotRaise
rsemUnlistedRaises
rsemUnlistedEffects
rsemOverrideSafetyMismatch
Expand Down
3 changes: 3 additions & 0 deletions compiler/front/cli_reporter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,9 @@ proc reportBody*(conf: ConfigRef, r: SemReport): string =
of rsemXCannotRaiseY:
result = "'$1' cannot raise '$2'" % [r.ast.render, r.raisesList.render]

of rsemHookCannotRaise:
result = "a hook routine is not allowed to raise. ($1)" % r.typ.render

of rsemUnlistedRaises, rsemWarnUnlistedRaises:
result.add("$1 can raise an unlisted exception: " % r.ast.render,
r.typ.render)
Expand Down
32 changes: 26 additions & 6 deletions compiler/mir/mirgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1459,12 +1459,13 @@ proc genIf(c: var TCtx, n: PNode, dest: Destination) =

template genElifBranch(branch: PNode, extra: untyped) =
## Generates the code for a single ``nkElif(Branch|Expr)``
let v = genUse(c, branch[0])
c.subTree mnkIf:
c.use v
c.scope:
genBranch(c, branch.lastSon, dest)
extra
c.scope:
let v = genUse(c, branch[0])
c.subTree mnkIf:
c.use v
c.scope:
genBranch(c, branch.lastSon, dest)
extra

if n.len == 1:
# an ``if`` statement/expression with a single branch. Don't emit the
Expand Down Expand Up @@ -2058,6 +2059,10 @@ proc generateCode*(graph: ModuleGraph, env: var MirEnv, owner: PSym,

c.scopeDepth = 1
c.add MirNode(kind: mnkScope)
if sfNeverRaises in owner.flags:
c.add MirNode(kind: mnkTry, len: 1)
c.add MirNode(kind: mnkStmtList)

if owner.kind in routineKinds:
# add a 'def' for each ``sink`` parameter. This simplifies further
# processing and analysis
Expand All @@ -2070,6 +2075,21 @@ proc generateCode*(graph: ModuleGraph, env: var MirEnv, owner: PSym,
c.add MirNode(kind: mnkNone)

gen(c, body)

if sfNeverRaises in owner.flags:
# if it's enforced that the procedure never raises, exceptions escaping
# the procedure terminate the program. This is achieved by wrapping the
# body in a catch-all exception handler
c.add endNode(mnkStmtList)
c.subTree MirNode(kind: mnkExcept, len: 1):
c.subTree mnkBranch:
c.subTree mnkVoid:
let p = c.graph.getCompilerProc("nimUnhandledException")
c.builder.buildCall c.env.procedures.add(p), p.typ,
typeOrVoid(c, p.typ[0]):
discard
c.add endNode(mnkTry)

c.add endNode(mnkScope)

swap(c.env, env) # swap back
Expand Down
34 changes: 1 addition & 33 deletions compiler/sem/injectdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,6 @@
## subsequently turning the assignment into a move and thus making the
## assertion fail with an ``IndexDefect``.

# XXX: there exists an effect-related problem with the lifetime-tracking hooks
# (i.e. ``=copy``, ``=sink``, ``=destroy``). The assignment rewriting and,
# to some degree, the destructor injection can be seen as a
# refinement/expansion/lowering and should thus not introduce (observable)
# side-effects (mutation of global state, exceptional control-flow, etc.) --
# it also violates the MIR specification. All three hooks are currently
# allowed to have side-effects, which violates the aforementioned rules.
# It also causes the concrete issue of cyclic dependencies: for example,
# the move analyser uses data-flow analysis (which requires a control-flow
# graph) in order to decide where to move and where to copy. If whether a
# copy or move is used affects the control-flow graph, the move analyser
# depends on its own output, which while possible to make work, would
# likely introduce a large amount of complexity.
# There are two possible solutions:
# 1. disallow lifetime-tracking hooks from having any side-effects
# 2. at least for the ``=copy`` and ``=sink`` hooks, each assignment
# could be said to have the union of the effects from both hooks.
# Those can be computed when generating the MIR code, as types and
# their type-bound operations are already figured out at that point.
# It's more complicated for ``=destroy`` hooks, since they are
# injected rather than being the result of an expansion. The current
# plan is to introduce the MIR concept of dedicated "scope finalizers",
# which could be used to attach the effects gathered from all possible
# destructor calls to

# XXX: not being able to rewrite an assignment into a call to the copy hook
# because it is disabled is a semantic error, meaning that it should
# be detected and reported during semantic analysis, not as part of
Expand Down Expand Up @@ -527,14 +502,7 @@ template buildVoidCall*(bu: var MirBuilder, env: var MirEnv, p: PSym,
body: untyped) =
let prc = p # prevent multi evaluation
bu.subTree mnkVoid:
let kind =
if canRaise(optPanics in graph.config.globalOptions, prc.ast[namePos]):
mnkCheckedCall
else:
mnkCall

# XXX: injected procedures should not introduce new control-flow paths
bu.subTree MirNode(kind: kind, typ: getVoidType(graph)):
bu.subTree MirNode(kind: mnkCall, typ: getVoidType(graph)):
bu.use toValue(env.procedures.add(prc), prc.typ)
body

Expand Down
11 changes: 2 additions & 9 deletions compiler/sem/liftdestructors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ type
asgnForType: PType
recurse: bool
addMemReset: bool # add wasMoved() call after destructor call
canRaise: bool
filterDiscriminator: PSym # we generating destructor for case branch
c: PContext # c can be nil, then we are called from lambdalifting!
idgen: IdGenerator
Expand Down Expand Up @@ -152,8 +151,6 @@ proc genContainerOf(c: var TLiftCtx; objType: PType, field, x: PSym): PNode =
proc destructorCall(c: var TLiftCtx; op: PSym; x: PNode): PNode =
var destroy = newTreeIT(nkCall, x.info, op.typ[0]):
[newSymNode(op), genAddr(c, x)]
if sfNeverRaises notin op.flags:
c.canRaise = true
if c.addMemReset:
result = newTree(nkStmtList):
[destroy, genBuiltin(c, mWasMoved, "wasMoved", x)]
Expand Down Expand Up @@ -303,8 +300,6 @@ proc newHookCall(c: var TLiftCtx; op: PSym; x, y: PNode): PNode =
# localReport(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s)
result = newNodeI(nkCall, x.info)
result.add newSymNode(op)
if sfNeverRaises notin op.flags:
c.canRaise = true
if op.typ.sons[1].kind == tyVar:
result.add genAddr(c, x)
else:
Expand All @@ -322,8 +317,6 @@ proc newHookCall(c: var TLiftCtx; op: PSym; x, y: PNode): PNode =
proc newOpCall(c: var TLiftCtx; op: PSym; x: PNode): PNode =
result = newTreeIT(nkCall, x.info, op.typ[0]):
[newSymNode(op), x]
if sfNeverRaises notin op.flags:
c.canRaise = true

proc newDeepCopyCall(c: var TLiftCtx; op: PSym; x, y: PNode): PNode =
result = newAsgnStmt(x, newOpCall(c, op, y))
Expand Down Expand Up @@ -948,7 +941,7 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
# bug #19205: Do not forget to also copy the hidden type field:
genTypeFieldCopy(a, typ, result.ast[bodyPos], d, src)

if not a.canRaise: incl result.flags, sfNeverRaises
incl result.flags, sfNeverRaises
completePartialOp(g, idgen.module, typ, kind, result)


Expand All @@ -972,7 +965,7 @@ proc produceDestructorForDiscriminator*(g: ModuleGraph; typ: PType; field: PSym,
result.ast[bodyPos].add v
let placeHolder = newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
fillBody(a, typ, result.ast[bodyPos], d, placeHolder)
if not a.canRaise: incl result.flags, sfNeverRaises
incl result.flags, sfNeverRaises


template liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) =
Expand Down
15 changes: 15 additions & 0 deletions compiler/sem/sempass2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,21 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
else:
effects[tagEffects] = t.tags

# ensure that user-provided hooks have no effects and don't raise
if sfOverriden in s.flags:
# if raising was explicitly disabled (i.e., via ``.raises: []``),
# exceptions, if any, were already reported; don't report errors again in
# that case
if raisesSpec.isNil or raisesSpec.len > 0:
let newSpec = newNodeI(nkArgList, s.info)
checkRaisesSpec(g, rsemHookCannotRaise, newSpec,
t.exc, hints=off, nil)
# override the raises specification to prevent cascading errors:
effects[exceptionEffects] = newSpec

# enforce that no defects escape the routine at run-time:
s.flags.incl sfNeverRaises

var mutationInfo = MutationInfo()
var hasMutationSideEffect = false
if {strictFuncs, views} * c.features != {}:
Expand Down
16 changes: 13 additions & 3 deletions compiler/sem/transf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,9 @@ proc transformWhile(c: PTransf; n: PNode): PNode =
loop[0] = newIntTypeNode(1, c.graph.getSysType(info, tyBool))
loop[0].info = info

# XXX: we need to help ``closureiters`` (which doesn't support 'yield' in
# if conditions...) here and unpack complex condition expressions;
# 'yield' in 'while' conditions would not work otherwise
# unwrap the statement list expression. It helps with the following
# lowering, and it's also necessary for the closure iterator
# transformation
var preamble = PNode(nil)
if cond.kind in {nkStmtListExpr, nkStmtList}:
preamble = newNodeI(nkStmtList, info, cond.len - 1)
Expand All @@ -384,6 +384,16 @@ proc transformWhile(c: PTransf; n: PNode): PNode =

cond = cond[^1]

# all definitions part of the condition expression are part of the while's
# scope, placing the expression into the if's condition slot would thus
# result in incorrect scoping
if not isAtom(cond):
let tmp = newTemp(c, cond.typ, cond.info)
if preamble.isNil:
preamble = newTree(nkStmtList)
preamble.add newTree(nkLetSection, newIdentDefs(tmp, cond))
cond = tmp

let exit =
newTreeI(nkIfStmt, info,
newTreeI(nkElifBranch, info,
Expand Down
4 changes: 3 additions & 1 deletion compiler/vm/compilerbridge.nim
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ proc buildError(c: TCtx, thread: VmThread, event: sink VmEvent): ExecErrorReport
## Creates an `ExecErrorReport` with the `event` and a stack-trace for
## `thread`
let stackTrace =
if event.kind == vmEvtUnhandledException:
if event.kind == vmEvtUnhandledException and event.trace.len > 0:
# HACK: an unhandled exception can be reported without providing a trace.
# Ideally, that shouldn't happen
createStackTrace(c, event.trace)
else:
createStackTrace(c, thread)
Expand Down
18 changes: 2 additions & 16 deletions compiler/vm/vm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,8 @@ template toException(x: DerefFailureCode): untyped =

proc reportException(c: TCtx; trace: sink VmRawStackTrace, raised: LocHandle) =
## Reports the exception represented by `raised` by raising a `VmError`

let name = $raised.getFieldHandle(1.fpos).deref().strVal
let msg = $raised.getFieldHandle(2.fpos).deref().strVal

# The reporter expects the exception as a deserialized PNode-tree. Only the
# 2nd (name) and 3rd (msg) field are actually used, so instead of running
# full deserialization (which is also not possible due to no `PType` being
# available), we just create the necessary parts manually

# TODO: the report should take the two strings directly instead
let empty = newNode(nkEmpty)
let ast = newTree(nkObjConstr,
empty, # constructor type; unused
empty, # unused
newStrNode(nkStrLit, name),
newStrNode(nkStrLit, msg))
let ast = toExceptionAst($raised.getFieldHandle(1.fpos).deref().strVal,
$raised.getFieldHandle(2.fpos).deref().strVal)
raiseVmError(VmEvent(kind: vmEvtUnhandledException, exc: ast, trace: trace))

func cleanUpReg(r: var TFullReg, mm: var VmMemoryManager) =
Expand Down
10 changes: 10 additions & 0 deletions compiler/vm/vmdeps.nim
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,13 @@ proc errorReportToString*(c: ConfigRef, error: Report): string =
# the report, so need to add `"Error: "`
# manally to stay consistent with the old
# output.

proc toExceptionAst*(name, msg: sink string): PNode =
## Creates the AST as for an exception object as expected by the report.
# TODO: the report should take the two strings directly instead
let empty = newNode(nkEmpty)
newTree(nkObjConstr,
empty, # constructor type; unused
empty, # unused
newStrNode(nkStrLit, name),
newStrNode(nkStrLit, msg))
12 changes: 12 additions & 0 deletions compiler/vm/vmops.nim
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ proc prepareExceptionWrapper(a: VmArgs) {.nimcall.} =
deref(a.getHandle(1)).strVal,
a.mem.allocator)

proc nimUnhandledExceptionWrapper(a: VmArgs) {.nimcall.} =
# setup the exception AST:
let
exc = a.heap[].tryDeref(a.currentException, noneType).value()
ast = toExceptionAst($exc.getFieldHandle(1.fpos).deref().strVal,
$exc.getFieldHandle(2.fpos).deref().strVal)
# report the unhandled exception:
# XXX: the current stack-trace should be passed along, but we don't
# have access to it here
raiseVmError(VmEvent(kind: vmEvtUnhandledException, exc: ast))

proc prepareMutationWrapper(a: VmArgs) {.nimcall.} =
discard "no-op"

Expand Down Expand Up @@ -247,6 +258,7 @@ iterator basicOps*(): Override =
systemop(getCurrentExceptionMsg)
systemop(getCurrentException)
systemop(prepareException)
systemop(nimUnhandledException)
systemop(prepareMutation)
override("stdlib.system.closureIterSetupExc",
setCurrentExceptionWrapper)
Expand Down
2 changes: 1 addition & 1 deletion lib/std/tasks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type
Task* = object ## `Task` contains the callback and its arguments.
callback: proc (args: pointer) {.nimcall, gcsafe.}
args: pointer
destroy: proc (args: pointer) {.nimcall, gcsafe.}
destroy: proc (args: pointer) {.nimcall, gcsafe, raises: [].}


proc `=copy`*(x: var Task, y: Task) {.error.}
Expand Down
3 changes: 3 additions & 0 deletions lib/system.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2348,6 +2348,9 @@ elif isNimVmTarget:
proc prepareException(e: ref Exception, ename: cstring) {.compilerproc.} =
discard

proc nimUnhandledException() {.compilerproc.} =
discard

proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
## Used by the closure transformation pass for preparing for exception
## handling. Implemented as a callback.
Expand Down
6 changes: 6 additions & 0 deletions lib/system/excpt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,12 @@ when true:
currException = nil
quit(1)

proc nimUnhandledException() {.compilerproc, noreturn.} =
## Called from generated code when propgation of an exception crosses a
## routine boundary it shouldn't.
reportUnhandledError(currException)
quit(1)

proc pushActiveException(e: sink(ref Exception)) =
e.up = activeException
activeException = e
Expand Down
32 changes: 29 additions & 3 deletions lib/system/jssys.nim
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ proc writeStackTrace() =
proc getStackTrace*(): string = rawWriteStackTrace()
proc getStackTrace*(e: ref Exception): string = e.trace

proc unhandledException(e: ref Exception) {.
compilerproc, asmNoStackFrame.} =
proc unhandledExceptionString(e: ref Exception): string =
var buf = ""
if e.msg.len != 0:
add(buf, "Error: unhandled exception: ")
Expand All @@ -128,7 +127,11 @@ proc unhandledException(e: ref Exception) {.
add(buf, "]\n")
when NimStackTrace:
add(buf, rawWriteStackTrace())
let cbuf = cstring(buf)
result = buf

proc unhandledException(e: ref Exception) {.
compilerproc, asmNoStackFrame.} =
let cbuf = cstring(unhandledExceptionString(e))
framePtr = nil
{.emit: """
if (typeof(Error) !== "undefined") {
Expand All @@ -139,6 +142,29 @@ proc unhandledException(e: ref Exception) {.
}
""".}

proc nimUnhandledException() {.compilerproc, asmNoStackFrame.} =
# |NimSkull| exceptions are turned into JavaScript errors for the purpose
# of better error messages
when defined(nodejs):
{.emit: """
if (lastJSError.m_type !== undefined) {
console.log(`toJSStr`(`unhandledExceptionString`(`lastJSError`)));
} else {
console.log('Error: unhandled exception: ', `lastJSError`)
}
process.exit(1);
""".}
else:
# it's currently not possible to truly panic (abort excution) for non-
# node.js JavaScript
{.emit: """
if (lastJSError.m_type !== undefined) {
`unhandledException`(lastJSError);
} else {
throw lastJSError;
}
""".}

proc prepareException(e: ref Exception, ename: cstring) {.
compilerproc, asmNoStackFrame.} =
if e.name.isNil:
Expand Down
Loading

0 comments on commit 44fff33

Please sign in to comment.