Bluish Coder

Programming Languages, Martials Arts and Computers. The Weblog of Chris Double.


2008-11-25

Dynamic Compilation and Loading of Modules in Haskell

The Haskell system GHC has libraries that provide the ability to compile Haskell code and dynamically load it into a running Haskell program. A library that provides this functionality is hs-plugins. Unfortunately hs-plugins doesn't work with the latest GHC release, 6.10.1.

There is an API to the internals of GHC that allows implementing runtime compilation and loading. The api is documented but there aren't very many usage examples that work with the latest GHC release. I stumbled across a blog post which no longer exists describing an older version of the API, but managed to retrieve it from archive.org which provided a lot of help since the API's are similar.

As a means to learn how to use the API I decided on a very simple use case of compiling and loading a very simple Haskell module containing a single function 'print' that outputs a string. The module looks like this:

module Test (Test.print) where

print :: String -> IO ()
print x = 
  putStrLn x

To compile and load this code dynamically we need a compilation target. This is created using the function guessTarget. It's passed a string that refers to the name of a module, or the name of a source file. By using a source file we can tell the Haskell system to compile and load it. The phase argument I set to Nothing as I have no idea what that is.

Once we have the target it needs to be added to the current compilation session. This is done via the addTarget function. When all the targets are added, a call to load will do the equivalent of a ghc --make to build the module if needed. So all the relevant target code looks like:

do
  target <- guessTarget "Test.hs" Nothing
  addTarget target
  load LoadAllTargets

Notice this is run within the 'do' syntax. This is because all these compiler functions must be called with a special Monad, called GhcMonad. The way this works is described later but just imagine that the GhcMonad is implicitly passed to all the Ghc functions we are calling automatically.

'load' returns a success flag that can be pattern matched on to determine if it succeeds or failed. If it succeeds we can start using the new module. To be able to access exported functions from the module we need to get a reference to the module using findModule and use setContext on it. 'setContext' takes two arrays containing modules. All modules in the first array will have their top level scope available. All modules listed in the second array will have only their exports available. In this example we want to access the 'print' function of 'Test', which is exported.

The function compileExpr is used to compile a string containing a Haskell expression. The result of this is an HValue which we can do things with. The expression that is compiled can access any of the bindings available in the context we set up via 'setContext'. So passing "Test.print" to 'compileExpr' will return the function 'print' from the 'Test' module that we dynamically built and loaded. Code similar to this:

do
  r <- load LoadAllTargets
  case r of
    Failed -> error "Compilation Failed"
    Succeeded -> do
      m <- findModule (mkModuleName "Test") Nothing
      setContext [] [m]
      value <- compileExpr "Test.print"

Unfortunately the result of 'compileExpr' is of no use to us as an HValue if we want the actual function type so we can call it. Haskell being strongly typed we have to 'cheat' by telling Haskell 'yes, I know you think this is an HValue, but I can assure you, it's really a function'. This is Haskell's super dangerous Unsafe.Coerce. It allows us to coerce from one type to another and is, as the name implies, completely unsafe. If the type you are coercing isn't really of the type you are coercing too then you'll very likely crash your program and/or corrupt data. It's the reinterpret_cast<> of the Haskell world. But for handling dynamic loading of code it seems exactly what we want. I'm not sure of any other way to convert the HValue type.

The type of 'print' in the 'Test' module is 'String -> IO ()'. 'unsafeCoerce' can be called to convert the 'HValue' to this:

value <- compileExpr "Test.print"
  do let value' =  (unsafeCoerce value) :: String -> IO ()
     return value'

The receiver of the result, value', can now call the function as it knows its type. The complete code for this example is:

import GHC
import GHC.Paths
import DynFlags
import Unsafe.Coerce

main :: IO ()
main =
    defaultErrorHandler defaultDynFlags $ do
      func <- runGhc (Just libdir) $ do
        dflags <- getSessionDynFlags
        setSessionDynFlags dflags
        target <- guessTarget "Test.hs" Nothing
        addTarget target
        r <- load LoadAllTargets
        case r of
          Failed -> error "Compilation failed"
          Succeeded -> do
            m <- findModule (mkModuleName "Test") Nothing
            setContext [] [m]
            value <- compileExpr ("Test.print")
            do let value' = (unsafeCoerce value) :: String -> IO ()
               return value'
      func "Hello"
      return ()

Build using:

$ ghc -package ghc --make Api.hs
[1 of 1] Compiling Main             ( Api.hs, Api.o )
Linking Api ...
$ ./Api
Hello
$

You can test that it is dynamically compiling and loading by modifying 'Test.hs' so the output is different. Change the 'putStrLn' to putStrLn $ x ++ " World!" and rerun 'Api' without recompiling it:

$ ./Api
Hello World!
$

I mentioned previously the implicit 'GhcMonad' that is passed to each GHC API function. This is provided by the runGhc word. It takes a pointer to the directory where GHC's library files reside. In this example I use the GHC.Paths (available from Hackage package to deal with this using 'libdir'. The second argument is the action to perform, the GhcMonad, and is built using the 'do' syntax. Haskell's 'do' syntax is syntactic sugar that does the magic of threading the GhcMonad state holding the targets, modules, contexts, etc through the various function calls. Find a favourite Monad tutorial/explanation if you want to know more.

'defaultErrorHandler' sets up the environment to handle compilation errors in a standard way. The 'DynFlags' related calls get and set standard flags that can be used to configure the compilation. You can set the equivalent of GHC arguments to control extra features that GHC can use, or use the interpreter (as used by GHCi) rather than the compiler.

Hopefully that helps explain some of what is going on and how to use the current GHC api. I'd appreciate comments on better ways of doing things, and other examples of the API.

Tags: haskell 

2008-11-19

Video, Audio and Cross Domain Usage

Update 19/Nov/2008: This is currently under discussion in the WHATWG and the W3C bug has been marked "won't fix". For now, cross domain usage is fine.

This is a heads up for a change to the HTML5 media elements that may break existing usage. If you use <video> or <audio> then you may need to make changes to the way you serve your media resources.

It looks likely the that <video> and <audio> elements will be restricted so that requests for a media resource from a domain other than the page containing the element will be disallowed. There is a W3C bug (<video> and <audio&ht; should prevent cross-origin media loads) about this.

Once the browsers implement this then default usage of cross domain media resources will fail. This includes requests for resources on subdomains, or different ports. So if you have a page on example.com, this will work in the Firefox implementation now, but will break when the change is made:

<video src='http://media.example.com/foo.ogg'></video>

Cross domain usage can be enabled by sending headers along with the file being requested. The header explicitly says 'this resource can be used cross domain'. The protocol for doing this is described in this Access Control document.

If you are hosting your media resource on a third party hosting system you'll need to ensure that they support these headers if you want to display video and audio on a different domain. Programs such as Icecast, used to stream video and audio, will also need to be compatible with the Access Control specification if you want to use the streams with <video> or <audio> from a server on a different host or port.

This unfortunately breaks some common patterns for building sites that allow uploadable media, or separate media files into subdomains, until they (or their software/hosting provider) supports access control. Now would seem to be a good time to request access control support if you're in this situation.

A discussion on this has started on the Theora mailing list. Jonas Sicking has a good summary of the reasons for the cross domain restriction.

Tags: mozilla 

2008-11-19

HTML5 video and audio spec changes

Some changes have occurred in the HTML 5 specification related to video and audio (the media portion of the spec). These are nicely outlined on the WHATWG blog.

After my blog post about cross domain restriction of audio and video there was a fairly lengthy discussion on the WHATWG mailing list about whether this should be done. The WHATWG blog entry mentioned previously goes into detail about this. The end result is that, for now, there will be no restriction. The W3C bug on the issue has been updated to reflect this.

Tags: mozilla 

2008-11-19

tinyvid.tv updates

I did some minor tweaks to tinyvid.tv recently. I added the ability to add comments to video pages, and add feedback on the site. This uses Intense Debate, a third party commenting engine.

On the video front I added a page to return a random video from those available. The video pages themselves show the duration, download progress and current playback position if the browser supports the relevant events. These should work in the latest Firefox nightlies, maybe with the exception of 'download progress'. The latter works well with the patch from bug 464376 applied. That bug should be resolved soon - it just needs tests written and handle the case of local files (those not served over HTTP).

File uploads are not yet supported. For uploads I imagine I will need to work out a way of moderating/screening the uploaded videos. Even without this functionality the site still has almost 200 videos available for testing the HTML 5 <video> element.

Tags: mozilla 

2008-11-19

Radio New Zealand's Open Source Web Based Audio Player

Radio New Zealand have released their first open source project. It's an audio player for websites, written in JavaScript. The source is hosted on github. The player uses the <audio> element if the browser supports it.

The first module (available now) is an audio player plugin based on the jQuery JavaScript library, and version 0.1 already has some interesting features.

It can play Ogg files natively in Firefox 3.1 using the audio tag. It can also play MP3s using the same javascript API - you just load a different filename and the player works out what to do. The project includes a basic flash-based MP3 player, and some example code to get you started.

Tags: audio 


This site is accessable over tor as hidden service 6vp5u25g4izec5c37wv52skvecikld6kysvsivnl6sdg6q7wy25lixad.onion, or Freenet using key:
USK@1ORdIvjL2H1bZblJcP8hu2LjjKtVB-rVzp8mLty~5N4,8hL85otZBbq0geDsSKkBK4sKESL2SrNVecFZz9NxGVQ,AQACAAE/bluishcoder/-61/


Tags

Archives
Links