OSWORD parameter block address

OSWORD (CALL &FFF1) takes a parameter block whose address is passed in Y% and X%. In 8-bit versions of BBC BASIC this is a 16-bit address with the most-significant byte in Y% and the least-significant byte in X%; in 32-bit versions it is a 32-bit address with the MS 24-bits in Y% and the LS 8-bits in X%.

But there's a problem with 64-bit versions of BBC BASIC because this scheme is capable of passing only a 40-bit signed address (MS 32-bits in Y%, LS 8-bits in X%) and whilst this is often sufficient, it isn't always. So I've been wondering how best to resolve this.

Obviously Y%:X% is in principle capable of passing a 64-bit address (32+32) but I can't see an entirely reliable way of doing that whilst maintaining compatibility with other versions. The best I can come up with is a rule such as 'if X% is a sign-extended 8-bit number it's a 32 or 40-bit address and if not it's a 64-bit address'.

But that would fail in the (admittedly unlikely) case of a 64-bit address which by chance had bits 7 to 31 all the same. Can anybody think of a better way?

Comments

  • In Matrix Brandy, the only reliable way to call these MOS interfaces on 64-bit platforms is to do it the RISC OS way, via SYS "OS_Word", A%, block%% (SYS can take 64-bit values in 64-bit hardware).

    That said, on RISC OS, while BASIC V intercepts the old MOS entry routines in CALL and USR, BASIC VI does not so the only way to call them is via SYS.
  • Soruk wrote: »
    In Matrix Brandy, the only reliable way to call these MOS interfaces on 64-bit platforms is to do it the RISC OS way, via SYS "OS_Word"
    Indeed, but that's only possible on an Acorn platform (or an emulator of an Acorn platform) because SYS "function_name" is supposed to access the native OS API. In RISC OS, SYS "OS_Word" is a native OS API call, but In Windows, SDL2, Linux etc. it isn't!

    So, in general, SYS "function_name" doesn't help here. Admittedly SYS numeric_value isn't inherently limited to accessing an OS API function, so that could be used. And indeed the workaround I am currently adopting does involve using SYS that way, but I'd prefer the CALL &FFF1 approach to work as well if that was possible.

    In the meantime I should probably amend the BBCSDL documentation to make it clear that CALL &FFF1 isn't reliable.
  • If you want to use the SYS (number) approach, I would personally recommend using the same numbers used by RISC OS (and Matrix Brandy). The easiest way to get the number is, in Brandy, to do:
    PRINT SYS("OS_Word") - in this case, 7.
  • Soruk wrote: »
    If you want to use the SYS (number) approach, I would personally recommend using the same numbers used by RISC OS (and Matrix Brandy).
    I don't have that freedom. In all my versions of BBC BASIC (indeed all non RISC OS versions I think) the number in SYS number is the memory address of the function entry point. Put another way, it's the function pointer. This is fundamental to how the binary ABI works in most Operating Systems, and is completely out of my control.

    RISC OS is quite exceptional in this regard (I suppose because it is based on the ARM CPU which has the SWI instruction) in taking not a function pointer but an index. The only similar feature I can think of on another CPU is the Z80's RST instruction.

    In RISC OS BBC BASIC, how do you call a function in a shared library?
  • I don't have that freedom.
    To be fair I could, in principle, intercept 'impossible' values for the SYS function pointer and make them do something special, that is after all what CALL does in treating &FFxx as a special case of code entry point. But I can't see any particular advantage considering the complication, and overhead, of having to make that test on every SYS call.
  • Sounds like a good plan to me - as you say, similar to the FFxx case, and in the case of making OS calls, a few extra microseconds is unlikely to be an issue.
  • BigEd wrote: »
    Sounds like a good plan to me - as you say, similar to the FFxx case, and in the case of making OS calls, a few extra microseconds is unlikely to be an issue.
    I'm surprised that you would say "a few extra microseconds is unlikely to be an issue" when SYS calls can be some of the most time-critical statements of all.

    I'm thinking particularly of things like the gfxlib library, which may make thousands of SYS calls every frame - turning a few extra microseconds into possibly several milliseconds, and the frame period is typically only 16.7 ms!

    But as far as I'm concerned the overriding consideration is if it ain't broke don't fix it. If CALL &FFF1 cannot be made to work reliably on a 64-bit platform, my current workaround using SYS certainly does. So why change it?
  • Ah yes, graphics - I was thinking of file accesses. I bet it's not even microseconds though - a nanosecond to check a number against a constant, perhaps?
  • I'm thinking particularly of things like the gfxlib library, which may make thousands of SYS calls every frame
    Consider, for example, the number of SYS calls being made every frame in this game, which has the distinction of being (I think) the only 100% BBC BASIC program to win outright an international games programming competition.
  • Another thought, since the names you pass are library function calls, can you create a new function inside BBCSDL (e.g. "bbcmos") so you could then do something like SYS "bbcmos", &FFF1, A%, block%%

    That shouldn't add any overhead to your SYS handler.
  • BigEd wrote: »
    I bet it's not even microseconds though - a nanosecond to check a number against a constant, perhaps?
    I am a bit paranoid when it comes to making something slower, even if only slightly slower. The trouble is you never actually know how close you are to exceeding a critical time period, until you do!

    Suppose some particular program is within 1 millisecond of exceeding a frame period, but you didn't actually know that it was. Say I then make the modification you propose, and despite the small time penalty it pushes the total duration just over the limit. Now not only will there be dropped frames, but probably sound stutters too.

    The only way you can be totally sure that a program that works now will continue to work is not to slow it down at all.
  • Soruk wrote: »
    you could then do something like SYS "bbcmos", &FFF1, A%, block%%
    As I said, SYS "function_name" is supposed to call OS API functions (and functions in shared libraries, e.g. DLLs in Windows) not internal functions. It would be the start of a slippery slope to make an exception for this specific issue.

    I would reiterate my previous comment. My existing workaround for &FFF1 not being reliable on 64-bit platforms works fine. Why are you so determined to give me more work, and break compatibility with existing programs, by changing it to something else?
  • My existing workaround for &FFF1 not being reliable on 64-bit platforms works fine.
    It relies on the existence of the @fn%() function table, which has been in BB4W for a long time (possibly not right back to 2001, but certainly many years ago). It's documented here, although only from the specific viewpoint of calling the internal functions from assembly language code, not BASIC code (parameters are passed in CPU registers).

    So it's not as though I've devised something entirely new specifically for the &FFF1 workaround, but rather that I've extended an existing data structure, and made the new functions callable by SYS. I hope that might make you slightly less critical of this approach.
  • I wasn't critical of your approach, it was more like your first post appeared to be asking for ideas, a misinterpretation I unreservedly apologise for.

    Can you give an example of how I would use this, as in my Note Quiz game I do use OSWORD, already for Matrix Brandy if detected I use the RISC OS type call for that very reason, and while I've not yet encountered a problem with the BBC style call on BBCSDL on 64 bits, as you say it can't be reliable. I am writing it to work on the BBC B and later machines, including modern 64-bit kit running BBCSDL or Matrix Brandy.
  • Soruk wrote: »
    I wasn't critical of your approach, it was more like your first post appeared to be asking for ideas
    I was asking for ideas for making the conventional CALL &FFF1 method of calling OSWORD work, because, if possible at all, that would restore good compatibility across the whole range of BBC BASIC versions from 8-bit to 64-bit.

    I was hoping that there might be some cunning way of reliably using Y%+X% for both 32-bit addresses and 64-bit addresses, which I had missed, but it seems that there isn't. Existing programs which contain Y% = parblk DIV 256 are bound to fail with a 'Number too big' error if the parameter block isn't at a reachable address.

    I had already been forced into devising a workaround (using SYS as it happens) otherwise I would have been left with a non-working application; therefore I am not particularly looking for suggestions for alternative workarounds that don't involve CALL &FFF1.
    Can you give an example of how I would use this
    I have already updated the documentation, I linked to it previously. I don't claim that it is elegant, but the @fn%(19) and @fn%(20) table entries were already in BBCSDL so it was a no-brainer to use them. The @fn%(21) entry is new (and not yet in any released version).

    It is undeniably true that the workaround I have adopted is messier and less compatible than had I added an @fn%() table entry corresponding to your SYS "OS_Word"; to be frank I had completely forgotten that SYS call existed. But it's no worse than dozens of other, avoidable, existing incompatibilities between Matrix Brandy and BBCSDL.

    Although I was, of course, aware of most of the changes Sophie made in BBC BASIC for the Acorn Archimedes, having never owned or used a RISC OS machine I was almost entirely ignorant about how the OS impacted on BASIC. That was true from its introduction in 1986 or thereabouts to when I first encountered the RISC-OS compatibility layer in Matrix Brandy.
  • Soruk
    edited September 2023
    Thank you for that, I'm updating Note Quiz to use this (Edit: That's in and working). However (and I could be wrong), in your OSWORD 10 example you're reserving 8 bytes in the DIM statement when I think you need to reserve 9 (character value + 8 bytes of character data). Yep, classic off by one error on my part. I knew arrays were zero based, but I totally forgot that reserving memory was also one more byte than specified. (Need to check if Matrix Brandy also does this ... Yes, it does.)
    But it's no worse than dozens of other, avoidable, existing incompatibilities between Matrix Brandy and BBCSDL.
    Agreed, and my program sets a few platform variables determined by INKEY(-256) which are then switched on to decide (primarily OSWORD 10) what approach to take - the BBC needs &FFF1, RISC OS could get away with that but doesn't work with BASIC VI so the SYS "OS_Word" call is used. Matrix Brandy also uses SYS"OS_Word" as I can give it a 64-bit pointer, and now BBCSDL using your SYS @fn(19) call - all 4 supported platforms need different ways of calling it! I don't think there's a tidier way to do this beyond abstracting the OSWORD call and reading the resulting data block to a procedure and function pair.

    I'm reminded back when I was claiming &4D for Matrix Brandy and JGH was decrying this "misuse" of it, you chimed in with the excellent point that once the program knows what interpreter it is running on, it can do more interpreter-specific things to identify what platform it's on and other things that depend much more strongly on the interpreter rather than whether it's running on Linux or Windows.
  • [Richard Russell]
    edited September 2023
    Soruk wrote: »
    I knew arrays were zero based, but I totally forgot that reserving memory was also one more byte than specified.
    I assume Sophie did that (if indeed it was her decision) precisely in order for the two forms of DIM to work the same way:
          DIM a(8)
          DIM a 8
    
    Each reserves memory from index 0 to 8 inclusive: a(0) to a(8) and a?0 to a?8. It can certainly be a source of confusion, but at least the most likely mistake will allocate too much memory rather than too little.

    There is however a difference between my BASICs and ARM BASIC (and probably Brandy) which is that in mine the memory block isn't guaranteed to be aligned but in ARM BASIC it is (a will be a multiple of 4). This stems from ARM CPUs having strict alignment requirements but x86 CPUs not.

    I do take special account of this in the ARM assemblers though: even if the memory block is not aligned, the assembled code is. But that can give rise to the slightly surprising result that code% and begin are not necessarily the same value here:
          DIM code% 1000
          P% = code%
          [.begin nop
    
    (begin is always aligned, in the case of ARM assemblers only; code% isn't necessarily).
  • Just one further comment on the OSWORD issue. The @fn%(19) - @fn%(21) table entries correspond to existing routines in the SDL2_gfxPrimitives library (the 2D graphics extension for SDL2). So it's not as though I have deliberately designed them to be incompatible.

    Arguably one advantage they have over OSWORD is that when reading the character bitmaps (either the 8x8 characters in MODEs 0-6 or the 16x20 characters in MODE 7) you need to make just one SYS @fn%(19) call - it returns the base address of the font table - rather than one for each character.

    You can similarly change the entire bitmap font in one SYS @fn%(20) call, by passing a pointer to a new table, although this isn't terribly useful if you just want to redefine a few characters (you would first have to make a copy of the old font and then modify just those characters).

    You can do something silly like this though:
          MODE 6
          IF POS REM SDL thread sync
          SYS @fn%(19) TO font%%
          SYS @fn%(20), font%% + 8, 8, 8
          PRINT "Hello world!"
    

    which prints:

    yzyxxrjwgajm.png
  • Am I doing something wrong here? This workaround is crashing the WASM build.ku2rckp7jp3s.png
  • Soruk wrote: »
    This workaround is crashing the WASM build.
    It will. I only documented those workarounds a day or so ago - they are a work-in-progress - and I know they don't yet work in the in-browser edition (it's because of the run-time function signature checking which is unique to Web Assembly). Nor have I updated mode7lib.bbc to use them, so that may fail too. Everything works here locally, but not yet in released versions.

    I fear that your expectation that they ought to work is a clash of cultures! I know that you are only too willing to release new versions of Matrix Brandy on a whim, with what I can only assume is minimal testing. But that's not how I do things: modifications get planned, implemented and only after extensive testing released. It's quite normal for the process to take 6-8 weeks.

    I can expedite updating the in-browser edition if you like: at least that is something I have complete control over and doesn't need users to proactively download and update their copies. But normally it only gets updated at the same time as all the rest - Windows, MacOS, 32-bit Linux, 64-bit Linux, 32-bit PiOS, 64-bit PiOS, Android (all 4 ABIs) and iOS!
  • No need to expedite it on my behalf - I'll roll back my .bbc file to use the CALL &FFF1 code for now, and that works fine in the browser.
  • Soruk wrote: »
    No need to expedite it on my behalf - I'll roll back my .bbc file to use the CALL &FFF1 code for now, and that works fine in the browser.
    As far as I know there's no prospect of 64-bit Web Assembly being rolled out any time soon, so at the moment it's very much a 32-bit platform. As such it isn't affected by the OSWORD addressing issue, and using CALL &FFF1 is completely safe when running in a browser.

    A 'universal' approach would be to use ON ERROR to trap the 'Number too big' error which will result from Y% = parblk DIV 256 if the address is unreachable, and only then switch to using the 64-bit workaround code.
  • The in-browser edition has been updated, @fn%(19) now works correctly:

    1mhusiho3zea.png
  • Thank you! I had worked around it by (rather than using ON ERROR) using @platform% to determine if the platform was 64-bit, but this may simplify the code.
  • Soruk wrote: »
    I had worked around it by (rather than using ON ERROR) using @platform%
    The ON ERROR method directly tackles the source of the problem: the address not being reachable by the Y%:X% mechanism. Even on 64-bit systems, it usually is reachable that way, as evidenced by the fact that the current release of BBCSDL (v1.37a) uses that code everywhere, even on 64-bit systems, and it works!

    It was only after a recent change in 64-bit Windows 11 that it stopped working and drew my attention to the issue, but of course I don't distribute a 64-bit Windows edition of BBCSDL! So nobody else is likely even to have encountered it, and may not unless other platforms change the way memory gets allocated.

    So I prefer the ON ERROR method as continuing to use CALL &FFF1 almost always. Of course if you want the program to remain compatible with 6502 BBC BASIC you can't use ON ERROR LOCAL, and that does limit where and how you trap the error. I presume it's a requirement that your code runs on BASIC 1 and BASIC 2, hence it using ON GOSUB rather than the much nicer ON PROC (which is implemented in 6502 BASIC 4 I think).
  • Soruk
    edited September 2023
    Yep, ON PROC works on my Master (indeed, I originally used ON PROC but then it failed when I loaded it into Elkulator), but because I'm targeting BASIC 2 on the BBC/Electron (not tried BASIC 1 yet, the notable differences there are no OSCLI, which I only use on non-BBC platforms, and the OPENIN/OPENUP/OPENOUT which changed in behaviour and I use all 3), I needed ON GOSUB instead. The program is already large enough that it has to be crunched pretty hard else it won't fit on a BBC with PAGE=&1900 and MODE 1's HIMEM=&3000 - and that's after all lines with "REM NotBeeb" being removed as it's imported from plaintext into B-Em.

    It's certainly been quite an exercise in writing a fairly complex program that will work on the original Beeb and all the modern flavours, and handling the various quirks that may be encountered.
  • Soruk wrote: »
    OPENIN/OPENUP/OPENOUT which changed in behaviour and I use all 3
    I think I'm right in saying that they changed in behaviour in such a way that 'tokenised' programs (which is of course the norm everywhere except Brandy) continued to work, because the original token for OPENIN was reassigned to OPENUP, as was the original functionality of OPENIN.

    Since you'll almost certainly want to use a tokenised program for the Beeb anyway (because it can be crunched more aggressively and still work) you can probably achieve compatibility with BASIC 1 by using OPENUP everywhere you currently use OPENIN. When loaded into BASIC 1 those OPENUPs will list as OPENINs.
  • Soruk
    edited September 2023
    Yep, just trying that now actually, just put together a custom machine model in B-Em with a BBC B, DFS and BASIC 1.

    Update: It works on DFS, but B-Em's VDFS creates a new file! I suspect that to be a B-Em or VDFS bug.
    I've raised a bug report for this.