Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assertion "st->c->value.argSize == func->parmTotal" from some Scripts #303

Closed
DanielGibson opened this issue Jul 26, 2020 · 17 comments
Closed

Comments

@DanielGibson
Copy link
Member

DanielGibson commented Jul 26, 2020

I ran into an assertion in the script interpreter when testing a dhewm3 mod in a Debug build: "st->c->value.argSize == func->parmTotal" from idInterpreter::Execute().
When ignoring or commenting out that assertion, another one in idInterpreter::LeaveFunction() triggers: "localstackUsed == localstackBase".

It turned out that the problem was the following:
We have a player "class" like:

object player : player_base {
	// .. lots of stuff not important for the issue
	void		init();

	float		music_volume;

	void		check_music_volume();
	// ... and more details
};

and then below there's implementations for those methods - and init() creates a thread with check_music_volume(), but the check_music_volume() implementation is below the init() implementation!

void player::init() {
	// .. whatever ..
	thread check_music_volume();
	// ...
}

// ...

void player::check_music_volume() {
	// ... does some stuff in an endless loop ...
	while(1) {
		if(music_volume > 0) {
			// .. do whatever ..
		}
	}
}

Now when I start a map and that code gets executed (player::init() is called when the player spawns), I get the aforementioned assertion (if assertions are enabled, like in debug builds), because the opcode pretends that the function call has no arguments at all (that need to be passed when calling), while in reality the "self" pointer to the player object is implicitly passed (so check_music_volume() knows what player object it belongs to and can access its fields, like music_volume).

This can be "fixed" by moving
void player::check_music_volume() { ... }
above
void player::init() { ... }

According to http://idtechforums.fuzzylogicinc.com/index.php?topic=897.0 this is a known problem in Doom3; it has also been mentioned in #204 (incl. the workaround of reordering); weirdly it doesn't seem to be documented anywhere else.
#265 (comment) also mentions this problem, with the Yet another flashlight Mod (which is script-only).

What is going wrong in the C++ Code

Now the problem was that the script compiler, when generating the ops for that thread creation
(idCompiler::ParseObjectCall() => idCompiler::EmitFunctionParms(), op == OP_OBJTHREAD)
it sets the wrong size for the statement, taken from func->value.functionPtr->parmTotal.
This happens at game startup, when all the scripts are parsed and compiled.

