Bootstrap revival – the basics

In the last time I was working on the system bootstrap again, trying to enhance the process, lower the imposed limitations… And I have a pretty new version. I’ll present here a summary of what I’ve learnt in the last time:

  • A review on the process steps, with exemplar code snippets
  • Some of the new key ideas and new infrastructure
  • And improvements over the last version :)

What are we going to bootstrap

For the sake of simplicity, we will not bootstrap a full Pharo (because that would include preparing for example Morphic to be bootstrapped, or to be loaded/unloaded). Instead, we will bootstrap an adapted version of MicroSqueak from John Maloney, which we re-baptized as Pharo Candle. Pharo Candle has only 50 classes, as you can see in the Github repo, and limited features. All classes from Pharo Candle has a PC prefix, which is not important for the bootstrap, since it will not let be name collisions.

To understand the rest of the post, the following is important: we will take a textual and static definition of the system (methods, classes…), and load it into our image. For that we will parse the files and include them as definition objects. These definition objects will ease the access to the data needed instead of accessing lots of lookup and symbol tables and stuff in a procedural way…

Some infrastructure basics

The last bootstrap implementation was very coupled to Pharo and its internal implementation. To create objects, to initialize classes, we relied on the existing system, on sending messages, on the VirtualMachine interpreter and infrastructure. So the first step for this new version was to decouple that. Decoupling the existing system from the new system that is about to be created. To do that we encapsulate the state of the new system inside an object, which we call an object space.

An object space reifies the new system. It is an object that understand messages such as “create an object”, “translate this string to a string of the new system”, “register this object as a class in yourself” or even “execute this piece of code”. This object space lets us structure the creation of the new system in a more comfortable way.

Our next concern, is not installing objects from the existing system into the new system. We want the new system to be transitively closed. So, the best is not to have direct references from the existing system to the new one. The best is to control those references carefully. Then, every time an object space gives you a reference to one of his objects, he really gives you a proxy. That proxy allows you to manipulate, at some level, the object inside the object space. A proxy can give and revoke permissions on the inner object, and make transformations or validations when necessary to keep the model consistent. Since these proxys may perform meta-operations on the new system objects, we end up calling them mirrors at some point.

Finally there are VirtualMachine limitations when executing code on the new space. We introduced a new piece in the puzzle to overcome them: our own language-level interpreter. In this particular case, we are using an AST interpreter, but we could, if available, use any other kind of interpreter. The important thing about using our own interpreter is controlling the semantics of the new system, and leverage the VM’s limitations.

Where do we start?

When the bootstrap starts, there is nothing. There are no objects, no structures, nothing. So we have to build everything from the start. And the question is… where do we start?

Our system is composed by objects, and thus, we need to create objects. And every new object may have pointers, which are initially pointing to nil. But what if there is no nil in our system? So, let’s build a first nil, so all objects created later can point to this nil object.

Creating the first object

As we decided before, we create our first nil object. However, there is a question that arises when we try to create nil. How can we create an object without a class? The answer is, so far, that until we have classes we will create objects without classes. We will not care about their classes and we will solve that later, once classes are created. Fortunately, this problem is present with very few objects.

theNil := objectSpace 
    createObjectWithFormat: undefinedObjectDefinition nilFormat.

Since we have no class to create nil, we have to specify the format of this object. That is, the amount of memory to be allocated for it, the amount of slots, and if they are pointers or bytes or what. The format is known by the definition of undefined object.

Fortunately, nil has no pointers to other objects, except for his class, simplifying the process. We will set nil’s class once we create it later.

We create the classes

Creating a class is a complex operation. A class has a metaclass. And it has a name which is a symbol (unique in the system). And it has a superclass, which may have not been created yet. And it has a dictionary of class variables. And… a lot of stuff.

Our objective is to keep this bootstrap the simplest. And for that, we will delay all the complex operations to the moment when they are not so complex. In this step we create empty classes. We only initialize their format with a SmallInteger, and we let the rest of their pointers pointing to nil.

The first step for creating a class, is to create its metaclass. And to create a metaclass, we need the class Metaclass. This Metaclass, in a ST-80 like model, follows the Metaclass<->Metaclass class loop. That is, Metaclass is an instance of Metaclass class, and Metaclass class is an instance of Metaclass, as shown in blue in the following figure.

SmalltalkMetaclassesWe create the first Metaclass and Metaclass class as objects without class, and then we make each one an instance of the other.

metaclassMirror := objectSpace
    createClassWithFormat: classFormat
    forInstancesOfFormat: metaclassFormat.
metaclassClassMirror := objectSpace
    createClassWithFormat: metaclassFormat
    forInstancesOfFormat: classFormat.

metaclassMirror 		setClass: metaclassClassMirror.
metaclassClassMirror 	setClass: metaclassMirror.

Once we have the first metaclass, we can create all the classes.

self behaviorDefinitions do: [ :aClassDefinition |
	| newClass newClassMetaclassMirror theMetaclassMirror |
	theMetaclassMirror := objectSpace classNamed: #PCMetaclass.
	newMetaclassMirror := theMetaclassMirror basicNew asClassMirror.
	newMetaclassMirror format: aClassDefinition classSide format.

	newClass := newClassMetaclassMirror basicNew asClassMirror.
	newClass format: aClassDefinition format.
	]
]

At this point, the classes only have set their format, and their class. All other slots have pointers to the nil object we created at the beginning.

Fix nil, create true and false!

Now we created all the classes, even if they are empty, we can fix the “classless” nil and create our true and false objects.

theNil setClass: (objectSpace classNamed: #PCUndefinedObject).
theTrue := (objectSpace classNamed: #PCTrue) basicNew.
theFalse := (objectSpace classNamed: #PCFalse) basicNew.

Initialize the classes state

Now we have all classes created, and the three basic objects we need (nil, true and false). So now we can start initializing all our classes. This initialization consists for each class in:

  • Set the superclass of the class. The root of the hierarchy should be nil.
  • classDefinition superclass isEndOfHierarchy ifFalse: [
        superclassMirror := objectSpace
            classNamed: classDefinition superclass name.
    ] ifTrue: [
        superclassMirror := objectSpace nilObject.
    ].
    classMirror superclass:superclassMirror.
    
  • Set the class name
  • classMirror className: classDefinition name.
    
  • Set the collection of instance variables of the class
  • classMirror instanceVariables: classDefinition instanceVariables.
    
  • Set the superclass of the metaclass. The root of the hierarchy should be PCClass.
  • metaclassMirror := classMirror classSide.
    classDefinition superclass isEndOfHierarchy ifFalse: [
        metaclassMirror superclass: superclassMirror classSide.
    ] ifTrue: [
        metaclassMirror superclass: (objectSpace classNamed: #PCClass).
    ].
    
  • Set the instance side relationship of the metaclass
  • metaclassMirror := classMirror classSide.
    metaclassMirror
        instanceSideClass: classMirror.
    
  • Set the collection of instance variables of the metaclass
  • metaclassMirror := classMirror classSide.
    metaclassMirror
        instanceVariables: classDefinition classSide instanceVariables.
    

After this initial initialization is performed for every class, we finish by initializing the class variables. Class variables are represented by a Dictionary object. A dictionary object is an object with a complex structure, and the way to manipulate it depends on the nature of the system we are bootstrapping. The solution, so far, is to delegate the initialization of the dictionary to the dictionary itself.

For that we use a combination of the code of the dictionary and an AST interpreter. An AST interpreter needs to be initialized before its usage so later, all class variables can be initialized. As you can see in the code below, the AST interpreter usage is hidden inside the mirror implementation :).

objectSpace initializeInterpreterForCodeProvider: self kernelSpec.
self behaviorDefinitions do: [ :classDefinition |
    | classMirror |
    classMirror := objectSpace
        classNamed: classDefinition name.
    classMirror
        classVariables: classDefinition classVariables.
].

Install methods

Now we have all classes of the new system created and initialized. We can start installing all their methods. Before, we should declare all global variables of the system, so the compiler knows how to bind them correctly. After that, we take the source code of all methods from the system description and compile them. The compilation gives us as result the bytecode of the method + the literals. This method is then translated to a method in the new world, and installed into the new system.

The globals initialization looks like:

objectSpace environment
            addGlobal: #Processor
            pointingTo: objectSpace nilObject.

Then, for each class, we have the following code to create and install the methods:

"build the methods as instances of this system"
newMethods := aMethodBuilder
    methodsForBehavior: mirror
    fromDefinition: aBehaviorDefinition.

"create a method dictionary of the new system"
newMethodDict := objectSpace createMethodDictionary: newMethods size.
newMethods do: [ :m |
    "install a method from this system to the other"
    "the translation to a method to the other side is made inside"
    newMethodDict installMethod: m
].
"we set the method dictionary to our class"
mirror methodDictionary: newMethodDict.

Initialize the system state

Finally, we initialize the system state, with the aid of the AST interpreter. This last step consists mainly in:

  • execute the initialize class side methods
  • set up the process scheduler of the system and install its processes
objectSpace interpreter evaluateCode: 'PCCharacter initialize'.
objectSpace interpreter evaluateCode: 'PCString initialize'.
objectSpace interpreter evaluateCode: 'PCFloat initialize'.

objectSpace interpreter evaluateCode: '
    Processor := PCProcessorScheduler basicNew.
    Processor initProcessLists.'.

process := objectSpace
        createProcessWithPriority: 3
        doing: 'PCSystem start'.
objectSpace installAsActiveProcess: process.

Conclusion

Bootstrap: achieved.

With our new pieces into the game, we were able to overcome the virtual machine limitations, and have a in-image full bootstrap. The next steps go in the way to:

  • serialize this object graph into an image file, so it becomes autonomous
  • test the bootstrapped system while still living along with the original system, without serializing it. I mean, run it into the same VM without AST interpreter.

I hope I explained myself well.
Arrivederci!!
Guille

When optimal matters

This last week I’ve been involved somehow in several discussions which, although not explicitly, talked about optimizations. In particular, premature optimizations. Of course, we all know they are evil. Do we? I’ll not discuss today on optimization techniques, but on what should an IT professional think about when thinking about optimizations in his programs.

The main arguments I heard this week of people supporting premature organizations were:

  • A guy using some technology X, should know the underlying details of X, or he will fail. Let’s say, if you are a Java programmer, you must not only know there is a GC, but also how it works.
  • A tech guy should be always conscious of the resources used. i.e., not to store a lot of objects in caches because memory is a finite resource.
  • Assumptions on what should be faster. Using or not a macro in C for example.

And these arguments are not even totally wrong. But they are not so totally true as they were stated.

Premature optimization is the root of all evil

You know Donald Knuth? This phrase (attributed to C.A.R. Hoare, btw) became famous because of this paper he authored. The interesting thing is that this phrase, when used, is taken out from context. The original phrase is:

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

Being optimal is not all that matters

When writing software, running optimal is not the only variable to analyze. We want programs to be debuggeable, understandable, extendable, maintainable. And sometimes, optimal code can be ugly, and can be hard to change, or to fix bugs on it.

So, let’s look at this piece of code written for Pharo. This piece of code takes a string, splits the substrings taking the space character as separator, excludes the substrings that do not exist as symbols in the system, and then it converts them to symbols.

aString := 'aaaaa bbb Class cccc ddd'.
((aString splitOn: Character space)
    reject: [ :each| Symbol lookup: each  ])
    collect: #asSymbol

Kind of understandable piece of code. But what goes on behind scenes?

  • splitOn: creates a new (and temporary) collection. It also creates strings for each substrings and copy all the contents into those strings;
  • reject: iterates over the result of #splitOn:, creates a new (temporary again) collection;
  • collect: iterates over the result of #reject:, creates a new collection to put the results of #asSymbol

Finally, there are two intermediate collections that are discarded, some substrings are created by copying all the contents and finally discarded (cause we only care about the symbols). Yes, that is inefficient: lots of temporary allocations that could launch the GC, several iterations over collections… we could do better. Let’s see an alternative version Camillo Bruni (Rmod, Inria) suggested to improve in terms of speed and memory usage:

Array streamContents: [ :s|
    aString
        splitOn: Character space
        indicesDo: [ :start :end|
            aString asSymbolFrom: start to: end ifPresent: [ :symbol|
            s nextPut: symbol]]

This new version, which btw ends up with the same result, is pretty much more efficient:

  • Streaming on the result causes only one collection allocation without temporal ones;
  • Some special methods introduced into String to avoid extra collection allocation, and substring copies;
  • One collection means only one iteration :)

But wow, the code became much more complicated (given the simplicity of the example), and less object oriented. We do not manipule so easily the substrings by sending messages to them, we have instead the indices into the source string. Our code is much more aware of the problems we stated before, and recurring to lower level APIs to avoid them.

Now, extend these ideas to a whole large application. Hundreds or thousands of classes written this way. We write methods of tens (or hundreds, why not?) of lines of code to avoid message sends (and therefore method lookups), we avoid at the maximum object allocation and go for an if based solution… and soon we will have lots of duplicated code, stringy code everywhere… And yet I can tell you (just guessing :^) your program will not be tons more optimal. What? Now my code is so hard to maintain and not very much faster? Not cool…

Being optimal when optimal matters

So let’s say we have this function that takes 100.000 database rows, makes some calculations, and show a simple result to a user. It takes 1 second, which is a lot for a nowadays machine. But this function is used once per hour…

Now take the code that evaluates the bytecode that access an object’s field. It gets executed maybe some lots of thousands of times per second? So, if this operation starts to take 1 second… :)

Or take this application that stores data on background, but when restoring wants to be as fast as possible to give a really good user experience. Will you care how much it takes the storing operation?

Do we really have to spend a lot of time optimizing code that is almost not used? Or code that does not need to run that fast? Wait! My application runs ok, do I really have to optimize something?

As Knuth says, 97% of the code is not critical. Only 3% deserves to be optimized.

Understand when and where optimal matters

So now you know the key point (optimizing when it matters), and you understood it mattered in your case. Time to find that 3%. And it may be not so obvious…

Thanks engineers invented profiling!  Just look a bit around, there are tons of tools to help you understand what you’re doing wrong: where is memory allocated?, and of which type?, is the GC launched so often? is a time consuming function executed too many times? Profiling is a technique that should be on every software engineer tool-case.

The rules of optimization

As a conclusion, today I found this link I want to share about the rules of optimization. And I think they are a pretty good guideline. When you are thinking on making an optimization:

  • First time: Don’t do it!
  • Second time: Don’t do it yet!
  • Third time: Ok, but you first profile and measure, and then optimize

There is much to lose when only thinking on the optimal solution to a problem in terms of machine resources. Remember people’s time to understand the written code, to adapt it to new situations and to fix bugs on it is also a valuable resource.

Guille

Keymappings 101 – for Pharo 2.0

Pharo

Pharo 2.0 release includes the Keymappings library. Keymappings is a library for configuring shortcuts for the current UI library (Morphic). It models concepts like: shortcuts, key combinations, event bubbling. It is a very simple library which I’ll introduce gradually in this post.

Key combinations

Keymappings main task is it’s ability to associate a key combination to an action. So we have to build up those key combinations. The simplest key combination is the one that gets activated when a single key is pressed. We call these combinations single key combinations:

$a asKeyCombination. -> "single key combination for A key."
Character cr asKeyCombination. -> "single key combination for  key."

Although, usually key combinations get a bit more complex. It is very common to combine single keys with meta keys or modifiers. These meta keys or modifiers are the well known ctrl, shift, alt and command keys. To build a modified key combination we can do as follows:

$a ctrl. -> "a modified key combination for Ctrl+A"
$a ctrl shift. -> "a modified key combination for Ctrl+Shift+A"
It is important to notice that all key combinations are not case sensitive. It takes a and A characters as the same, since they are the same key.

Have you ever used emacs, Eclipse or Visual Studio? Then you probably know sequences of key combinations that launch one only action. Like Alt+Shift+X, T (to run JUnit tests in eclipse)? So keymappings can do that too:

$a command shift, $b shift. -> "key sequence (Cmd+Shift+A, Shift+B)"

Sometimes, you want to configure an action to be activated in two different cases. Those are Keymapping options, and get activated when one of the options gets activated:

$a command | $b command. -> "key combination (Cmd+A or Cmd+B)"

Finally, since Pharo is a cross platform system and it is important to provide a good user experience by with the most suitable shortcut layout, keymapping implements platform specific shortcuts, which get activated only when running in the specific platform:

$a command win | $b command unix. -> "Cmd+A on windows, but Cmd+B on unix"

Shortcut configurations

Now you know how to build key combinations for your purposes, you probably want to go to the action. Map those combinations to actions and make them work!

Single shortcut configuration

The simplest way to attach a shortcut to a morph is by sending him the #on:do: message. The first argument expected is a key combination and the second one is an action. In the example below, a workspace is created with two shortcuts:

  • when Cmd+Shift+A is pressed, the workspace is deleted
  • when Cmd+Shift+D is pressed, an information growl should appear yelling ‘this shortcut works!’
w:= Workspace new.
morph := w openLabel: 'keymapping test'.
morph on: $a shift command do: [ morph delete ].
morph on: $d shift command do: [ UIManager default inform: 'this shortcut works!' ].

Easy, huh? So let’s move on…

Shortcut categories

Sometimes you want to group and organize shortcuts in a meaningful way and apply them all together on a morph. Sometimes you want some morphs from different hierarchies to share the same group of shortcuts easily. Those groups of shortcuts are what keymapping calls Categories. A category is a group of shortcuts, so far (will change in the future) defined statically by using a keymap pragma on class side:

"defining a category"
SystemWindow class>>buildShortcutsOn: aBuilder
    <keymap>

A class side method marked as <keymap> will be called with a builder object, which can be used to define a named set of shortcuts:

SystemWindow class>>buildShortcutsOn: aBuilder
    <keymap>
    (aBuilder shortcut: #close)
        category: #WindowShortcuts
        default: $w ctrl | $w command mac
        do: [ :target | target delete ]
        description: 'Close this window'.

Shortcuts defined through the builder specify the name of the category they belong to, a default key combination, an action, and a description. All this metadata is there to be used as settings in the future.

Finally in order to get your morph handle those shortcuts you can use the #attachKeymapCategory: message as in:

w:= Workspace new.
morph := w openLabel: 'keymapping test'.
morph attachKeymapCategory: #Growling.

Bubbling

Keymappings’ shortcuts bubble to their parent if not handled, up until the main world morph. That has two main consequences:

  • Shortcuts for your application can be designed in a hierarchical way and;
  • Every time a shortcut does not work for you, it means that a morph below you has handled it ;) (be careful with text editors that handle loooots of key combinations)

Future work

So far, so good, but there is some plan on Keymappings for Pharo 3.0 development, which I can anticipate:

  • Some API changes: #on:do: can be confused with exception or announcement handling. #asShortcut will probably be properly renamed as #asKeyCombination. There is an inconsistency between the #command and #ctrl messages…
  • A lot of renames and new comments :)
  • Spread it all over the system
  • Make keymap categories first class objects, not any more a symbol ;)

à la prochain!
Guille

Pharo 2.0 Released

Pharo Project

Aaaand, a new version of the Pharo project cames out. It ressembles the version 2.0 of this dynamically typed object-oriented programming language and environment. This release includes many cool stuff improving a lot the infrastructure of the system, adding new core libraries and lots of cleansing and improvements. Let’s make some remarks on this release.

Cool Development Tools, all by default

Pharo’s default browser is now Nautilus by Benjamin Van Ryseghem. Nautilus has lots of cool features, like an alternative Group view, a plugin architecture, and integration with Monticello, refactorings and the Critics browser. Yes! Now by default Pharo includes refactorings, since they are one of the cornerstones of the development activities. Critics browser is also included, so the code quality can only improve :).

Auto completion has also seen lots of changes: default completion is <enter>, press <tab> to complete word per word á la command line, and it has also been revisited to provide better and more meaningful results.

Finally, if you press <shift+enter> you’ll see on the right upper corner the Spotlight by Esteban Lorenzano. A simple but powerful way tool to quickly browse a class or method.

Boosted by NativeBoost and Fuel

Pharo wants to be fast. And that’s something NativeBoost and Fuel achieve. That’s why you can find them included by default in the system. NativeBoost (by Igor Stasenko) gives us the ability to execute machine code from the language side, and a new generation FFI with callbacks. Use it with caution :). Fuel, written by Mariano Martinez Peck and Martin Dias, is a cool object serializer focusing on fast deserialization (materialization), and the ability to serialize any kind of objects: Block closures? yes. Contexts? yes. Complete debuggers so we can restore them and debug failures in other environments? YES.

UI Front – Spec and Keymappings

Pharo 2.0 includes two new cool libraries on the UI front: Spec and Keymappings.

Spec is a framework, mainly work of Benjamin Van Ryseghem under the tutelage of Stéphane Ducasse, to build UI components declaratively. It puts its main focus on component reuse and ability to be composed. Spec was included into Pharo 2.0 and some tools were reimplemented to use it. How do you give it a try?

On the other side, Keymappings is a shortcut library mostly re-written by me (Guille Polito) to adapt it to Pharo. It’s main objective is to provide common shortcut semantics for desktop UIs, and remove hardcoded semantics spread all over the system. Pharo 2.0 includes Keymappings and has already replaced some users of the old-fashioned(harwiredandmessy cof cof) shortcut declaration by nice keymapping ones. On the documentation side, I owe it to you :). I promise to a nice tutorial-post this week!

System changes – System Announcements, RPackage, FileSystem, branded VM

On the internals of the system, the notification of system events was replaced by System Announcements, RPackage was introduced so the old and ugly packaging system can be slowly migrated, and the old FileDirectory was tackled down and all its usages were replaced by the new cool FileSystem library (already there in 1.4).

Also, the Pharo VM is now branded, and includes many fixes and bundled libraries (nativeBoost and SSL plugins, cairo, freetype). You should run your Pharo images on a Pharo VM, which you will identify by a nice Pharo icon ;).

And of course there are lots of other clean and cool new stuff to see like SSL, command line tools, non UI blocking notifications… A more detailed list is here. So take Pharo, have a look, enjoy, and give feedback. Remember that any contribution is valuable, as small as it looks.

Download Pharo
Pharo website
Joining and helping
Pharo By Example book (available as a free PDF)
Screencasts!
Reporting problems
Pharo vision document

Chaus, Guille

The Bootstrap Chronicles – The end of a phase

As some of you already know, the GSOC project edition 2012 is coming to an end.  And along with it, the bootstrap project reaches a checkpoint. This post covers is the news since the last chapter, and discusses about the future steps. In a next post I’ll document  the details of the project deliverables.

Where we where?

The first product of this project was a renewal of an image serializer: the SystemTracer. It takes an object graph and serializes it into the image format a vm works with. The System tracer was refactored reifying the memory object formats and updated to write Cog images.

The second step was to work on bootstrapping. And it was successful.  Hazelnut, the bootstrap process tool, is able to build a smalltalk image from a description. To ensure the quality and health of these newly created images I set up jenkins jobs loading different packages on top of them, and running tests over them.

What happened since last time: Declarative kernel descriptions

A kernel definition has two main parts:

  • the code and definitions of the entities of that kernel
  • a definition on how to build the basic model of that kernel and how to initialize it finally

The first one can take the form of source files like in https://github.com/guillep/PharoKernel, which is the source code Hazelnut actually uses to bootstrap Pharo images. The second part of the kernel definition contains some imperative parts, and by now they are declared in simple pharo classes you download from Monticello.

So now, the bootstrap loads the kernel definition from those source files, generates the bootstrapped environment, and serializes it into a new image file.

The future of bootstrapping pharo

Since our goal is to bootstrap pharo to support it’s modularity and  evolution, there are some keypoints to attack in the near future:

  • getting the pharo sourcecode in sync with this bootstrap representation
  • choosing the really important parts for a kernel. What should be and what should not in those source files? Where do we package what’s not going kernel?
  • building pharo from bootstrapped images.

Even, when looking at the upcoming pharo changes like first class slots and class layouts, or the new Tanker package manager, the bootstrap will need for sure some updates.

Conclusions

I hope this project makes pharo grow and get better! We can now generate images with the source code defined statically in source files, so for the GSOC program the scope has been fulfilled.

See you in a next post documenting the project!

Salut!

The Bootstrap Chronicles Chapter 4 – Pump up with Fuel

Last time we were generating a new image with useful information.  This post tells the story after that first bootstrap: How we ensure that little monster is healthy, and how we ensure that our process is flexible and robust enough, and how does it help pharo in the modularization cruzade.

Fuel – The modularization sword

Probably some of you already know about Fuel: a fast object serializer written by Mariano Peck and Martín Dias. And maybe you are also aware of another Google Summer of Code Martín is working on: a Binary Package manager on top of fuel. Mariano showed us already a first proof of concept of that idea in this post: http://marianopeck.wordpress.com/2011/09/24/importing-and-exporting-packages-with-fuel/.

So, I want to work on building stuff on top of my little bootstrapped image.  And I thought Fuel was a nice gun to attack that problem.

Detecting illnesses

In my last post about the bootstrap I’ve already shown you how to get a list of broken/uninitialized stuff. That gives us an idea of how healthy our image is.  But there are other tools that we already use for that: tests.

So, Can we run SUnit on the bootstrap? Yes.

When I thought about Fuel and the bootstrap, I thought Fuel should be included by default, otherwise it should be difficult make it grow.  Of course I could’ve chosen the compiler for the same purpose.  But I’d like to enable modularization with binary packages.

So, what about exporting sunit as a binary package, and import it in the bootstrap? And what if we also export the tests over sunit, and include them also? Then we should be able to run the tests of sunit. Nice. Then this same idea can be applied to tests the kernel, or the compiler…

And I did it :)

Completeness (or “does it have all its essential parts?”)

Another thing we can think on is testing the completeness.  When should you consider that the bootstrap is complete? A fun definition could be “when it is able to define itself”. Ok, let’s export the bootstrapping code with fuel, and import it in the bootstrapped image, and let’s try to bootstrap from the bootstrap. And do it again, just for fun.

Once the bootstrap bootstrap was working, I put the tests to work on the last generation of bootstraps.

The results

I’ve created some Jobs to test all this stuff in the Ecole de Mines’ Jenkins.

The test results are exported in JUnit format, so we can tell what’s broken and look at the stack trace.  All this jobs are working on the bleeding edge of the project using latest Pharo image and latest CogVM.

Have Fun!

Guille

Smalltalk behind the scenes: the meta model

Have you ever evaluated this pieces of code in Pharo?

 ProtoObject superclass.
 ProtoObject class superclass.
 Metaclass class class = Metaclass.

Wait, WTF? How is that ProtoObject superclass is nil? Wait again, and the one of it’s superclass is Class? Metaclass class is an instance of Metaclass? Hey, that’s kind of the chiken and the egg problem, which one was first?

You know that when you create a class, you specify a superclass for it.  This superclass will specify some other properties and the VM will use it to perform the method lookup.

Also, probably you already know that when a class is created in Smalltalk, a metaclass is created for it implicitly. That metaclass describes the class side behavior: class side methods, class instance variables…

Funny thing about this implicit metamodel, is that a second class hierarchy is built in parallel to the original class hierarchy.

Now, if you think about this, you can understand why the method lookup works also in the class side methods, and they are not static like in Java or C# :).

You can have a look at the following invariants:

aClass superclass class = aClass class superclass.
aClass class class = Metaclass.

Which of course have it’s exceptions. The method lookup ends when it reaches a class whose superclass is nil.  And the class side objects also behave like a Class, because they finally inherit from Class. HA! But then the metaclass hierarchy re-enters the non-metaclass hierarchy. Thinking of this in an operational way is kind of meta confusing, isn’t it?

But this is not the motivation of this post. The motivation is this: Are we really coupled to that meta model?  How can I create my own?

If you remember from my post on vm limitations I learned during the bootstap, the vm only expects 3 things from a class:

  • that it’s first instance variable is it’s superclass.
  • that it’s second instance variable is it’s method dictionary.
  • that it’s third instance variable is it’s format.

Any object respecting that contract can be treated like a class by the VM.  Then you can think on creating your own metaclass loop, kind of independent from the original one…

classFormat := ...
metaclassFormat := ...

"This metaclass defines how our metaclass instances will be.  It is only here to define the first metaclass format, and it will be discarded"
metaclassClass := Metaclass new.
metaclassClass
superclass: Class
methodDictionary: (MethodDictionary new)
         format: classFormat.

metaclass := metaclassClass basicNew.
metaclass instVarAt: 1 put: Metaclass.
metaclass instVarAt: 2 put: MethodDictionary new.
metaclass instVarAt: 3 put: metaclassFormat.

metaclassClass := metaclass basicNew.
metaclassClass instVarAt: 1 put: Metaclass class.
metaclassClass instVarAt: 2 put: MethodDictionary new.
metaclassClass instVarAt: 3 put: classFormat.

metaclassClass adoptInstance: metaclass.

Once you have a metaclass, instantiate it to create your class, and instanciate it to create your little object! That’s crafting Smalltalk using Smalltalk. Well, that is bootstrapping the meta model :).
The only ugly thing is that in order to create a new meta model with different instance variables, you have to create a transient class in the middle, because the VM does not like to have objects with a format X, whose class defines a format Y… So the hack just solves the format problem :).

Now you can think about simpler stuff like a class instance of itself, subclass of nil. Or more complex one :).

You can change it, I told you. Now it’s up to you how to use it…