Skip to content

Commit

Permalink
mir: add unchecked signed-integer arithmetic ops (#1164)
Browse files Browse the repository at this point in the history
## Summary

Which signed integer arithmetic operation are checked for overflow is
now made explicit within the MIR. This:
* makes the exceptional control-flow arising from overflow checks
  visible to data-flow analysis, fixing destructors sometimes not being
  run
* moves decision-making regarding overflow checks out of the code
  generators and into a central place (the AST -> MIR translation)

## Details

In order to give the code generators more freedom over how they
implement the overflow checks, no separate magics that implement the
checks are introduced.

### MIR additions

* add built-in signed integer arithmetic unary operators (negation)
* add built-in signed integer arithmetic binary operators (addition,
  subtraction, multiplication, division, and remainder)
* the behaviour of the built-in operators regarding over- and
  underflow, as well as division-by-zero, is undefined

### General architecture

* when overflow checks are enabled, signed integer arithmetic
  operations are kept as magic calls (using `mnkCheckedCall` if panics
  are disabled)
* when overflow checks are disabled, signed integer arithmetic
  operations are translated to use the MIR built-in operators
* signed integer arithmetic operators kept as magic calls are
  unconditionally translated to checked operations by the code
  generators
* the built-in arithmetic operators are unconditionally translated to
  unchecked operations (if available) by the code generators
* the overflow check behaviour of `abs` is still controlled
  individually by each code generator

### AST-to-MIR translation

* `mSucc` (successor) and `mPred` (predecessor) are lowered into normal
  addition and subtraction operators, respectively. This was previously
  left to the code generators

### MIR-to-CGIR translation

* built-in operations equivalent to the ones added to the MIR are added
  to the `CgNode` IR
* the MIR's arithmetic operations have a one-to-one correspondence with
  the `CgNode` ones

### Code generation

* `vmgen` generates `Addu`, `Subu`, and `Mulu` for unchecked signed
  additions, subtractions, and multiplications. For a lack of unchecked
  versions, negation, division, and remainder still use the checked
  opcodes
* `cgen` and `jsgen` generate the same code for the unchecked built-in
  operators as they previously did for unchecked arithmetic magics
* all three code generators ignore `mSucc` and `mPred`

### Other

* `mirexec` treats the built-in arithmetic operations operands as
  usages
* the MIR built-in arithmetic operators are rendered as how they would
  look like in the high-level language (`+`, `*`, `div`, etc.)

---------

Co-authored-by: Saem Ghani <saemghani+github@gmail.com>
  • Loading branch information
zerbina and saem authored Feb 4, 2024
1 parent 74bd23e commit 093ffd7
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 170 deletions.
69 changes: 42 additions & 27 deletions compiler/backend/ccgexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -303,17 +303,14 @@ template binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc;

proc binaryArithOverflow(p: BProc, e: CgNode, d: var TLoc, m: TMagic) =
const
prc: array[mAddI..mPred, string] = [
prc: array[mAddI..mModI, string] = [
"nimAddInt", "nimSubInt",
"nimMulInt", "nimDivInt", "nimModInt",
"nimAddInt", "nimSubInt"
"nimMulInt", "nimDivInt", "nimModInt"
]
prc64: array[mAddI..mPred, string] = [
prc64: array[mAddI..mModI, string] = [
"nimAddInt64", "nimSubInt64",
"nimMulInt64", "nimDivInt64", "nimModInt64",
"nimAddInt64", "nimSubInt64"
"nimMulInt64", "nimDivInt64", "nimModInt64"
]
opr: array[mAddI..mPred, string] = ["+", "-", "*", "/", "%", "+", "-"]
var a, b: TLoc
assert(e[1].typ != nil)
assert(e[2].typ != nil)
Expand All @@ -322,10 +319,7 @@ proc binaryArithOverflow(p: BProc, e: CgNode, d: var TLoc, m: TMagic) =
# skipping 'range' is correct here as we'll generate a proper range check
# later via 'chckRange'
let t = e.typ.skipTypes(abstractRange)
if optOverflowCheck notin p.options:
let res = "($1)($2 $3 $4)" % [getTypeDesc(p.module, e.typ), rdLoc(a), rope(opr[m]), rdLoc(b)]
putIntoDest(p, d, e, res)
else:
if true:
# we handle div by zero here so that we know that the compilerproc's
# result is only for overflows.
if m in {mDivI, mModI}:
Expand All @@ -343,9 +337,8 @@ proc unaryArithOverflow(p: BProc, e: CgNode, d: var TLoc, m: TMagic) =
assert(e[1].typ != nil)
initLocExpr(p, e[1], a)
t = skipTypes(e.typ, abstractRange)
if optOverflowCheck in p.options:
linefmt(p, cpsStmts, "if ($1 == $2){ #raiseOverflow(); $3}$n",
[rdLoc(a), intLiteral(firstOrd(p.config, t)), raiseInstr(p)])
linefmt(p, cpsStmts, "if ($1 == $2){ #raiseOverflow(); $3}$n",
[rdLoc(a), intLiteral(firstOrd(p.config, t)), raiseInstr(p)])
case m
of mUnaryMinusI:
putIntoDest(p, d, e, "((NI$2)-($1))" % [rdLoc(a), rope(getSize(p.config, t) * 8)])
Expand All @@ -356,14 +349,14 @@ proc unaryArithOverflow(p: BProc, e: CgNode, d: var TLoc, m: TMagic) =
else:
assert(false, $m)

proc binaryArith(p: BProc, e: CgNode, d: var TLoc, op: TMagic) =
proc binaryArith(p: BProc, e, x, y: CgNode, d: var TLoc, op: TMagic) =
var
a, b: TLoc
s, k: BiggestInt
assert(e[1].typ != nil)
assert(e[2].typ != nil)
initLocExpr(p, e[1], a)
initLocExpr(p, e[2], b)
assert(x.typ != nil)
assert(y.typ != nil)
initLocExpr(p, x, a)
initLocExpr(p, y, b)
# BUGFIX: cannot use result-type here, as it may be a boolean
s = max(getSize(p.config, a.t), getSize(p.config, b.t)) * 8
k = getSize(p.config, a.t) * 8
Expand All @@ -375,6 +368,11 @@ proc binaryArith(p: BProc, e: CgNode, d: var TLoc, op: TMagic) =
)

case op
of mAddI: applyFormat("($4)($1 + $2)")
of mSubI: applyFormat("($4)($1 - $2)")
of mMulI: applyFormat("($4)($1 * $2)")
of mDivI: applyFormat("($4)($1 / $2)")
of mModI: applyFormat("($4)($1 % $2)")
of mAddF64: applyFormat("(($4)($1) + ($4)($2))")
of mSubF64: applyFormat("(($4)($1) - ($4)($2))")
of mMulF64: applyFormat("(($4)($1) * ($4)($2))")
Expand Down Expand Up @@ -435,12 +433,12 @@ proc genIsNil(p: BProc, e: CgNode, d: var TLoc) =
else:
unaryExpr(p, e, d, "($1 == 0)")

proc unaryArith(p: BProc, e: CgNode, d: var TLoc, op: TMagic) =
proc unaryArith(p: BProc, e, x: CgNode, d: var TLoc, op: TMagic) =
var
a: TLoc
t: PType
assert(e[1].typ != nil)
initLocExpr(p, e[1], a)
assert(x.typ != nil)
initLocExpr(p, x, a)
t = skipTypes(e.typ, abstractRange)

template applyFormat(frmt: untyped) =
Expand All @@ -455,8 +453,12 @@ proc unaryArith(p: BProc, e: CgNode, d: var TLoc, op: TMagic) =
applyFormat("($3)((NU$2) ~($1))")
of mUnaryPlusF64:
applyFormat("$1")
of mUnaryMinusF64:
of mUnaryMinusF64, mUnaryMinusI64:
applyFormat("-($1)")
of mUnaryMinusI:
applyFormat("((NI$2)-($1))")
of mAbsI:
applyFormat("($1 > 0? ($1) : -($1))")
else:
assert false, $op

Expand Down Expand Up @@ -1571,7 +1573,7 @@ proc binaryFloatArith(p: BProc, e: CgNode, d: var TLoc, m: TMagic) =
if optInfCheck in p.options:
linefmt(p, cpsStmts, "if ($1 != 0.0 && $1*0.5 == $1) { #raiseFloatOverflow($1); $2}$n", [rdLoc(d), raiseInstr(p)])
else:
binaryArith(p, e, d, m)
binaryArith(p, e, e[1], e[2], d, m)

proc skipAddr(n: CgNode): CgNode =
if n.kind == cnkHiddenAddr: n.operand
Expand Down Expand Up @@ -1649,10 +1651,16 @@ proc genBreakState(p: BProc, n: CgNode, d: var TLoc) =

proc genMagicExpr(p: BProc, e: CgNode, d: var TLoc, op: TMagic) =
case op
of mNot..mUnaryMinusF64: unaryArith(p, e, d, op)
of mUnaryMinusI..mAbsI: unaryArithOverflow(p, e, d, op)
of mNot..mUnaryMinusF64: unaryArith(p, e, e[1], d, op)
of mUnaryMinusI, mUnaryMinusI64: unaryArithOverflow(p, e, d, op)
of mAbsI:
# TODO: lower the unchecked ``abs`` variant earlier
if optOverflowCheck in p.options:
unaryArithOverflow(p, e, d, op)
else:
unaryArith(p, e, e[1], d, op)
of mAddF64..mDivF64: binaryFloatArith(p, e, d, op)
of mShrI..mXor: binaryArith(p, e, d, op)
of mShrI..mXor: binaryArith(p, e, e[1], e[2], d, op)
of mEqProc: genEqProc(p, e, d)
of mAddI..mPred: binaryArithOverflow(p, e, d, op)
of mGetTypeInfo: genGetTypeInfo(p, e, d)
Expand Down Expand Up @@ -2049,6 +2057,13 @@ proc expr(p: BProc, n: CgNode, d: var TLoc) =
genMagicExpr(p, n, d, m)
else:
genCall(p, n, d)
# unchecked arithmetic operations:
of cnkNegI: unaryArith(p, n, n[0], d, mUnaryMinusI)
of cnkAddI: binaryArith(p, n, n[0], n[1], d, mAddI)
of cnkSubI: binaryArith(p, n, n[0], n[1], d, mSubI)
of cnkMulI: binaryArith(p, n, n[0], n[1], d, mMulI)
of cnkDivI: binaryArith(p, n, n[0], n[1], d, mDivI)
of cnkModI: binaryArith(p, n, n[0], n[1], d, mModI)
of cnkSetConstr:
genSetConstr(p, n, d)
of cnkArrayConstr:
Expand Down
8 changes: 8 additions & 0 deletions compiler/backend/cgir.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ type
## the following operands the arguments
cnkCheckedCall ## like ``cnkCall``, but the call might raise an exception

# arithmetic operations:
cnkNegI
cnkAddI
cnkSubI
cnkMulI
cnkDivI
cnkModI

# constructors:
cnkTupleConstr ## tuple constructor
cnkObjConstr ## object constructor
Expand Down
9 changes: 9 additions & 0 deletions compiler/backend/cgirgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,15 @@ proc exprToIr(tree: MirBody, cl: var TranslateCl,
res.add argToIr(tree, cl, cr)[1]
of mnkCall, mnkCheckedCall:
callToIr(tree, cl, n, cr)
of UnaryOps:
const Map = [mnkNegI: cnkNegI]
treeOp Map[n.kind]:
res.add valueToIr(tree, cl, cr)
of BinaryOps:
const Map = [mnkAddI: cnkAddI, mnkSubI: cnkSubI,
mnkMulI: cnkMulI, mnkDivI: cnkDivI, mnkModI: cnkModI]
treeOp Map[n.kind]:
res.kids = @[valueToIr(tree, cl, cr), valueToIr(tree, cl, cr)]
of AllNodeKinds - ExprKinds - {mnkNone}:
unreachable(n.kind)

Expand Down
Loading

0 comments on commit 093ffd7

Please sign in to comment.