Help from a C expert sought!

I know this is a good forum for highly technical questions, so here goes. BBC BASIC for SDL 2.0 determines whether to use 64-bit doubles or 80-bit long-doubles for its 'suffixless' numeric variables using code like this:

#if defined __arm__ || defined __aarch64__ || defined __EMSCRIPTEN__

If any of those are defined it uses 64-bit doubles and if not 80-bit long doubles. But this breaks when running the Android edition on a Chromebook: it has an x86-64 CPU but no support for 80-bit long doubles in the C runtime library!

So I need to find an alternative way of determining the correct data type, preferably a test which more directly ascertains whether the C library does or does not support 80-bit long doubles. Any ideas?

Comments

  • [Richard Russell]
    edited February 2023
    Soruk wrote: »
    Does this help?
    Indeed, that's exactly what I'm doing as a workaround, and it's fine as far as it goes. But it's still an 'indirect' test, it's not actually determining whether the C runtime library supports the 80-bit long double data type or not. Hence it could easily fail should I (or more likely somebody else) try to port BBCSDL to another x86-64 platform which shares this curious characteristic.
  • Argh, this really is horrible! To make BBCSDL run on an x86_64 Chromebook I have to persuade it to use 64-bit doubles rather than 80-bit long doubles. So far so good, but it also means that the binary value of PI changes from the nearest 80-bit value to the nearest 64-bit value.

    You wouldn't think that would matter, but in several places (notably in some libraries) I use the binary value of PI as the way of telling whether BBCSDL is running on an x86 or an ARM! So now it thinks that the Chromebook is an ARM and all sorts of things fail (such as sortlib, which tries to use the wrong assembler code).

    You may feel that this was never a very satisfactory way of determining the CPU type, and you would be right but I don't know of a better alternative. Perhaps I should have incorporated a CPU identifier (in addition to 32/64-bit) in the @platform% value but hindsight is a wonderful thing.
  • You could create something like @cpu% that returns different values for i686, x86_64, aarch32 and aarch64, that won't negatively affect @platform% but does mean you now have 2 variables instead of 1 to convey this information. And/or @floatwidth% that returns 64 or 80, depending on how compiled.
  • floatwidth seems like a good plan, if that's the actual thing that matters here. Or longdoublewidth perhaps?
  • Soruk wrote: »
    You could create something like @cpu%...
    It sounds like such a simple change, doesn't it?! But adding an extra system variable like that is actually a lot of (risky) work, because they are declared as a linked-list in the 'data' module - of which there are five versions depending on the platform:

    bbdata_x86_32.nas
    bbdata_x86_64.nas
    bbdata_arm_32.s
    bbdata_arm_64.s
    bbdata_wasm32.c

    Two of them are x86 assembler code, two of them are ARM assembler code and one is C (of sorts). To give you an idea of what would be involved here is the relevant extract from bbdata_wasm32.c:
    int sysvar[127] = {
    	0x00000010, 0x686D656D, 0x00256364, 0x00000000, 		// 0   @memhdc%
    	0x00000010, 0x72617077, 0x00256D61, 0x00000000, 		// 4   @wparam%
    	0x00000012, 0x7261706C, 0x00256D61, 0x00000000, 		// 8   @lparam%
    	0x00100000, 0x77680000, 0x0025646E, 0x00000000,			// 12  @hwnd%
    	0x000E0000, 0x70680000, 0x00256C61, (int) palette,		// 16  @hpal%
    	0x0000000C, 0x0025786F, 0x00000000,				// 20  @ox%
    	0x0000000C, 0x0025796F, 0x00000000,				// 23  @oy%
    	0x00000013, 0x6C696668, 0x00282565, (int) stavar+447,		// 26  @hfile%(
    	0x10000000, 0x6D000000,	0x00256773, 0x00000000, 		// 30  @msg%
    	0x0E000000, 0x76000000, 0x00257564, (int) vduvar,		// 34  @vdu%
    	0x00001000, 0x70736900, 0x00256C61, 0x00000000, 		// 38  @ispal%
    	0x00000F00, 0x616C6600, 0x00257367, 0x00000000, 		// 42  @flags%
    	0x00000012, 0x63696F76, 0x00257365, 0x00000000, 		// 46  @voices%
    	0x00110000, 0x6F7A0000, 0x00256D6F, 0x00008000, 		// 50  @zoom%
    	0x14000000, 0x64000000, 0x00247269, 0x00000000, 0x00000000,	// 54  @dir$
    	0x14000000, 0x6C000000, 0x00246269, 0x00000000, 0x00000000,	// 59  @lib$
    	0x14000000, 0x63000000, 0x0024646D, 0x00000000, 0x00000000,	// 64  @cmd$
    	0x14000000, 0x75000000, 0x00247273, 0x00000000, 0x00000000,	// 69  @usr$
    	0x14000000, 0x74000000, 0x0024706D, 0x00000000, 0x00000000,	// 74  @tmp$
    	0x0F000000, 0x68000000, 0x00256F77, 0x00000000,			// 79  @hwo%
    	0x00120000, 0x6C700000, 0x6F667461, 0x00256D72, 0x00000000, 	// 83  @platform%
    	0x00000012, 0x6D726863, 0x00257061, 0x00000000,			// 88  @chrmap%
    	0x00100000, 0x61700000, 0x0025786E, 0x00000000, 		// 92  @panx%
    	0x00110000, 0x61700000, 0x0025796E, 0x00000000, 		// 96  @pany%
    	0x14000000, 0x76000000, 0x007B7564, (int) vdustr + 1, (int) vduvar, 	// 100 @vdu{
    	0x0E000000, 0x66000000, 0x0028256E, (int) fntab + 3, 		// 105 @fn%(
    	0x00001000, 0x6B726200, 0x00257470, 0x00000000,    		// 109 @brkpt%
    	0x00001100, 0x6B726200, 0x00256968, 0x00000000, 		// 113 @brkhi%
    	0x00140000, 0x69730000, 0x007B657A, (int) ptfmt, (int) vduvar + 208, 	// 117 @size{
    	0x00000000, 0x68630000, 0x007B7261, (int) ptfmt, (int) vduvar + 216};	// 122 @char{
    
    Do you see @platform% in there? Can you see how to modify this to add an extra system variable like @cpu%? No, me neither!

    I expect a clever man like you could have come up with some cunning C macro which would have automatically created this linked list at compile-time, but I wouldn't know where to start.

    So despite its superficial attraction, adding another system variable is not practical.
  • Soruk
    edited February 2023
    This might work, look at sizeof(long double) on your various platforms, you might be able to switch on that? On my RasPi this returns 8 (bytes), on x86_64 this returns 16! So, not quite sure what it's doing with those extra 6 bytes...

    Further testing, on x86_64 and aarch32 using your console mode interpreters...
    Doing
    f=2E309
    
    on ARM results in "Number too big", but on x86_64 the input is expected. Trapping that error (perhaps using a LOCAL ERROR construct) should allow you to be able to detect and switch within BASIC independently of your CPU type.
  • I think Richard has described two quite different problems: one is the build-time detection of 64 bit vs 80 bit long doubles, and the other is the run-time detection of ARM vs x86, for the purposes of running machine code.

    Previously, 80 bit floats were always available on x86, and only available on x86. Now, there's an x86 platform with 64 bit long doubles, and there's a question of how to tell at run time whether it's an ARM or not. Previously, checking PI would do, because it checks the size of a long double.

    One possibility, perhaps, is to assemble a [ NOP ] and see how much P% moves by - is it one byte or four.
  • Soruk
    edited February 2023
    Good point, I had lost sight of that.

    Also - with a quick test on ARM, P% jumped by 7! Then I realised my buffer didn't start on a 32-bit boundary...

    Using some preprocessor defines (that show up using gcc -dM -E - < /dev/null), you could check with something like
    #if __SIZEOF_LONG_DOUBLE__ >= 10
     (code for 80-bit)
    #endif
    
    This define may well be a GCC extension (can't test this, I don't have any other compiler, though a quick Google indicates Clang also supports this define), and if this define isn't defined (e.g. on ARM), the test fails so the code isn't built.

    On ARM32 this define is 8, x86_32 it's 12 and x86_64 it's 16.