Remix.run Logo
kevindamm 4 days ago

Preemptive multithreading is better than cooperative multithreading (which windows 3 used) but then it's de-fanged by allowing the threads and process to adjust their own priority and set arbitrary lower bounds on how much time gets allotted to a thread per thunk.

Then there's this:

   > All of the OS/2 API routines use the Pascal extended keyword for their calling convention so that arguments are pushed on the stack in the opposite order of C. The Pascal keyword does not allow a system routine to receive a variable number of arguments, but the code generated using the Pascal convention is smaller and faster than the standard C convention.
Did this choice of a small speed boost over compatibility ever haunt the decision makers, I wonder? At the time, the speed boost probably was significant at the ~Mhz clock speeds these machines were running at, and Moore's Law had only just gotten started. Maybe I tend to lean in the direction of compatibility but this seemed like a weird choice to me. Then, in that same paragraph:

   > Also, the stack is-restored by the called procedure rather than the caller.
What could possibly go wrong?
mananaysiempre 4 days ago | parent | next [-]

16-bit Windows used the Pascal calling convention, with the documentation in the Windows 1.0 SDK only listing Pascal function declarations. (Most C programs for 16-bit Windows use FAR PASCAL in their declarations—the WINAPI macro was introduced with Win32 as a porting tool.) The original development environment for the Macintosh was a Lisa prototype running UCSD Pascal, and even the first edition of Inside Macintosh included Pascal declarations only. (I don’t know how true it is that Windows originated as a porting layer for moving (still-in-development) Excel away from (still-in-development) Mac, but it feels at least a bit true.) If you look at the call/return instructions, the x86 is clearly a Pascal machine (take the time to read the full semantics of the 80186’s ENTER instruction at some point). Hell, the C standard wouldn’t be out for two more years, and function prototypes (borrowed early from the still-in-development C++, thus the unhinged syntax) weren’t a sure thing. C was not yet the default choice.

>> Also, the stack is restored by the called procedure rather than the caller.

> What could possibly go wrong?

This is still the case for non-vararg __stdcall functions used by Win32 and COM. (The argument order was reversed compared to Win16’s __far __pascal.) By contrast, the __syscall convention that 32-bit OS/2 switched to uses caller cleanup (and passed some arguments in registers).

Uvix 4 days ago | parent | next [-]

I don't know if Windows started as a porting layer but it certainly ended up as one. Windows was already on v2.x by the time Excel was released on PC, but the initial PC version of Excel shipped with a stripped-down copy of Windows so that it could still run on machines without Windows. https://devblogs.microsoft.com/oldnewthing/20241112-00/?p=11...

p_l 3 days ago | parent | next [-]

Before Windows 3.0 made a big splash, it was major source of Windows revenue - bundling a stripped down windows runtime with applications as GUI SDK.

Windows 3.0 effort was initially disguised as update for this before management could be convinced to support the project.

3 days ago | parent | prev [-]
[deleted]
JdeBP 3 days ago | parent | prev [-]

The Windows as a porting layer story is a whole subject in its own right. But the WINAPI macro was a porting thing.

But this is an example on this very page of the telephone-game problem that happened during the Operating System Wars, where the porting tool of the WINAPI macro that Microsoft introduced into its DOS-Windows SDK, allowing 32-bit programmers to divorce themselves from the notion of "far" function calls that 16-bit programmers had to be very aware of, becomes intertwined into a larger "Windows is a porting layer" tale, despite the two being completely distinct.

That a couple of applications essentially supplied 16-bit Windows as a runtime really was not related to the 16-bit to 32-bit migration, which came out some while after DOS-Windows was a standalone thing that one ran explicitly, rather than as some fancy runtime underpinnings for a Microsoft application.

* https://jdebp.uk/FGA/function-calling-conventions.html#WINAP...

dnh44 4 days ago | parent | prev | next [-]

I loved OS/2 but I also remember the dreaded single input queue... but it didn't stop me using it until about 2000 when I realised it was time to move on.

JdeBP 3 days ago | parent | next [-]

You actually mis-remember. One of the things that was a perpetual telephone-game distortion during the Operating System Wars was people talking about a single input queue.

Presentation Manager did not have a single input queue. Every PM application had its own input queue, right from when PM began in OS/2 1.1, created by a function named WinCreateMsgQueue() no less. There were very clearly more than 1 queue. What PM had was synchronous input, as opposed to asynchronous in Win32 on Windows NT.

Interestingly, in later 32-bit OS/2 IBM added some desynchronization where input would be continued asynchronously if an application stalled.

Here's Daniel McNulty explaining the difference in 1996:

* https://groups.google.com/g/comp.os.os2.beta/c/eTlmIYgm2WI/m...

And here's me kicking off an entire thread about it the same year:

* https://groups.google.com/g/comp.os.os2.programmer.misc/c/Lh...

dnh44 3 days ago | parent [-]

Thanks for the reminder! It’s very likely I read that post as a teenager.

chiph 3 days ago | parent | prev [-]