The reason that parmTotal for the "check_music_volume" function_t is 0 (while it should be 8, at least on my machine, for the self object reference) is, that even though an idTypeDef with type ev_function and the function_t is created when object player : player_base { is parsed and it encounters the void check_music_volume(); line, parmTotal is not calculated and set until the implementation of the function is parsed much later (when it reaches void player::check_music_volume() { way below).

It all happens in idCompiler::ParseFunctionDef(...) - that function is called both for the function declaration/prototype (void check_music_volume();) is called from idCompiler::ParseObjectDef() when it parses the player object, [i]and[/i] later when parsing the [i]implementation[/i] (after the bytecode for player::init() is generated), in that case it's called from ParseNamespace() => ParseDefs() => ParseFunctionDef().

The reason the second call calculates and sets parmTotal (and also numParams) correctly (and the first call doesn't) is the following lines in idCompiler::ParseFunctionDef(...):

	// check if this is a prototype or declaration
	if ( !CheckToken( "{" ) ) {
		// it's just a prototype, so get the ; and move on
		ExpectToken( ";" );
		return;
	}

	// calculate stack space used by parms
	numParms = type->NumParameters();
	func->parmSize.SetNum( numParms );
	for( i = 0; i < numParms; i++ ) {
		parmType = type->GetParmType( i );
		if ( parmType->Inherits( &type_object ) ) {
			func->parmSize[ i ] = type_object.Size();
		} else {
			func->parmSize[ i ] = parmType->Size();
		}
		func->parmTotal += func->parmSize[ i ];
	}

	// define the parms
	// ... etc ...

So when parsing the method declaration/prototype ParseFunctionDef(...) returns before doing the calculations for stack space needed by function parameters - this is only done when parsing the implementation!
So, if the method is called in the script file [i]before[/i] the the implementation, assert( st->c->value.argSize == func->parmTotal ); in idInterpreter::Execute() fails.

I have no idea why the stack space calculation etc isn't just done when parsing the prototype.

A Workaround

Modify idCompiler::EmitFunctionParms() like this:

	} else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) {
		EmitOpcode( op, object, VirtualFunctionConstant( func ) );

		// need arg size seperate since script object may be NULL
		statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 );
		int size = func->value.functionPtr->parmTotal; // DG: added this and the following lines
		if(size == 0) // can happen if function implementation hasn't been parsed yet
		{
			const idTypeDef* funcType = func->value.functionPtr->type;
			int calcSize = 0;
			int numParms = funcType->NumParameters();
			for( int i = 0; i < numParms; i++ ) {
				idTypeDef* parmType = funcType->GetParmType( i );
				if ( parmType->Inherits( &type_object ) ) {
					calcSize += type_object.Size();
				} else {
					calcSize += parmType->Size();
				}
			}
			if(calcSize > size) {
				size = calcSize;
			}
		}
		statement.c = SizeConstant( size ); // DG: modified this to use size
	} else {

A proper solution would be to do the full parameter calculation in ParseFunctionDef() when parsing the function prototype and not just when parsing the implementation - but then you gotta make sure the parameters and their sizes aren't added twice (when parsing the prototype and when parsing the implementation), of course, so it needs a bit of thinking and cautiousness.

(I originally documented this at http://idtechforums.fuzzylogicinc.com/index.php?topic=897.0 but I think we should have a proper issue here so I hopefully don't forget to "properly" fix it eventually)

DanielGibson added a commit that referenced this issue Aug 2, 2020
If a "class" (object) in a Script has multiple member function
prototypes, and one function implementation later calls another before
that is implemented, there was an assertion when the script was parsed
(at map start), because the size of function arguments at the call-site
didn't match what the function expected - because the function hadn't
calculated that size yet, that only happened once its own
implementation was parsed.
Now it's calculated (and stored) when the prototype/declaration is
parsed to prevent this issue, which seems to be kinda common with Mods,
esp. Script-only mods, as the release game DLLs had Assertions disabled.
@DanielGibson
Copy link
Member Author

I think I have a "proper" fix for this, see the script-fixes branch.

Not sure I dare to merge this before 1.5.1; testing+feedback is welcome!

@DanielGibson
Copy link
Member Author

Here is a windows build (latest git master + this script fix for base.dll and d3xp.dll): dhewm3-scriptfix.zip

As the script interpreter is part of the gamecode this must be fixed separately in every mod that needs its own DLL, so right now only the main game and RoE are fixed (and mods without their own DLL that use base.dll).

@AntonieS
Copy link

AntonieS commented Aug 3, 2020

dhewm3.exe do not unload properly from RAM. Process stay in memory.
Win 7 Ult SP1 x64, 64Gb RAM. No any antiviruses.

@DanielGibson
Copy link
Member Author

DanielGibson commented Aug 3, 2020

There's some kind of crash when ending the game due to a bug in the current openal-soft version - but I've never seen it not exit at all

@AntonieS
Copy link

AntonieS commented Aug 3, 2020

So, I have a situation during test when 7 processes named dhewm3.exe exists in memory.

@DanielGibson
Copy link
Member Author

Weird - I'll have to test on my old Win7 machine.
Is is sufficient to start dhewm3.exe and immediately quit from main menu?

@AntonieS
Copy link

AntonieS commented Aug 3, 2020

My test:
Load Dhewm3 (Doom3), load game/hellhole, exit from dhewm3 using main menu.

On version 1.5.1pre1304 there is no problem.

@DanielGibson
Copy link
Member Author

So the problem doesn't happen if you don't load the map?

Can you try if the problem goes away if you use 1.5.1rc2 with the OpenAL32.dll from 1.5.1pre ?

@AntonieS
Copy link

AntonieS commented Aug 3, 2020

Made a test: just load dhewm3 without load any map: still stay in memory.

Upd: test with OpenAL from 1.5.1.per1304: dhewm3 unload from RAM correctly.
By the way: I have Creative X-Fi Titanium HD card in system installed.

@DanielGibson
Copy link
Member Author

@AntonieS: Can you try if it works better when replacing OpenAL32.dll with this one:
OpenAL32.dll.zip

@AntonieS
Copy link

With new OpenAL.dll work well. But in Lost Mission crush when load exis1.

@DanielGibson
Copy link
Member Author

DanielGibson commented Aug 23, 2020

I need more information on the exis1 crash.
Does it happen if you do map game/le_exis1? Or only from a savegame or something? If so, can you share that savegame?
Does it crash while loading, directly after loading or ... ?
Do you have any further information on the crash, like errors being displayed when it crashes?

@AntonieS
Copy link

I became crash only when load savegame (while loading game hangs). Whean load from console - all Ok.
Exis1 autosave
https://yadi.sk/d/5AQ-ftycKTGd1Q
No errors displayed.

@AntonieS
Copy link

With new dll sound is much better. In both rc2 and pre2 versions.

DanielGibson added a commit that referenced this issue Sep 5, 2020
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  #303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit that referenced this issue Sep 5, 2020
If a "class" (object) in a Script has multiple member function
prototypes, and one function implementation later calls another before
that is implemented, there was an assertion when the script was parsed
(at map start), because the size of function arguments at the call-site
didn't match what the function expected - because the function hadn't
calculated that size yet, that only happened once its own
implementation was parsed.
Now it's calculated (and stored) when the prototype/declaration is
parsed to prevent this issue, which seems to be kinda common with Mods,
esp. Script-only mods, as the release game DLLs had Assertions disabled.
@DanielGibson
Copy link
Member Author

@AntonieS: The crash should be fixed in the latest build, can you test it? dhewm3_1.5.1_RC3_win32.zip

@AntonieS
Copy link

AntonieS commented Sep 5, 2020

Now all works fine. Thanks.

@DanielGibson
Copy link
Member Author

Good to know, thanks for testing!

By the way, I also put the fix for that script assertion into the master branch and it's also in the 1.5.1 RC3 build linked above, so I guess this can be closed

DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Sep 14, 2020
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Jan 18, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Jan 18, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Jan 18, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Jan 18, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Jan 18, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Jan 18, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
DanielGibson added a commit that referenced this issue Feb 7, 2021
"Fix "t->c->value.argSize == func->parmTotal" Assertion in Scripts, #303"
had broken old savegames because the script checksum
(idProgram::CalculateChecksum()) changed, see #344.
This is fixed now, also the BUILD_NUMBER is increased so old savegames
can be identified for a workaround.

Don't use this commit without the next one which will further modify the
savegame format (for the new BUILD_NUMBER 1305)

Before merging this to master I'll also modify d3xp/ accordingly
DanielGibson added a commit that referenced this issue Feb 7, 2021
"Fix "t->c->value.argSize == func->parmTotal" Assertion in Scripts, #303"
had broken old savegames because the script checksum
(idProgram::CalculateChecksum()) changed, see #344.
This is fixed now, also the BUILD_NUMBER is increased so old savegames
can be identified for a workaround.

Don't use this commit without the next one which will further modify the
savegame format (for the new BUILD_NUMBER 1305)
DanielGibson added a commit that referenced this issue Feb 21, 2021
"Fix "t->c->value.argSize == func->parmTotal" Assertion in Scripts, #303"
had broken old savegames because the script checksum
(idProgram::CalculateChecksum()) changed, see #344.
This is fixed now, also the BUILD_NUMBER is increased so old savegames
can be identified for a workaround.

Don't use this commit without the next one which will further modify the
savegame format (for the new BUILD_NUMBER 1305)
DanielGibson added a commit to dhewm/dhewm3-sdk that referenced this issue Feb 23, 2021
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
rorgoroth pushed a commit to rorgoroth/dhewm3 that referenced this issue Apr 8, 2023
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
rorgoroth pushed a commit to rorgoroth/dhewm3 that referenced this issue Apr 8, 2023
…ewm#303

If a "class" (object) in a Script has multiple member function
prototypes, and one function implementation later calls another before
that is implemented, there was an assertion when the script was parsed
(at map start), because the size of function arguments at the call-site
didn't match what the function expected - because the function hadn't
calculated that size yet, that only happened once its own
implementation was parsed.
Now it's calculated (and stored) when the prototype/declaration is
parsed to prevent this issue, which seems to be kinda common with Mods,
esp. Script-only mods, as the release game DLLs had Assertions disabled.
rorgoroth pushed a commit to rorgoroth/dhewm3 that referenced this issue Apr 8, 2023
"Fix "t->c->value.argSize == func->parmTotal" Assertion in Scripts, dhewm#303"
had broken old savegames because the script checksum
(idProgram::CalculateChecksum()) changed, see dhewm#344.
This is fixed now, also the BUILD_NUMBER is increased so old savegames
can be identified for a workaround.

Don't use this commit without the next one which will further modify the
savegame format (for the new BUILD_NUMBER 1305)
IvanTheB pushed a commit to IvanTheB/fraggingfree-dhewm3-sdk that referenced this issue Apr 29, 2023
It corrupted the stack when called with buffers allocated on the stack
and numSamples that are not a multiple of four, apparently, by writing
4 floats too many, at least in the 22KHz Stereo case..

This caused the crash described in
  dhewm/dhewm3#303 (comment)

Now it just uses the generic C code, like all platforms besides MSVC/x86
already do.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants