Re-entrancy

From Gnash Project Wiki

Jump to: navigation, search

The Gnash core libs are not re-entrant. This page addresses various design problems that are obstacles to a re-entrant VM and how to fix them.

Contents

Obstacles

The main obstacles are:

  1. The VM is a singleton and is accessed during parsing through the static VM::get() function
  2. The MediaHandler is a singleton.
  3. Many prototypes and constructor functions are static pointers.
  4. The garbage collector is a singleton and is accessed with GC::get() in the constructor of every GcResource and by movie_root for running the cycle

VM singleton

Some adjustments will be necessary during parsing, such as delaying use of the VM until execution starts. This usually means storing data in the parsed definitions and transferring it when the definition is instantiated or otherwise used. Object prototypes also rely on the VM singleton.

GC singleton

The GC should likely be a per-run object, which may be allocated by the VM or by the movie_root. Construction of GcResource instances (every as_object and subclasses) should happen trough a factory for this to be simple to maintain. Currently the Global object acts like a factory which would help, but there are still cases of direct allocation (using 'new') which should be dropped.

Media handler

It should be relatively easy to add this to RunResources and not use the singleton.

Prototypes and constructor functions

The code refactoring between 0.8.5 and 0.8.7 removed static pointers for all constructors and prototypes, so that they are no longer shared between (theoretical) VM instances or AVM1 and AVM2. The only exception is the Function type. This is not especially difficult, but needs careful testing and more extensive changes than most of the other types.

AS2 classes

There are two sorts of AS2 class: genuine classes - that is, abstract interfaces from which an object can be created - and simple objects with properties. Genuine classes are implemented in AS2 using a constructor function and a prototype. Simple objects cannot be instantiated. They are often singletons in ActionScript, and have certain properties or member functions that control a singleton object. Examples are Mouse and Stage.

The best approach is to redesign the AS2 implementation so that no static prototypes are used and delegating creation functions to AVM1Global as much as possible. Changing AS3 class, object, and prototype creation will then require far fewer changes.

Genuine classes

Most classes are created using a setup script that itself is written in ActionScript. An example of a genuine class is Color, which is created using ActionScript and looks like this:

 function Color (target) {
   this.target = target;
   ASSetPropFlags(this, null, 7);
 }
 var o = Color.prototype;
 ASSetNative(o, 700, "setRGB,setTransform,getRGB,getTransform");
 ASSetPropFlags(o, null, 7);

The code declares a function 'Color' that sets a property 'target' of the created object (the 'this' pointer). The function receives a 'prototype' property on creation. Properties are then attached to this prototype.

When the code:

 var c = new Color()

is called, the object c receives the property 'target' (undefined in this case). Its __proto__ property is set to point to Color.prototype, so it inherits the properties setRGB, setTransform, getRGB and getTransform.

It is important to note that the 'this' pointer already exists when the constructor function runs, and it already has a __proto__ property.

Inherited Color functions are created using ASnative functions. This is a table of functions native to the VM, that is, they are not implemented in ActionScript. Many objects have a mixture of native functions and AS-implemented functions.

Not all classes are created using ActionScript like the Color class. Particularly the basic classes String, Array, Number and Boolean use a mechanism similar to ASnative. The ASconstructor function returns a function like ASnative does, but additionally with a prototype. These prototypes are generally simple objects; the function appears to be a mere convenience to avoid assigning the prototype manually.

Implementation

Built-in classes

Currently, Gnash makes no distinction between native and built-in (ActionScript-declared) functions. Gnash does not construct the 'this' pointer using the prototype before running the constructor, which means that constructor functions do not know what their prototype is. This forces Gnash to use a static pointer for its prototype, to ensure that the prototype attached to the class (e.g. Color.prototype) and the prototype of constructed objects (c.__proto__) are the same. This can easily be avoided by constructing the object from its prototype before the constructor function is run. The constructor function should then simply change the 'this' pointer and need not return anything.

This would bring built-in classes into line with classes created in ActionScript. It clearly makes sense to do this, as they are in fact created in ActionScript.

Built-in genuine classes that are entirely implemented in ActionScript are:

  • MovieClip
  • LoadVars
  • XMLSocket
  • AsBroadcaster
  • Color
  • NetConnection
  • NetStream
  • Camera
  • Microphone
  • SharedObject
  • LocalConnection

Built-in objects entirely implemented in ActionScript are:

  • Mouse
  • Key
  • Stage
  • System
  • CustomActions

Note: though the classes themselves are implemented entirely in ActionScript - that is, their constructor function and prototype are created by the initialization script - their member functions often use ASnative functions. Note also that MovieClip, Microphone, and Camera are not created using their constructors, but rather functions such as createEmptyMovieClip(), Microphone.get() etc.

The implementation also depends on native classes.

Native classes

Gnash used to implement native classes as subclasses of as_object. This meant that the object to be constructed (e.g. String) must be a String_as in order for Gnash's implementation to work. Construction of a generic as_object before running the constructor, which works for built-in classes, cannot create a String_as when required. The requirement for these types is that they must be convertible to one another. For instance, a String object may be converted to a Boolean object. In doing so it will lose all all native String typing, but keep any other properties it may have (provided they are not overwritten in the Boolean constructor).

There is a temptation to implement native constructors differently, i.e. constructing an object from a stored prototype, much as Gnash did before, but without using a static pointer. This appears to be how swfdec implements the basic types (this statement may not be correct). However, the following test:

 String.prototype = 8;
 check_equals(typeof(String.prototype), "number");
 check_equals(String.prototype, 8);
 s = new String("hello");
 xcheck_equals(s, undefined);
 xcheck_equals(s.__proto__, 8);

shows that this is also not the case. That is, changing String.prototype does change the String class. So here, too, it is essential to make sure the String object ('this' pointer) exists before running the constructor function.

As of Gnash 0.8.7, these problems have been almost completely fixed. All native types are implemented using Relays with the exception of basic types that appear not to be convertible to each other. These non-convertible types include Function, and Super (as well as the AS3 Class type).

ASconstructor

The ASconstructor mechanism provides some hints on how to implement native classes. It shows various important characteristics:

  1. ASconstructor returns a constructor function with prototype.
  2. The prototype is always a new object: it is not the same object each time
  3. The prototype has no AS properties; these are attached afterwards.

This suggests that ASconstructor is used to return constructors with particular type-creating ability: that is, they create objects with native typing that normal, AS-created objects do not have. This includes, for instance, the ability to store a string, number, or boolean value. ASnative functions often work only with objects of the required type. Number.toString - ASnative(106, 1) - only works on objects constructed by ASconstructor(106, 2).

This is handled in swfdec by the concept of a 'relay'. Swfdec uses the glib type system to check types of objects.

Built-in SWF8 objects that use ASconstructor are:

  • Number
  • Boolean
  • String
  • Array
  • Date
  • Sound
  • XMLNode
  • XML
  • Button
  • TextField
  • TextFormat
  • Video

Additionally, Object and Function are both implemented in ASconstructor(101, 9), but this appears to be assigned before the initialization script runs.

State of Gnash's implementation

There are effectively three cases to implement:

  1. Simple objects (these include Mouse, Selection, and all AS3 constant enumeration objects
  2. Built-in classes
  3. Native classes.

Simple objects

Gnash implements these correctly. The function gnash::registerBuiltinObject() (see Global_as.h) should be used for all cases where the object has default PropFlags (which appears to be all cases). There may be some files where this has not yet been done.

Built-in classes

Gnash can implement these correctly, provided the Gnash implementation only uses plain as_objects and not a subclass. The object is constructed with the class's prototype before the constructor function is run and is available to the function as fn.this_ptr. These constructors should return as_value() (undefined). Examples where this is implemented include:

  • Error
  • AsBroadcaster
  • ContextMenuItem

Instances of classes that should be built-in, but are implemented in Gnash like a native class:

  • NetStream
  • NetConnection

The function gnash::registerBuiltinClass() (see Global_as.h) should be used for all cases.

Native classes

Gnash uses a 'Relay': a polymorphic object owned by an as_object. The name is borrowed from Swfdec, which implemented it long ago. The type of this object can be checked and made available for use through C++ runtime type information (generally dynamic_cast).

The Relay object is attached during native function constructors. So far, the following classes use a relay:

  • String
  • Date
  • Number
  • Boolean
  • LoadVars
  • XML
  • XMLNode
  • flash.display.BitmapData
  • all flash.filter classes (but they are unimplemented)
  • flash.geom.Transform
  • flash.geom.ColorTransform
  • TextFormat

Objects that need regular updates of their status, such as NetStream, XMLSocket, MovieClipLoader, are implemented using a subclass of Relay (ActiveRelay) that contains a virtual update() function. This replaced the advanceState() virtual function of as_object; movie_root contains a list of registered ActiveRelays (instead of as_objects as before).

Objects implemented using an ActiveRelay are:

  • Sound
  • NetConnection
  • NetStream
  • XMLSocket
  • LocalConnection

DisplayObjects

DisplayObject typing (those objects that can be rendered) is a separate type system. These types may also theoretically be convertible to one another, though there are few practical consequences of this. The separateness of these typing systems is demonstrable by running a native constructor such as Array or Boolean on a MovieClip or TextField. It can be shown that DisplayObjects take on native typing.

DisplayObjects are now a separate GC-managed type. A DisplayObject may have an associated as_object, in which case it is referenceable in ActionScript. Such objects include MovieClips, Buttons, and TextFields. An object must have an associated DisplayObject if it is to appear in a DisplayList and be rendered. Objects gain DisplayObject typing in the course of particular native functions, such as createEmptyMovieClip() and createTextField().

The example of createTextField demonstrates that a plain as_object can gain DisplayObject typing. The createTextField function calls the _global.TextField constructor. By overriding this constructor we can observe that a simple object created in this constructor only later becomes a genuine TextField. (Bwy 13:37, 31 January 2010 (MST))