[squeak-dev] Re: [Vm-dev] Better VM <-> plugin API

Andreas Raab andreas.raab at gmx.de
Sat Nov 22 09:34:26 UTC 2008


Igor Stasenko wrote:
> By more security, i meant a little feature, that since getting atom
> value is inexpensive operation, you can
> load value identified by atom and check for non-null value each time
> before calling function.

Oh, I think you mean it can be safer to use (with which I would agree) 
not necessarily more secure.

> Old scheme, needs to check for non-null as well, but in addition you
> need to be careful to clean out this pointer when you get notification
> that module, from where you taken this pointer is unloaded.

Yes. Although, you won't get around doing something about unloading 
either - we spent today learning about the intricacies of unloading 
OpenAL32.dll and it turned out to be absolutely crucial to be able to 
unload our own plugins. A flat shared namespace might have caused some 
serious issues here.

> I'm not sure what you mean by per-plugin namespace.
> And how much difference in namespaces between this:
>  bitblt = ioLoadFunctionFrom("ioBitBlt", "BitBltPlugin")
> and this:
>    atom = makeAtom("BitBltPlugin.ioBitBlt");
>    bitBlt = getAtomValue(atom);
> 
> The difference that first binds symbol at compile/link time, while
> second - at run time.

It would be quite possible to bind this at runtime, too. But what I mean 
by per-plugin namespace is that the *export* isn't done into a flat 
namespace but rather into a structured one.

And yes, one could conceivably use a naming scheme that is equivalent in 
practice but unless that's automated (the current scheme is fully 
automated) it seems error-prone and one of these things were people are 
simply too lazy in practice to utilize it correctly (if that were 
different I would expect people to use class and selector prefixes 
consistently which they don't).

> As for modularity, let me illustrate what i meant.
> There are many primitives in VM, which look like:
> 
> primitiveFoo: x with: y
> 
> ^ self cCode: 'ioFoo(x,y)'.
> 
> Obviously, when you generate this code using VMMaker, it wont compile
> unless you having ioFoo() function implemented somewhere.
> And you have a little choice where to put this implementation - in one
> of internal plugins or in platform-specific part of VM.
> 
> Now, lets consider, if i refactor this code to use atoms (i skipping
> details like declarations etc) :
> 
> primitiveFoo: x with: y
> 
> ioFoo := getAtomValue: AtomIoFoo.
> ioFoo notNil ifTrue: [ ^ self cCode: 'ioFoo(x,y)' ].
> ^ self primitiveFail.
> 
> 1. the code will compile regardless i declared a function or not.
> 2. i'm free in choice where to put the implementation of this
> function, or even build VM initially w/o this function.
> 3. across different platforms, one can use same VMMaker to generate
> sources , and it will always compile & link.
> 
> doesn't it look like more modular?

No. It looks utterly pointless to me. You introduce a plugin that does 
nothing but looking up and call an atom; what good is that plugin? If 
you generalize that just a little you have the FFI where you might 
declare ioFoo() directly and call it. Which of course could be done via 
atom table too, but I still fail to see how that would be more modular.

> Now, take a look at a sqWin32Stubs.c - how it would look if we would
> use shared namespace? It would look as zero length file.
> Because all we have to do in platform code is just initialize pointers:
> 
> pointers = {
>   "ioFoo" , ioFoo
> #ifdef NO_SOME_STUFF
>   "ioBar" , ioBar
> #endif
>  ...
> };

And if you stick this in sqWin32Stubs.c (or its equivalent) you end up 
with a non-empty stubs file. In other words, you are replacing one set 
of stubs with another one. Not much of an improvement.

> I'm currently trying to make a HostWindowsPlugin and want to put all
> windowing and i/o stuff in it from VM platform sources.

Why do another one? Is there something that the current host window 
plugin doesn't address?

> It would be much a cut'n'paste experience to me, if all callouts in VM
> would use atoms - because then i don't need to touch headers & sources
> and many different places to make compiler content. Because there
> would be no need in exporting function in C-like manner.

Ah. But that's a fallacy. That you don't need to "touch" these places 
doesn't mean you don't need to know about them. In fact, having to touch 
them, having the compiler complain about them is a great way to learn 
and know and understand all the areas you need to touch - if the VM 
would randomly crash on you because you have replaced one but not 
another function you'd be in for a hellish experience. Yes, the C 
compiler can be notorious but it can also be a pretty good teacher.

> And lastly, remember InterpreterProxy thing? Each time you want to
> introduce new functionality , you have to extend the structure and
> increase version number. But what is it? Its just a list of pointers!

No, it's not. It's an interface (a contract) between the VM and the plugin.

> Isn't it would be simpler for plugin to just lookup a function by its
> name - and use it if such function exists. Its pretty much same as
> dynamic linking, just s/dlsym/getAtomValue/ and don't let your code be
> constrained by compiler/linker anymore :)

I *very much* doubt that it would be simpler for each plugin to look up 
the function every time they use it and test for its existence every 
time it is used. Consider this primitive:

primitiveStringClone
   interpreterProxy methodArgument = 1
     ifFalse:[^interpreterProxy primitiveFail].
   arg := interpreterProxy stackValue: 0.
   (interpreterProxy isBytes: arg)
     ifFalse:[^interpreterProxy primitiveFail].
   clone := interpreterProxy clone: arg.
   interpreterProxy pop: 2.
   interpreterProxy push: clone.

Now let's rewrite this in pseudo-code to see what it would look like 
without an interface:

primitiveStringClone
   ((interpreterProxy has: #methodArgumentCount)
     and:[interpreterProxy methodArgument = 1])
       ifFalse:[^interpreterProxy primitiveFail].
   (interpreterProxy has: #stackValue)
       ifFalse:[^interpreterProxy primitiveFail].
   arg := interpreterProxy stackValue: 0.
   (interpreterProxy has: #isBytes)
       ifFalse:[^interpreterProxy primitiveFail].
   (interpreterProxy isBytes: arg)
       ifFalse:[^interpreterProxy primitiveFail].
   (interpreterProxy has: #clone)
       ifFalse:[^interpreterProxy primitiveFail].
   clone := interpreterProxy clone: arg.
   (interpreterProxy has: #pop)
       ifFalse:[^interpreterProxy primitiveFail].
   interpreterProxy pop: 2.
   (interpreterProxy has: #push)
       ifFalse:[^interpreterProxy primitiveFail].
   interpreterProxy push: clone.


Simpler? You've got to be kidding me ;-)

Cheers,
   - Andreas



More information about the Squeak-dev mailing list