Integrating Haskell to.NET André Santos / Monique Monteiro Rotor Workshop Sep MSR
Motivation Advantages of having Haskell compiler for the.NET platformAdvantages of having Haskell compiler for the.NET platform –Easier interop with other languages –Stronger integration of Haskell to.NET components and tools –Performance –Limitation of previous implementations
Related work Previous Haskell implementations for.NETPrevious Haskell implementations for.NET –Little or no documentation –Partial implementations –(very) limited availability and documentation Implementation of other functional languages for.NETImplementation of other functional languages for.NET –Scheme, F#, SML.NET, Mondrian, etc. –But Haskell is a lazy functional language Implementations of Haskell for the JVMImplementations of Haskell for the JVM
JVM x.NET CLR Focus on OO Languages Focus on language interoperability Support for tail calls Support for function pointers JVM CLR
Main Design Decisions Evaluation strategyEvaluation strategy –uses push/enter model (instead of eval/apply) Data types representationData types representation –Use common container classes (instead of Classes for each constructor of each Haskell datatype) Closure representationClosure representation
Closure Representation One class per closureOne class per closure –Large number of classes: (expected) high overhead for class loading, verification routines, etc. Classes with Function PointersClasses with Function Pointers –Unverifiable code DelegatesDelegates –Type-safe –Better performance in.NET 2.0
One class / closure public class Closure_302 : Closure { public Closure value; public static Closure enter(){ // if value is available // return it // otherwise evaluate specific closure code, update it and return it } public class Closure_302 : Closure { public Closure value; public static Closure enter(){ // if value is available // return it // otherwise evaluate specific closure code, update it and return it }
Common closure class public class Closure : Delegate { public Closure value; public static IClosure Enter(){ // if value is available return it // otherwise save stacks state, evaluate (call to Delegate.Invoke), // update value, restore stacks state and return value... } } public class Closure : Delegate { public Closure value; public static IClosure Enter(){ // if value is available return it // otherwise save stacks state, evaluate (call to Delegate.Invoke), // update value, restore stacks state and return value... } }
Closure Representation Updateable closuresUpdateable closures –Keep a value field Non-updateable closuresNon-updateable closures –Keep an arity field –Keep a partial application field to encapsulate received arguments Both keep the free variablesBoth keep the free variables –Set to null after update Delegates point to the slow entry pointDelegates point to the slow entry point –Slow entry point X fast entry point
Runtime System - Closures
Example: map function map = {} \n {f,l} -> case l of Nil {} -> Nil {} Cons {x,xs} -> let fx = {f,x} \u {} -> f {x} fxs = {f,xs} \u {} -> map {f,xs} in Cons {fx,fxs} map = {} \n {f,l} -> case l of Nil {} -> Nil {} Cons {x,xs} -> let fx = {f,x} \u {} -> f {x} fxs = {f,xs} \u {} -> map {f,xs} in Cons {fx,fxs}
Example: generated code public static IClosure map(NonUpdatable clo){... } public static IClosure map(IClosure f,IClosure l){ Pack scrutinee = (Pack)l.Enter(); switch(scrutinee.tag){ case 1: return new Pack(1); case 2: Pack_2 cons = (Pack_2 ) scrutinee; IClosure x = cons.arg1; IClosure xs = cons.arg2; Updateable_2_FV fx_closure = new Updateable_2_FV(fx); fx_closure.fv1 = f; fx_closure.fv2 = x; Updateable_2_FV mfxs_closure = new Updateable_2_FV (mfxs); mfxs_closure.fv1 = f; mfxs_closure.fv2 = xs ; return new Pack_2 (2, fx_closure, mfxs_closure); } public static IClosure map(NonUpdatable clo){... } public static IClosure map(IClosure f,IClosure l){ Pack scrutinee = (Pack)l.Enter(); switch(scrutinee.tag){ case 1: return new Pack(1); case 2: Pack_2 cons = (Pack_2 ) scrutinee; IClosure x = cons.arg1; IClosure xs = cons.arg2; Updateable_2_FV fx_closure = new Updateable_2_FV(fx); fx_closure.fv1 = f; fx_closure.fv2 = x; Updateable_2_FV mfxs_closure = new Updateable_2_FV (mfxs); mfxs_closure.fv1 = f; mfxs_closure.fv2 = xs ; return new Pack_2 (2, fx_closure, mfxs_closure); } Slow entry point Fast entry point Nil tag Cons tag
Implementation Decisions Use existing optimizing compiler for HaskellUse existing optimizing compiler for Haskell –The Glasgow Haskell Compiler – GHC –STG Intermediate representation Language Generate code in CILGenerate code in CIL –No support for explicit tail-calls from C# –Cannot extend (Multicast)delegate class from C#
Current Status Support for parts of the Haskell preludeSupport for parts of the Haskell prelude –(GHC) modules Base, List, Num, Show, Real, Enum, … Currently performing tests and performance evaluation using some programs from Nofib Benchmark suiteCurrently performing tests and performance evaluation using some programs from Nofib Benchmark suite Limited performance results so farLimited performance results so far
Execution Times GHC.NETNative GHC.NET/Native Digits_e Digits_e2 13, Exp Primes Queens Wheel-sieve Wheel-sieve Tak CAFs (top-level constants)
Some Profiling Results CAFs (updateable closure + value) lead to long-lived objects (Gen 2) and large memory consumptionCAFs (updateable closure + value) lead to long-lived objects (Gen 2) and large memory consumption Delegates are the largest objectsDelegates are the largest objects Delegate constructors have the highest cost among runtime system methodsDelegate constructors have the highest cost among runtime system methods
Main Difficulties In-place updating is not supportedIn-place updating is not supported –It is not possible to store pointers to references Tail-callsTail-calls –Its not clear in which situations they are supported by the execution system Delegates? Virtual methods? Method in other assemblies?Delegates? Virtual methods? Method in other assemblies? –Disabled in fully-trusted environments
Main Difficulties CLR stack limitations:CLR stack limitations: –Push/enter approach requires the direct management of a stack for handling calls to unknown functions –Separate stack(s) had to be implemented –Possible solution: eval/apply approach
Main Difficulties.NET 2.0 in Beta version.NET 2.0 in Beta version Limited profiling tools for.NET 2.0Limited profiling tools for.NET 2.0 Rotor for.NET 2.0 is not available yetRotor for.NET 2.0 is not available yet Calls to C routines in Prelude modulesCalls to C routines in Prelude modules –implement.NET FFI convention
Next Steps Fix the CAF problemFix the CAF problem Evaluate changes in delegates inheritanceEvaluate changes in delegates inheritance Investigate tail-calls and their limitations/overhead in.NET 2.0Investigate tail-calls and their limitations/overhead in.NET 2.0 Extend Rotor with support for ClosuresExtend Rotor with support for Closures Investigate ways of exposing Haskell code to other.NET languages and vice-versaInvestigate ways of exposing Haskell code to other.NET languages and vice-versa
References MONTEIRO, M. L. B., ARAUJO, M., BORGES, R., SANTOS, A. Compiling Non-Strict Functional Languages for the.NET Platform. Journal of Universal Computer Science, v.11, n.7, p , 2005.MONTEIRO, M. L. B., ARAUJO, M., BORGES, R., SANTOS, A. Compiling Non-Strict Functional Languages for the.NET Platform. Journal of Universal Computer Science, v.11, n.7, p , 2005.
Questions? André Santos / Monique Monteiro