Because of that, I got good at creating multi-threaded GUI apps. Stardock were champs at this - they had a newsgroup reader/downloader named PMINews that took full advantage of multithreading.

The rule of thumb I had heard and followed was that if something could take longer than 500ms you should get off the UI thread and do it in a separate thread. You'd disable any UI controls until it was done.

dnh44 3 days ago | parent | next [-]

I always liked Stardock; if had to use Windows I'd definitely just get all their UI mods out of the nostalgia factor.

silon42 3 days ago | parent | prev [-]

Why do I remember it was 50ms?

chiph 11 hours ago | parent [-]

You're probably right. It was long long ago... I keep meaning to look at ArcaOS but I never seem to have the hardware to dedicate to it at the same time my interest returns.

maximilianburke 4 days ago | parent | prev | next [-]

Callee clean-up was (is? is.) standard for the 32-bit Win32 API; it's been pretty stable now for coming up on 40 years now.

jvert 3 days ago | parent | next [-]

Early in Win32 development, the x86 calling convention was __cdecl. I did all the work to change it to __stdcall (callee clean-up). Yes, it was done purely for performance reasons. It was a huge change to the codebase and as a side-effect turned up a lot of "interesting" code which relied on cdecl calling conventions.

scottlu2 3 days ago | parent [-]

Good times.

to11mtm 4 days ago | parent | prev [-]

For 32 bit yes, although IIRC x64 convention is caller clean-up.

maximilianburke 4 days ago | parent [-]

That's why I said 32-bit Win32 :-)

flohofwoe 4 days ago | parent | prev | next [-]

> Did this choice of a small speed boost over compatibility ever haunt the decision makers,

...in the end it's just another calling convention which you annotate your system header functions with. AmigaOS had a vastly different (very assembly friendly) calling convention for OS functions which exclusively(?) used CPU registers to pass arguments. C compilers simply had to deal with it.

> What could possibly go wrong?

...totally makes sense though when the caller passes arguments on the stack?

E.g. you probably have something like this in the caller:

    push arg3      => place arg 3 on stack
    push arg2      => place arg 2 on stack
    push arg1      => place arg 1 on stack
    call function  => places return address on stack
...if the called function would clean up the stack it would also delete the return address needed by the return instruction (which pops the return address from the top of the stack and jumps to it).

(ok, x86 has the special `ret imm16` instruction which adjusts the stack pointer after popping the return address, but I guess not all CPUs could do that back then)

agent327 3 days ago | parent [-]

AmigaOS only used D0 and D1 for non-ptr values, and A0 and A1 for pointer values. Everything else was spilled to the stack.

flohofwoe 3 days ago | parent [-]

Ah ok, I remembered wrong then. Thanks for the correction.

karmakaze 3 days ago | parent | prev | next [-]

I remember learning this while I was on Microsoft's campus learning about OS/2 APIs. As I recall they said about 7% faster which is hard to leave on the table. I never had any issue with it the whole time I developed for OS/2 as well as Windows NT that used the same convention.

rep_lodsb 4 days ago | parent | prev | next [-]

On x86, the RET instruction can add a constant to the stack pointer after popping the return address. Compared to the caller cleaning up the stack, this saves 3 bytes (and about the same number of clock cycles) for every call.

There is nothing wrong with using this calling convention, except for those specific functions that need to have a variable number of arguments - and why not handle those few ones differently instead, unless you're using a braindead compiler / language that doesn't keep track of how functions are declared?

skissane 3 days ago | parent | next [-]

> There is nothing wrong with using this calling convention, except for those specific functions that need to have a variable number of arguments

I think it is a big pity that contemporary mainstream x86[-64] calling conventions (both Windows and the SysV ABI used by Linux-and almost everybody else) don’t pass the argument count in a register for varargs functions. This means there is no generic way for a varargs function to know how many arguments it was called with - some functions use a sentinel value (often NULL), for some one of the arguments contains an embedded DSL you need to parse (e.g. printf and friends). Using obtuse preprocessor magic you can make a macro with the same name as your function which automatically passes its argument count as a parameter-but that is rarely actually done.

OpenVMS calling convention-including the modified version of SysV ABI which the OpenVMS x86-64 port uses-passes the argument count of varargs function calls in a register (eax), which is then available using the va_count macro. I don’t know why Windows/Linux/etc didn’t copy this idea, I wish they had - but it is too late now.

mananaysiempre 3 days ago | parent | prev [-]

> There is nothing wrong with using this calling convention

Moreover, it can actually support tail calls between functions of arbitrary (non-vararg) signatures.

ataylor284_ 3 days ago | parent | prev [-]

Yup. If you call a function with the C calling convention with the incorrect number of parameters, your cleanup code still does the right thing. With the Pascal calling convention, your stack is corrupted.

rep_lodsb 3 days ago | parent [-]

Yeah, it's really irresponsible how Pascal sacrifices such safety features in the name of faster and more compact code... oh, wait, the compiler stops you from calling a function with incorrect parameters? Bah, quiche eaters!

JdeBP 3 days ago | parent [-]

I have not read someone talking of quiche eaters in some years. Thank you for keeping the joke alive. (-: