Advertisement

Strange bug related to opPostInc and native calling convention on Linux x64

Started by September 14, 2024 10:01 AM
4 comments, last by WitchLord 4 days, 4 hours ago

The same C++ code and script give me different result between Windows+MSVC and Linux+GCC.


#include <angelscript.h>

#include <iostream>

class my_int
{
public:
    my_int() = default;
    my_int(const my_int&) = default;

    my_int& operator=(const my_int&) = default;

    int value = 0;

    my_int& operator++()
    {
        ++value;
        return *this;
    }

    my_int operator++(int)
    {
        my_int tmp(*this);
        ++*this;
        return tmp;
    }
};

void output(int val)
{
    std::cout << val << std::endl;
}

int main(int argc, char** argv)
{
    asIScriptEngine* engine = asCreateScriptEngine();

    engine->RegisterObjectType(
        "my_int", sizeof(my_int), asOBJ_VALUE | asOBJ_APP_CLASS_CDK
    );

    engine->RegisterObjectBehaviour(
        "my_int",
        asBEHAVE_CONSTRUCT,
        "void f()",
        asFunctionPtr(+[](void* mem)
        {
            new(mem) my_int();
        }),
        asCALL_CDECL_OBJLAST
    );
    engine->RegisterObjectBehaviour(
        "my_int",
        asBEHAVE_CONSTRUCT,
        "void f(const my_int&in)",
        asFunctionPtr(+[](void* mem, const my_int& other)
        {
            new(mem) my_int(other);
        }),
        asCALL_CDECL_OBJFIRST
    );
    engine->RegisterObjectBehaviour(
        "my_int",
        asBEHAVE_DESTRUCT,
        "void f()",
        asFunctionPtr(+[](void* mem)
                      {
                          // empty
                      }),
        asCALL_CDECL_OBJLAST
    );

    engine->RegisterObjectMethod(
        "my_int",
        "my_int& opPreInc()",
        asMETHODPR(my_int, operator++, (), my_int&),
        asCALL_THISCALL
    );
    engine->RegisterObjectMethod(
        "my_int",
        "my_int opPostInc()",
        asMETHODPR(my_int, operator++, (int), my_int),
        asCALL_THISCALL
    );
    engine->RegisterObjectProperty(
        "my_int",
        "int value",
        asOFFSET(my_int, value)
    );

    engine->RegisterGlobalFunction(
        "void output(int val)",
        asFUNCTION(output),
        asCALL_CDECL
    );

    asIScriptModule* m = engine->GetModule("test", asGM_ALWAYS_CREATE);

    m->AddScriptSection(
        "test.as",
        "void f()"
        "{"
        "my_int i;"
        "output(i.value);"
        "++i;"
        "output(i.value);"
        "my_int tmp = i++;"
        "output(tmp.value);"
        "output(i.value);"
        "}"
    );
    m->Build();

    std::cout << asGetLibraryVersion() << std::endl;

    asIScriptFunction* f = m->GetFunctionByName("f");
    asIScriptContext* ctx = engine->CreateContext();

    ctx->Prepare(f);
    ctx->Execute();

    ctx->Release();

    engine->ShutDownAndRelease();
    return 0;
}

The output of Windows+MSVC, which is the expected output

0
1
1
2

But the output of Linux+GCC is obviously wrong. The third line of output keeps changing among every execution. Here is an example output

0
1
-266470689
1

The architecture of testing environment is both x64. The version of AngelScript is 2.37.0.

But if I registered the opPostInc using asCALL_GENERIC , output of both platforms give expected result.

// The wrapper
void opPostInc_gen(asIScriptGeneric* gen)
{
    my_int* this_ = (my_int*)gen->GetObject();
    my_int result = (*this_)++;
    gen->SetReturnObject(&result);
}

// in main()
engine->RegisterObjectMethod(
    "my_int",
    "my_int opPostInc()",
    asFUNCTION(opPostInc_gen),
    asCALL_GENERIC
);

None

UPDATE:

I also tried to register by native calling convention using a wrapper, in order to avoid interference of that unused int in the postfix version of operator++ in C++. It seems that the unused int is not the problem is.

my_int opPostInc_native(my_int& this_)
{
    return this_++;
}

// int main()
engine->RegisterObjectMethod(
    "my_int",
    "my_int opPostInc()",
    asFUNCTION(opPostInc_native),
    asCALL_CDECL_OBJLAST
);

But this time, while Linux+GCC still gives me a random result, the Windows+MSVC also gives me an unexpected output. It looks like the argument of opPostInc_native is passed by copy instead of desired passed by reference.

0
1
1
1

UPDATE 2:

It seems that the opPostDec also has the same problem. My temporary workaround is to register them by generic calling convention.

None

Advertisement

I believe the issue is with the use of asOBJ_CLASS_CDK. Since you have defaulted these they shouldn't be informes.

Try using asGetTypeTraits to get the correct flags.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

By using asGetTypeTraits, I get the following results:

  1. The output on Linux+GCC give a clear error message, telling me native calling convention is not supported on this platform.
  2. But the output on Windows+MSVC is interesting.

    The asCALL_THISCALL and asCALL_GENERIC both give me the expected result, which is 0 1 1 2. But the result of asCALL_CDECL_OBJLAST with the wrapper mentioned in the UPDATE section is still the unexpected 0 1 1 1.

Addition question: is there any document clarifying when to use a generic calling convention?

None

Now that I'm sitting in front of my computer I could review your code better.

As far as I can tell, the class my_int should be registered with the following flags: asOBJ_VALUE | asGetTypeTraits<my_int>() | asOBJ_APP_CLASS_ALLINTS.

https://angelcode.com/angelscript/sdk/docs/manual/doc_register_val_type.html#doc_reg_val_2

I'll have to investigate what happened when you used the opPostInc_native wrapper. I'll need to do some debugging to identify where it is going wrong.

"Addition question: is there any document clarifying when to use a generic calling convention?"

Not really. Obviously it must be used when native calling convention is not supported, but there are cases where generic calling convention can be more convenient as well. It may also be a case where the generic calling convention has less overhead than the native calling convention, thus leading to better performance. However it is not really possible to give precise instructions when it is better to use the generic calling convention as it depends on so many variables.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Advertisement