Issues with multithread program
  • Just looking around
    Posts: 15 from 2020/8/2
    Hi all,
    I'm having a problem with the compilation of a multithreaded program (specifically, of the Stockfish chess program I'm trying to port to MorphOS - in an earlier version than the last one, which had already been ported successfully under OS4). Warnings are issued during compilation (which gets to the end, the executable file is created anyway), then there are problems running it: program suddenly crashes while running, and one thing it never succeeds in doing is exiting by returning the command to the user (always crashing).

    These are the warnings generated:

    Code:

    In file included from types.h:43,
    from movegen.h:23,
    from thread.cpp:23:
    thread.cpp: In instantiation of 'T* {anonymous}::new_thread() [with T = TimerThread]':
    thread.cpp:190:35: required from here
    platform.h:70:57: warning: cast between incompatible function types from 'long int (*)(ThreadBase*)' to 'pt_start_fn' {aka 'void* (*)(void*)'} [-Wcast-function-type]
    70 | # define thread_create(x,f,t) pthread_create(&(x),NULL,(pt_start_fn)f,t)
    thread.cpp:48:4: note: in expansion of macro 'thread_create'
    48 | thread_create(th->handle, start_routine, th); // Will go to sleep
    | ^~~~~~~~~~~~~
    thread.cpp: In instantiation of 'T* {anonymous}::new_thread() [with T = MainThread]':
    thread.cpp:191:36: required from here
    platform.h:70:57: warning: cast between incompatible function types from 'long int (*)(ThreadBase*)' to 'pt_start_fn' {aka 'void* (*)(void*)'} [-Wcast-function-type]
    70 | # define thread_create(x,f,t) pthread_create(&(x),NULL,(pt_start_fn)f,t)
    thread.cpp:48:4: note: in expansion of macro 'thread_create'
    48 | thread_create(th->handle, start_routine, th); // Will go to sleep
    | ^~~~~~~~~~~~~
    thread.cpp: In instantiation of 'T* {anonymous}::new_thread() [with T = Thread]':
    thread.cpp:226:36: required from here
    platform.h:70:57: warning: cast between incompatible function types from 'long int (*)(ThreadBase*)' to 'pt_start_fn' {aka 'void* (*)(void*)'} [-Wcast-function-type]
    70 | # define thread_create(x,f,t) pthread_create(&(x),NULL,(pt_start_fn)f,t)
    thread.cpp:48:4: note: in expansion of macro 'thread_create'
    48 | thread_create(th->handle, start_routine, th); // Will go to sleep
    | ^~~~~~~~~~~~~


    This the makefile:

    Code:

    # $@ = Stockfish5_MorphOS

    CC = g++
    CP = copy clone
    RM = delete force
    CFLAGS = -mnewlib -O3 -Wall -fsigned-char -N
    # Stockfish specific
    CFLAGS += -Wcast-qual -fno-exceptions -fno-rtti
    CFLAGS += -ansi -pedantic -Wno-long-long -Wextra -Wshadow
    LIBS = -lpthread -lstdc++

    TARGET = Stockfish5_MorphOS
    OBJS = benchmark.o bitbase.o bitboard.o book.o endgame.o evaluate.o main.o
    material.o misc.o movegen.o movepick.o notation.o pawns.o position.o
    search.o thread.o timeman.o tt.o uci.o ucioption.o

    $(TARGET): $(OBJS)
    $(CC) -o $@ $(OBJS) $(LIBS)
    strip -R.comment $@
    protect $@ re

    benchmark.o: benchmark.cpp
    $(CC) -c benchmark.cpp -o benchmark.o $(CFLAGS)

    bitbase.o: bitbase.cpp
    $(CC) -c bitbase.cpp -o bitbase.o $(CFLAGS)

    bitboard.o: bitboard.h bitboard.cpp
    $(CC) -c bitboard.cpp -o bitboard.o $(CFLAGS)

    book.o: book.h book.cpp
    $(CC) -c book.cpp -o book.o $(CFLAGS)

    endgame.o: endgame.h endgame.cpp
    $(CC) -c endgame.cpp -o endgame.o $(CFLAGS)

    evaluate.o: evaluate.h evaluate.cpp
    $(CC) -c evaluate.cpp -o evaluate.o $(CFLAGS)

    main.o: main.cpp
    $(CC) -c main.cpp -o main.o $(CFLAGS)

    material.o: material.h material.cpp
    $(CC) -c material.cpp -o material.o $(CFLAGS)

    misc.o: misc.h misc.cpp
    $(CC) -c misc.cpp -o misc.o $(CFLAGS)

    movegen.o: movegen.h movegen.cpp
    $(CC) -c movegen.cpp -o movegen.o $(CFLAGS)

    movepick.o: movepick.h movepick.cpp
    $(CC) -c movepick.cpp -o movepick.o $(CFLAGS)

    notation.o: notation.h notation.cpp
    $(CC) -c notation.cpp -o notation.o $(CFLAGS)

    pawns.o: pawns.h pawns.cpp
    $(CC) -c pawns.cpp -o pawns.o $(CFLAGS)

    position.o: position.h position.cpp
    $(CC) -c position.cpp -o position.o $(CFLAGS)

    search.o: search.h search.cpp
    $(CC) -c search.cpp -o search.o $(CFLAGS)

    thread.o: thread.h thread.cpp
    $(CC) -c thread.cpp -o thread.o $(CFLAGS)

    timeman.o: timeman.h timeman.cpp
    $(CC) -c timeman.cpp -o timeman.o $(CFLAGS)

    tt.o: tt.h tt.cpp
    $(CC) -c tt.cpp -o tt.o $(CFLAGS)

    uci.o: uci.cpp
    $(CC) -c uci.cpp -o uci.o $(CFLAGS)

    ucioption.o: ucioption.h ucioption.cpp
    $(CC) -c ucioption.cpp -o ucioption.o $(CFLAGS)



    install: $(TARGET)
    $(CP) $(TARGET) /MorphOS/

    clean:
    $(RM) $(OBJS) $(TARGET)


    This the code of the part who manages the threads:

    thread.h
    Code:

    /*
    Stockfish, a UCI chess playing engine derived from Glaurung 2.1
    Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
    Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, Tord Romstad

    Stockfish is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Stockfish is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
    */

    #ifndef THREAD_H_INCLUDED
    #define THREAD_H_INCLUDED

    #include <bitset>
    #include <vector>

    #include "material.h"
    #include "movepick.h"
    #include "pawns.h"
    #include "position.h"
    #include "search.h"

    const int MAX_THREADS = 128;
    const int MAX_SPLITPOINTS_PER_THREAD = 8;

    struct Mutex {
    Mutex() { lock_init(l); }
    ~Mutex() { lock_destroy(l); }

    void lock() { lock_grab(l); }
    void unlock() { lock_release(l); }

    private:
    friend struct ConditionVariable;

    Lock l;
    };

    struct ConditionVariable {
    ConditionVariable() { cond_init(c); }
    ~ConditionVariable() { cond_destroy(c); }

    void wait(Mutex& m) { cond_wait(c, m.l); }
    void wait_for(Mutex& m, int ms) { timed_wait(c, m.l, ms); }
    void notify_one() { cond_signal(c); }

    private:
    WaitCondition c;
    };

    struct Thread;

    struct SplitPoint {

    // Const data after split point has been setup
    const Position* pos;
    const Search::Stack* ss;
    Thread* masterThread;
    Depth depth;
    Value beta;
    int nodeType;
    bool cutNode;

    // Const pointers to shared data
    MovePicker* movePicker;
    SplitPoint* parentSplitPoint;

    // Shared data
    Mutex mutex;
    std::bitset<MAX_THREADS> slavesMask;
    volatile bool allSlavesSearching;
    volatile uint64_t nodes;
    volatile Value alpha;
    volatile Value bestValue;
    volatile Move bestMove;
    volatile int moveCount;
    volatile bool cutoff;
    };


    /// ThreadBase struct is the base of the hierarchy from where we derive all the
    /// specialized thread classes.

    struct ThreadBase {

    ThreadBase() : handle(NativeHandle()), exit(false) {}
    virtual ~ThreadBase() {}
    virtual void idle_loop() = 0;
    void notify_one();
    void wait_for(volatile const bool& b);

    Mutex mutex;
    ConditionVariable sleepCondition;
    NativeHandle handle;
    volatile bool exit;
    };


    /// Thread struct keeps together all the thread related stuff like locks, state
    /// and especially split points. We also use per-thread pawn and material hash
    /// tables so that once we get a pointer to an entry its life time is unlimited
    /// and we don't have to care about someone changing the entry under our feet.

    struct Thread : public ThreadBase {

    Thread();
    virtual void idle_loop();
    bool cutoff_occurred() const;
    bool available_to(const Thread* master) const;

    template <bool Fake>
    void split(Position& pos, const Search::Stack* ss, Value alpha, Value beta, Value* bestValue, Move* bestMove,
    Depth depth, int moveCount, MovePicker* movePicker, int nodeType, bool cutNode);

    SplitPoint splitPoints[MAX_SPLITPOINTS_PER_THREAD];
    Material::Table materialTable;
    Endgames endgames;
    Pawns::Table pawnsTable;
    Position* activePosition;
    size_t idx;
    int maxPly;
    SplitPoint* volatile activeSplitPoint;
    volatile int splitPointsSize;
    volatile bool searching;
    };


    /// MainThread and TimerThread are derived classes used to characterize the two
    /// special threads: the main one and the recurring timer.

    struct MainThread : public Thread {
    MainThread() : thinking(true) {} // Avoid a race with start_thinking()
    virtual void idle_loop();
    volatile bool thinking;
    };

    struct TimerThread : public ThreadBase {
    TimerThread() : run(false) {}
    virtual void idle_loop();
    bool run;
    static const int Resolution = 5; // msec between two check_time() calls
    };


    /// ThreadPool struct handles all the threads related stuff like init, starting,
    /// parking and, most importantly, launching a slave thread at a split point.
    /// All the access to shared thread data is done through this class.

    struct ThreadPool : public std::vector<Thread*> {

    void init(); // No c'tor and d'tor, threads rely on globals that should
    void exit(); // be initialized and are valid during the whole thread lifetime.

    MainThread* main() { return static_cast<MainThread*>((*this)[0]); }
    void read_uci_options();
    Thread* available_slave(const Thread* master) const;
    void wait_for_think_finished();
    void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);

    Depth minimumSplitDepth;
    Mutex mutex;
    ConditionVariable sleepCondition;
    TimerThread* timer;
    };

    extern ThreadPool Threads;

    #endif // #ifndef THREAD_H_INCLUDED


    thread.cpp
    Code:

    /*
    Stockfish, a UCI chess playing engine derived from Glaurung 2.1
    Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
    Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, Tord Romstad

    Stockfish is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Stockfish is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
    */

    #include <algorithm> // For std::count
    #include <cassert>

    #include "movegen.h"
    #include "search.h"
    #include "thread.h"
    #include "ucioption.h"

    using namespace Search;

    ThreadPool Threads; // Global object

    extern void check_time();

    namespace {

    // start_routine() is the C function which is called when a new thread
    // is launched. It is a wrapper to the virtual function idle_loop().

    extern "C" { long start_routine(ThreadBase* th) { th->idle_loop(); return 0; } }


    // Helpers to launch a thread after creation and joining before delete. Must be
    // outside Thread c'tor and d'tor because the object will be fully initialized
    // when start_routine (and hence virtual idle_loop) is called and when joining.

    template<typename T> T* new_thread() {
    T* th = new T();
    thread_create(th->handle, start_routine, th); // Will go to sleep
    return th;
    }

    void delete_thread(ThreadBase* th) {
    th->exit = true; // Search must be already finished
    th->notify_one();
    thread_join(th->handle); // Wait for thread termination
    delete th;
    }

    }


    // notify_one() wakes up the thread when there is some work to do

    void ThreadBase::notify_one() {

    mutex.lock();
    sleepCondition.notify_one();
    mutex.unlock();
    }


    // wait_for() set the thread to sleep until condition 'b' turns true

    void ThreadBase::wait_for(volatile const bool& b) {

    mutex.lock();
    while (!b) sleepCondition.wait(mutex);
    mutex.unlock();
    }


    // Thread c'tor just inits data and does not launch any execution thread.
    // Such a thread will only be started when c'tor returns.

    Thread::Thread() /* : splitPoints() */ { // Value-initialization bug in MSVC

    searching = false;
    maxPly = splitPointsSize = 0;
    activeSplitPoint = NULL;
    activePosition = NULL;
    idx = Threads.size(); // Starts from 0
    }


    // cutoff_occurred() checks whether a beta cutoff has occurred in the
    // current active split point, or in some ancestor of the split point.

    bool Thread::cutoff_occurred() const {

    for (SplitPoint* sp = activeSplitPoint; sp; sp = sp->parentSplitPoint)
    if (sp->cutoff)
    return true;

    return false;
    }


    // Thread::available_to() checks whether the thread is available to help the
    // thread 'master' at a split point. An obvious requirement is that thread must
    // be idle. With more than two threads, this is not sufficient: If the thread is
    // the master of some split point, it is only available as a slave to the slaves
    // which are busy searching the split point at the top of slave's split point
    // stack (the "helpful master concept" in YBWC terminology).

    bool Thread::available_to(const Thread* master) const {

    if (searching)
    return false;

    // Make a local copy to be sure it doesn't become zero under our feet while
    // testing next condition and so leading to an out of bounds access.
    int size = splitPointsSize;

    // No split points means that the thread is available as a slave for any
    // other thread otherwise apply the "helpful master" concept if possible.
    return !size || splitPoints[size - 1].slavesMask.test(master->idx);
    }


    // TimerThread::idle_loop() is where the timer thread waits msec milliseconds
    // and then calls check_time(). If msec is 0 thread sleeps until it's woken up.

    void TimerThread::idle_loop() {

    while (!exit)
    {
    mutex.lock();

    if (!exit)
    sleepCondition.wait_for(mutex, run ? Resolution : INT_MAX);

    mutex.unlock();

    if (run)
    check_time();
    }
    }


    // MainThread::idle_loop() is where the main thread is parked waiting to be started
    // when there is a new search. The main thread will launch all the slave threads.

    void MainThread::idle_loop() {

    while (true)
    {
    mutex.lock();

    thinking = false;

    while (!thinking && !exit)
    {
    Threads.sleepCondition.notify_one(); // Wake up the UI thread if needed
    sleepCondition.wait(mutex);
    }

    mutex.unlock();

    if (exit)
    return;

    searching = true;

    Search::think();

    assert(searching);

    searching = false;
    }
    }


    // init() is called at startup to create and launch requested threads, that will
    // go immediately to sleep. We cannot use a c'tor because Threads is a static
    // object and we need a fully initialized engine at this point due to allocation
    // of Endgames in Thread c'tor.

    void ThreadPool::init() {

    timer = new_thread<TimerThread>();
    push_back(new_thread<MainThread>());
    read_uci_options();
    }


    // exit() cleanly terminates the threads before the program exits. Cannot be done in
    // d'tor because we have to terminate the threads before to free ThreadPool object.

    void ThreadPool::exit() {

    delete_thread(timer); // As first because check_time() accesses threads data

    for (iterator it = begin(); it != end(); ++it)
    delete_thread(*it);
    }


    // read_uci_options() updates internal threads parameters from the corresponding
    // UCI options and creates/destroys threads to match the requested number. Thread
    // objects are dynamically allocated to avoid creating all possible threads
    // in advance (which include pawns and material tables), even if only a few
    // are to be used.

    void ThreadPool::read_uci_options() {

    minimumSplitDepth = Options["Min Split Depth"] * ONE_PLY;
    size_t requested = Options["Threads"];

    assert(requested > 0);

    // If zero (default) then set best minimum split depth automatically
    if (!minimumSplitDepth)
    minimumSplitDepth = requested < 8 ? 4 * ONE_PLY : 7 * ONE_PLY;

    while (size() < requested)
    push_back(new_thread<Thread>());

    while (size() > requested)
    {
    delete_thread(back());
    pop_back();
    }
    }


    // available_slave() tries to find an idle thread which is available as a slave
    // for the thread 'master'.

    Thread* ThreadPool::available_slave(const Thread* master) const {

    for (const_iterator it = begin(); it != end(); ++it)
    if ((*it)->available_to(master))
    return *it;

    return NULL;
    }


    // split() does the actual work of distributing the work at a node between
    // several available threads. If it does not succeed in splitting the node
    // (because no idle threads are available), the function immediately returns.
    // If splitting is possible, a SplitPoint object is initialized with all the
    // data that must be copied to the helper threads and then helper threads are
    // told that they have been assigned work. This will cause them to instantly
    // leave their idle loops and call search(). When all threads have returned from
    // search() then split() returns.

    template <bool Fake>
    void Thread::split(Position& pos, const Stack* ss, Value alpha, Value beta, Value* bestValue,
    Move* bestMove, Depth depth, int moveCount,
    MovePicker* movePicker, int nodeType, bool cutNode) {

    assert(pos.pos_is_ok());
    assert(-VALUE_INFINITE < *bestValue && *bestValue <= alpha && alpha < beta && beta <= VALUE_INFINITE);
    assert(depth >= Threads.minimumSplitDepth);
    assert(searching);
    assert(splitPointsSize < MAX_SPLITPOINTS_PER_THREAD);

    // Pick the next available split point from the split point stack
    SplitPoint& sp = splitPoints[splitPointsSize];

    sp.masterThread = this;
    sp.parentSplitPoint = activeSplitPoint;
    sp.slavesMask = 0, sp.slavesMask.set(idx);
    sp.depth = depth;
    sp.bestValue = *bestValue;
    sp.bestMove = *bestMove;
    sp.alpha = alpha;
    sp.beta = beta;
    sp.nodeType = nodeType;
    sp.cutNode = cutNode;
    sp.movePicker = movePicker;
    sp.moveCount = moveCount;
    sp.pos = &pos;
    sp.nodes = 0;
    sp.cutoff = false;
    sp.ss = ss;

    // Try to allocate available threads and ask them to start searching setting
    // 'searching' flag. This must be done under lock protection to avoid concurrent
    // allocation of the same slave by another master.
    Threads.mutex.lock();
    sp.mutex.lock();

    sp.allSlavesSearching = true; // Must be set under lock protection
    ++splitPointsSize;
    activeSplitPoint = &sp;
    activePosition = NULL;

    if (!Fake)
    for (Thread* slave; (slave = Threads.available_slave(this)) != NULL; )
    {
    sp.slavesMask.set(slave->idx);
    slave->activeSplitPoint = &sp;
    slave->searching = true; // Slave leaves idle_loop()
    slave->notify_one(); // Could be sleeping
    }

    // Everything is set up. The master thread enters the idle loop, from which
    // it will instantly launch a search, because its 'searching' flag is set.
    // The thread will return from the idle loop when all slaves have finished
    // their work at this split point.
    sp.mutex.unlock();
    Threads.mutex.unlock();

    Thread::idle_loop(); // Force a call to base class idle_loop()

    // In the helpful master concept, a master can help only a sub-tree of its
    // split point and because everything is finished here, it's not possible
    // for the master to be booked.
    assert(!searching);
    assert(!activePosition);

    // We have returned from the idle loop, which means that all threads are
    // finished. Note that setting 'searching' and decreasing splitPointsSize is
    // done under lock protection to avoid a race with Thread::available_to().
    Threads.mutex.lock();
    sp.mutex.lock();

    searching = true;
    --splitPointsSize;
    activeSplitPoint = sp.parentSplitPoint;
    activePosition = &pos;
    pos.set_nodes_searched(pos.nodes_searched() + sp.nodes);
    *bestMove = sp.bestMove;
    *bestValue = sp.bestValue;

    sp.mutex.unlock();
    Threads.mutex.unlock();
    }

    // Explicit template instantiations
    template void Thread::split<false>(Position&, const Stack*, Value, Value, Value*, Move*, Depth, int, MovePicker*, int, bool);
    template void Thread::split< true>(Position&, const Stack*, Value, Value, Value*, Move*, Depth, int, MovePicker*, int, bool);


    // wait_for_think_finished() waits for main thread to go to sleep then returns

    void ThreadPool::wait_for_think_finished() {

    MainThread* t = main();
    t->mutex.lock();
    while (t->thinking) sleepCondition.wait(t->mutex);
    t->mutex.unlock();
    }


    // start_thinking() wakes up the main thread sleeping in MainThread::idle_loop()
    // so to start a new search, then returns immediately.

    void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, StateStackPtr& states) {

    wait_for_think_finished();

    SearchTime = Time::now(); // As early as possible

    Signals.stopOnPonderhit = Signals.firstRootMove = false;
    Signals.stop = Signals.failedLowAtRoot = false;

    RootMoves.clear();
    RootPos = pos;
    Limits = limits;
    if (states.get()) // If we don't set a new position, preserve current state
    {
    SetupStates = states; // Ownership transfer here
    assert(!states.get());
    }

    for (MoveList<LEGAL> it(pos); *it; ++it)
    if ( limits.searchmoves.empty()
    || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), *it))
    RootMoves.push_back(RootMove(*it));

    main()->thinking = true;
    main()->notify_one(); // Starts main thread
    }


    The other files of which the program is composed compile without any problems.
    Any ideas?
  • »06.08.24 - 22:06
    Profile
  • Priest of the Order of the Butterfly
    Priest of the Order of the Butterfly
    Tcheko
    Posts: 528 from 2003/2/25
    From: France
    Stack size?
    Quelque soit le chemin que tu prendras dans la vie, sache que tu auras des ampoules aux pieds.
    -------
    I need to practice my Kung Fu.
  • »07.08.24 - 03:41
    Profile Visit Website
  • MorphOS Developer
    jacadcaps
    Posts: 3077 from 2003/3/5
    From: Canada
    Use -noixemul instead of -mnewlib. MorphOS has no newlib.
  • »07.08.24 - 12:41
    Profile Visit Website
  • Just looking around
    Posts: 15 from 2020/8/2
    Hello! Thanks for your replies.

    @Tcheko: even under OS4 there was the issue of the stack, which had to be set to 512000 (otherwise Stockfish would cause an error). Trying a 'stack 512000' command before launching Stockfish for MorphOS unfortunately didn't change things, executable has the same problems.

    @jacadcaps: I tried replacing mnewlib with noixemul, but unfortunately compiler emits the same messages, and the executable has the same problems.
  • »07.08.24 - 19:36
    Profile
  • MorphOS Developer
    Piru
    Posts: 587 from 2003/2/24
    From: finland, the l...
    Quote:

    domenikov wrote:
    Trying a 'stack 512000' command before launching Stockfish for MorphOS unfortunately didn't change things, executable has the same problems.


    On MorphOS the "Stack" command changes the 68k stack size, and is only used for emulated legacy 68k applications.

    Native MorphOS applications are supposed to provide enough stack storage themselves. The application writer knows the required stack size the best and thus the application itself should specify the required size for correct operation.

    To set the stack size to 512000 in libnix (-noixemul) application you should do the following in the application code:
    Code:

    long __stack = 512000;


    For ixemul applications that stack can be set with "ixstack" command:
    Code:

    ixstack 512000 path:to/binary

    You can verify the currently specified stack size of ixemul compiled binary with:
    Code:

    ixstack -l path:to/binary


    Finally, in case you use pthread and you want to set the stack size for the specific thread, use pthread_attr_setstacksize as seen in macOS specific code here: src/thread_win32_osx.h#L50
  • »07.08.24 - 22:36
    Profile
  • Just looking around
    Posts: 15 from 2020/8/2
    I tried entering the indicated line of code...

    Code:

    long __stack = 512000;


    ...into the 'main' function, as follows:

    Code:

    int main(int argc, char* argv[]) {

    long __stack = 512000;
    std::cout << engine_info() << std::endl;

    UCI::init(Options);
    Bitboards::init();
    Position::init();
    Bitbases::init_kpk();
    Search::init();
    Pawns::init();
    Eval::init();
    Threads.init();
    TT.resize(Options["Hash"]);

    UCI::loop(argc, argv);

    Threads.exit();
    }


    It does not seem to work. The only thing I got was a compiler message 'warning: unused variable __stack'. I probably didn't quite understand how that line of code was to be used.

    Instead, the ixstack command seems to have solved the problem: after executing it on the executable file, Stockfish no longer had any problems. No crashes when computing moves. It quits correctly on the 'quit' command (which always caused a crash before). I will continue to do more tests to make sure that there aren't problems anymore, however I think the problem is solved. Thank you for your help!
  • »08.08.24 - 23:20
    Profile
  • MorphOS Developer
    Piru
    Posts: 587 from 2003/2/24
    From: finland, the l...
    Quote:

    domenikov wrote:
    ...
    It does not seem to work.

    The __stack variable needs to be a global. Here's a small example:
    Code:

    #include <iostream>
    #include <proto/exec.h>

    long __stack = 128 * 1024;

    int main()
    {
    ULONG stacksize;
    NewGetTaskAttrsA(NULL, &stacksize, sizeof(stacksize), TASKINFOTYPE_STACKSIZE, NULL);
    std::cout << "Stack size is " << stacksize << " bytes" << std::endl;
    return 0;
    }


    Compile it with:
    Code:

    g++ -noixemul -Wall -O2 stacksize.cpp -o stacksize
  • »09.08.24 - 00:56
    Profile
  • MorphOS Developer
    jacadcaps
    Posts: 3077 from 2003/3/5
    From: Canada
    Since this is C++, you have to make the __stack variable extern "C", too.
  • »09.08.24 - 15:14
    Profile Visit Website