Reflection isn't able to find Final methods

Archives Forums/BlitzMax Bug Reports/Reflection isn't able to find Final methods

Yasha(Posted 2015) [#1]
Uncomment either of the Final declarations in the code below:

Import BRL.Reflection
SuperStrict

Type a Abstract
	Method foo()
	End Method
End Type

Type b Extends a 'Final
	Method bar() 'Final
	End Method
End Type

Local obj:b= New b

Local id:TTypeId = TTypeId.ForObject(obj)
For Local m:TMethod = EachIn id.EnumMethods()
	Print m.name() + ": " + m._index
Next


...and you'll find that the indexing is all messed up. The app will crash if it tries to invoke this.

My guess is that this is somehow related to Max emitting direct function references instead of pointer calls for Final methods, but this surely shouldn't also be applied to the debug information that reflection queries to get offsets? Reflection doesn't care whether the method in question is being optimized elsewhere in the code... it needs a pointer either way.

(How has this gone unnoticed for so long? Does nobody use Final types?)


Brucey(Posted 2015) [#2]
Here's the details of b with and without Final..

Non-Final
_12:
	dd	2
	dd	_13
	dd	6
	dd	_8
	dd	_9
	dd	16
	dd	6
	dd	_10
	dd	_9
	dd	20
	dd	6
	dd	_14
	dd	_9
	dd	52
	dd	0
	align	4


and with Final
_12:
	dd	2
	dd	_13
	dd	6
	dd	_8
	dd	_9
	dd	_bb_b_New
	dd	6
	dd	_10
	dd	_9
	dd	_bb_b_Delete
	dd	6
	dd	_14
	dd	_9
	dd	_bb_b_bar
	dd	0
	align	4


Although interestingly, Invoke() in TMethod does do a check on _index to determine if it is a valid index or a reference :
	Method Invoke:Object( obj:Object,args:Object[] )
		If _index<65536
			Return _Call( bbRefMethodPtr( obj,_index ),_typeId,obj,args,_argTypes )
		EndIf
		Return _Call( Byte Ptr(_index),_typeId,obj,args,_argTypes )
	End Method


So it appears it is all by design.

Since _index is an internal variable, you wouldn't be using it anyway :-)

Where is it crashing for you?

Works fine for me when I invoke the method using :
m.Invoke(obj, Null)



Yasha(Posted 2015) [#3]
Using this minor modification (so it actually tries):



I get this output:

New: 16
Delete: 20
foo: 48
New: 16
Delete: 20
bar: 52
in bar


...which changes to this if I uncomment Final:

New: 16
Delete: 20
foo: 48
New: 10573
Delete: 10615
bar: 10652


(...boom) In debug mode the offsets are in the 20k-range instead, to much the same effect. No other settings I have found affect this noticeably.

This is on a clean install of BlitzMax on OSX. I didn't test Invoke on my Windows box yesterday (only the offset printing, not having noticed that Invoke does actually do a Final check; wasn't paying enough attention), and it actually works fine on that machine after all. Linux doesn't have this problem either (hah). I guess I'll install it again on OSX and see if it persists in case I somehow screwed up the installation.

EDIT: behaviour persists on a completely fresh install. Unzip -> build modules -> paste -> run -> boom.


Brucey(Posted 2015) [#4]
Yes, it appears to be a mistaken assumption that a function pointer will never have an address below 0x10000, which on OS X appears to be a possibility.


Yasha(Posted 2015) [#5]
GAH I nearly found a fix for this...

The -sectalign option to lld allows one to specify the alignment of a section, so I added this line after line 280 of bmk_util.bmx:

cmd:+" -Wl,-sectalign,__TEXT,__text,0x10000"


...reasoning that since it can't place code at 0, a minimum alignment of 0x10000 will mean that all code is emitted above that point.

This is what it says in response:

ld: argument for -sectalign must be less than or equal to 0x8000


Using the maximum value of 0x8000 does move the code, but not enough - it puts it at around 35k for the above example.


Brucey(Posted 2015) [#6]
A fix would be to do away with indexes altogether :-)


Yasha(Posted 2015) [#7]
Indeed. But that requires changing the compiler to emit consistent class descriptions (unless there's some reason why the Final optimization needs to apply anywhere other than at actual method call sites?).

I have no idea what I was thinking with -sectalign actually... modifying bmk only is no better than changing BRL.Mod or swapping out the compiler as far as compatibility with other existing users goes. They've either patched their installation or they haven't, so that wasn't really any kind of useful solution.


Brucey(Posted 2015) [#8]
unless there's some reason why the Final optimization needs to apply anywhere other than at actual method call sites?

I've no idea why it's done differently for Final, since, as you say, it's up to the compiler to decide what you can extend or override. I don't see why it can't be indexes for Final types either.


marksibly_v2(Posted 2015) [#9]
A fix for this is to change _index<65536 to _index<4096 in reflection.bmx

According to 'man ld', this will be safe because 32 bit apps have a 4K 'page zero' which will always cause an error when read. This can be changed via ld options, but it seems like a good default anyway.

This does mean vftbl indices have to be <4K, but this allows for up to 1000-ish methods which is quite a lot!

I probably did it this way so final methods called be called directly without going via the vftbl, ie: if the compiler knows that the static type contains a reference to a final method, it doesn't have to use indirection to call the method.

In the case of final methods that don't override anything, there may not even be a vftbl slot 'allocated' for them, although it probably would have been cleaner to do so anyway.