www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 15573] New: safe code using TLS works in debug; crashes in

https://issues.dlang.org/show_bug.cgi?id=15573

          Issue ID: 15573
           Summary:  safe code using TLS works in debug; crashes in
                    release
           Product: D
           Version: D2
          Hardware: x86_64
                OS: Linux
            Status: NEW
          Severity: major
          Priority: P1
         Component: dmd
          Assignee: nobody puremagic.com
          Reporter: thomas.bockman gmail.com

This is a serious bug, but a little bit complicated to explain; sorry I wasn't
able to reduce it further.

CONTEXT: The code below was reduced from some test code in my checkedint
project. It's original purpose was to compare my checked integer math
operations to the built in floating-point operations, to verify exhaustively
that all the problematic corner cases (such as division by zero) are handled
correctly.

One extra test I threw in (and am now very glad I did!) was a consistency check
- given the same inputs, a basic operation like division should always return
the same value.

`safeDiv()` isn't actually `pure`, because of the way it does error reporting.
Nevertheless, it should be consistent since it never reads the TLS variable
`intFlag`; it just overwrites it sometimes.

THE PROBLEM: On LDC and GDC, this code works great. It also works in debug or
unittest builds with DMD.

However, when built in 64-bit release mode with DMD (tested with master,
2.069.2, and 2.068.2), it fails catastrophically. DUB command:
    dub run -b release -a x86_64 --compiler=dmd

A large number of combinations of `n` and `m` fail the "consistent" check with
output like this:
    byte n        = -128
    byte m        = -2
    real theory   = +64
    int practice1 = +0
    int practice2 = +64
    intFlag  = {}
    FAILS: consistent

A few combinations fail the "correct" check with output like this:
    byte n        = -1
    byte m        = -2
    real theory   = +0
    int practice = +0
    intFlag  = {divide by zero}
    FAILS: correct

Finally, the program crashes with this unhelpful message:
    Program exited with code -8

I haven't yet been able to determine exactly what "code -8" means, although it
seems to indicate that the program did something bad enough that Linux forcibly
terminated the process.

A HINT: I wasn't able to reduce it further, but I did discover that the problem
goes away if `intFlag` is put anywhere other than thread local storage.

SYSTEM DETAILS:
    Linux Mint 17.3 64-bit running kernel 4.2.0-23-lowlatency
    Intel Xeon E3-1225 v3 (Haswell quad-core)

DUB CONFIG: Since this bug may be dependant on the compiler options, here's my
reduced DUB config:

{
  "name": "bug",
  "description": "Bug demonstrator.",
  "copyright": "Copyright © 2015, Thomas Stuart Bockman",
  "authors": ["Thomas Stuart Bockman"],
  "license": "BSL-1.0",

  "targetType": "executable",
  "mainSourceFile": "source/app.d",
}

SOURCE CODE:

// source/app.d
module app;

import std.math, std.stdio, std.traits;

 safe:

enum ulong[] naturals = function() {
    ulong[34 + 3*(64 - 5) - 2] nats;

    size_t n = 0;
    while(n <= 33) {
        nats[n] = n;
        ++n;
    }

    int sh = 6;
    while(sh < 64) {
        nats[n++] = (1uL << sh) -1;
        nats[n++] = (1uL << sh);
        nats[n++] = (1uL << sh) + 1;
        ++sh;
    }
    nats[n] = ulong.max;

    return nats;
}();
struct TestValues
{
    ptrdiff_t index = -38L;

     property bool empty() const {
        return index > 37L; }
     property byte front() const {
        if(index < 0)
            return -cast(byte)naturals[-index];

        return cast(byte)naturals[index];
    }
     property void popFront() {
        ++index; }
}


enum IntFlag : uint {
    NULL = 0,
    div0 = 1,
    posOver = 2
}
auto intFlag = IntFlag.NULL;

int safeDiv(const byte left, const byte right)
{
    const div0 = (right == 0);
    const posOver = (left == int.min) && (right == -1);

    if(div0 || posOver) {
        intFlag = (posOver? IntFlag.posOver : IntFlag.div0);
        return 0; // Prevent unrecoverable FPE
    } else
        return mixin("left / right");
}

void main()
{
    foreach(const m; TestValues()) {
        foreach(const n; TestValues()) {
            const theory = trunc(cast(real)n / cast(real)m);
            bool thrInval;
            thrInval = theory.isNaN;

            intFlag = IntFlag.NULL;
            const practice1 = safeDiv(n, m);
            int practice2 = practice1;

            void require(string name, const bool success) {
                if(success)
                    return;

                writeln();
                writefln("\t" ~ byte.stringof ~ " n        = %+d", n);
                writefln("\t" ~ byte.stringof ~ " m        = %+d", m);
                writefln("\treal theory   = %+.22g", theory);
                if(practice1 == practice2)
                    writefln("\tint practice = %+d", practice1);
                else {
                    writefln("\tint practice1 = %+d", practice1);
                    writefln("\tint practice2 = %+d", practice2);
                }
                writefln("\tintFlag  = %s", ["{}", "{divide by zero}",
"{positive overflow}"][cast(uint)intFlag]);

                write("\tFAILS: ");
                writefln(name);
            }

            require("correct", (!thrInval && (theory == practice1)) ^ (intFlag
!= IntFlag.NULL));

            intFlag = IntFlag.posOver;
            practice2 = safeDiv(n, m);
            require("sticky", (intFlag != IntFlag.NULL));
            intFlag = IntFlag.NULL;

            require("consistent", (practice2 == practice1));
        }
    }
}

--
Jan 16