Developing in the Wolfram Compiler

Abdul Dakkak
Wolfram Research

Init

SetOptions[Notebook,"TrackCellChangeTimes"->False,PrivateNotebookOptions->{"FileOutlineCache"->False}];
PacletDirectoryAdd["/Users/abduld/compiler/Compile"];
PacletDirectoryAdd["/Users/abduld/compiler/CompileUtilities/CompileUtilities"];
PacletDirectoryAdd["/Users/abduld/compiler/CompileAST/CompileAST"];
PacletDirectoryAdd["/Users/abduld/compiler/LLVMTools/LLVMTools"];
PacletDirectoryAdd["/Users/abduld/compiler/LLVMCompileTools"];
PacletDirectoryAdd["/Users/abduld/compiler/LLVMCompileTools/LLVMCompileTools"];
PrependTo[$Path,"/Users/abduld/compiler/LLVMCompileTools"];
PrependTo[$Path,"/Users/abduld/compiler/LLVMCompileTools/LLVMCompileTools"];
PacletDirectoryAdd["/Users/abduld/compiler/CompiledLibrary/CompiledLibrary"];
PacletDirectoryAdd["/Users/abduld/compiler/TypeFramework/TypeFramework"];
Needs["Compile`"]
Needs["TypeFramework`"]
Needs["TypeFramework`Environments`TypeEnvironment`"]
InitializeCompiler["LoadResources"->False];
cs=LibraryFunctionLoad["/Users/abduld/Library/Mathematica/SystemFiles/LibraryResources/MacOSX-x86-64/CodeSyntaxHighlightLink.dylib","wrtHighlightString",{"UTF8String","UTF8String"},"UTF8String"];
replacements={"\\\\"->"\\","\\\""->"\"","\\("->"\(","\\)"->"\)","\\!"->"\!","\\*"->"\*"};
highlight[code_,lang_]:=StringReplace[cs[code,lang],replacements];

syntaxHighlight[code_]:=
DisplayForm[
With[{lines=StringSplit[code,"\n"]},
RowBox[Riffle[Table[highlight[line,"llvm"],{line,lines}],"\n"]]
]
];

Overview of the Compiler

- A compiler for the Wolfram language
- 100% of the code is written in the Wolfram language
- First released in v12 and continously added with new features and support

Overview of the Talk

- Describe basic usage of the compiler
- Describe a little bit of the internals 
- Detail how developers can add extend the compiler
- Show how one can use the compiler to develop multi-threaded code

Multiple Talks on Compiler or Related Technology

- From PackedArray to NumericArray (Wed 9:30)
- Finance Data Feeds in Wolfram Finance Platform (Thur 10:30)
- Advances in the Wolfram Compiler (Thur 1:30)
- Parsing the Wolfram Language (Thur 2:30)

Overview of the Compiler

Data Interoperability

Interpreted data and compiled data are in the same format, so you can use the compiler for the “compute” parts of the code and the top level for other parts (such as visualization)

fun=FunctionCompile[ Function[{Typed[len,"MachineInteger"]}, 
NestList[Module[{arg=RandomReal[{0., 2.Pi}]},{-Cos[arg],Sin[arg]}]+#&,{0.,0.},len]]];

fig=ListLinePlot[fun[2000]]

Code Interoperability

Can call the interpreter for functions that are not available within the compiler

cfun=FunctionCompile[Function[{Typed[planetName,"String"],Typed[a,"PackedArray"["Real64",1]]},
Module[{getMass,m},
getMass=Typed[KernelFunction[Function[{name},
SetPrecision[
QuantityMagnitude[Entity["Planet",name]["Mass"]],
MachinePrecision]
]],{"String"}->"Real64"];
m=getMass[planetName];
m * a
]]];

cfun["Earth",{1.0,1.0}]

Abortable Code

Compiled code is abortable

cfun=FunctionCompile[Function[{Typed[a,"MachineInteger"]},
Module[{r=0,idx=1},
While[idx>0,
r=If[EvenQ[idx],1,0];
idx=idx+r;
];
r
]]];

TimeConstrained[cfun[3],0.5]

Compiled Code is Composable

Compiled code functions can be passed to other compiled codes

mapperFunc=FunctionCompile[Function[{Typed[a,"MachineInteger"]},
a+2
]];
myMapFunc=FunctionCompile[Function[{Typed[f,{"MachineInteger"}->"MachineInteger"],Typed[lst,"PackedArray"["MachineInteger",1]]},
Module[{res=lst},
Do[
res[[idx]]=f[lst[[idx]]],
{idx,Length[lst]}
];
res
]
]];

myMapFunc[mapperFunc,Range[10]]

Compiled Code supports strings

Compiled code functions support strings (currently adding more support)

letterCountFunc=FunctionCompile[Function[{Typed[text,"String"]},
Module[{getIdx,cidx,res=ConstantArray[0,27]},
getIdx=Function[{Typed[char,"String"]},
Module[{charCode=First[ToCharacterCode[char]]},
If[charCode<First[ToCharacterCode["a"]]||charCode>First[ToCharacterCode["z"]],
27,
charCode-First[ToCharacterCode["a"]]+1
]
]
];
Do[
cidx=getIdx[StringTake[text,{idx}]];
res[[cidx]]++,
{idx,StringLength[text]}
];
res
]
]];

letterCountFunc[ToLowerCase@ExampleData[{"Text","DeclarationOfIndependence"}]]

Compiled Code supports expressions

Compiled code functions support expressions (currently adding more support)

exprFunc=FunctionCompile[Function[{Typed[a,"Expression"],Typed[b,"Expression"]},
a*b
]];

exprFunc[m,Sin[n]]

Compiled Code supports numeric array

Compiled code functions support numeric arrays

movingAverageFunc=FunctionCompile[Function[{Typed[lst,"NumericArray"["Real64",1]]},
Module[{res,len=Length[lst],x=0.0,y=0.0,z=0.0,n=0.0},
Table[
Which[
idx===1,
n=2.0;x=0.0;y=lst[[1]];z=lst[[2]],
idx===len,
n=2.0;x=lst[[len-1]];y=lst[[len]];z=0.0,
True,
n=3.0;x=lst[[idx-1]];y=lst[[idx]];z=lst[[idx+1]]
];
(x+y+z)/n,
{idx,len}
]
]
]];

arry=NumericArray[Range[10],"Real64"];
movingAverageFunc[arry]

MovingAverage[arry,2]

arry=Range[100000.0];
MovingAverage[arry,2];//AbsoluteTiming
nd=NumericArray[arry,"Real64"];
movingAverageFunc[nd];//AbsoluteTiming

ieval=({{"[◼]", "EvaluateBenchmark"}})[MovingAverage[#,2]&,ConstantArray[1,#]&,10^Range[6]];
ceval=({{"[◼]", "EvaluateBenchmark"}})[movingAverageFunc,NumericArray[ConstantArray[1,#],"Real64"]&,10^Range[6]];

ListLogLogPlot[{Legended[ieval,"Builtin"],Legended[ceval,"Compiled"]},GridLines->Automatic,AxesLabel->{"n","time (s)"},ImageSize->Scaled[0.9],PlotTheme->"Grid",Joined->True,Mesh->All,AspectRatio->1/4]

Performance that can be achieved

Implemented a set functions in C and in the compiler

Compiler Internals

Designed as a multi-stage pipeline

Compiler Internals

Expression first get analyzed and simplified into a limited number of primitives

Compiler Internals

An untyped intermediate representation gets created which is system independent

Compiler Internals

Type inference and other optimizattions get applied on the IR

Compiler Internals

Machine dependent IR is then generated

Compiler Internals

Finally assembly, executable code, or a library is created

Developing in Compiler

Users are free to plug in code within any stage of the pipeline

Developing in Compiler

We are just going to concentrate on how to extend the compiler by adding new types or macros

***Syntax is not final and subject to change

Adding Type

Types can be added by extending the type environment

binaryTy=TypeSpecifier[ {"Int64Modulo24","Int64Modulo24"}->"Int64Modulo24"];
plusOp=Function[{a0,b0},
Module[{a,b},
a=Native`BitCast[a0,"Integer64"];
b=Native`BitCast[b0,"Integer64"];
Native`BitCast[Mod[a+b,24],"Int64Modulo24"]
]
];
timesOp=Function[{a0,b0},
Module[{a,b},
a=Native`BitCast[a0,"Integer64"];
b=Native`BitCast[b0,"Integer64"];
Native`BitCast[Mod[a*b,24],"Int64Modulo24"]
]
];

env=$DefaultTypeEnvironment;
env["reopen"];
env["declareType", TypeConstructor["Int64Modulo24"]];
env["declareFunction", Plus,Typed[plusOp, binaryTy]];
env["declareFunction", Times,Typed[timesOp, binaryTy]];
env["finalize"]

someFun=Function[{Typed[a0,"Integer64"],Typed[b0,"Integer64"]},
Module[{a,b,res},
a=Native`BitCast[a0,"Int64Modulo24"];
b=Native`BitCast[b0,"Int64Modulo24"];
res=(a*b+b)+(a*b+b);
Native`BitCast[res,"Integer64"]
]
];

pm=CompileToIR@someFun;

pm["toString"]

Adding Functions

Functions can be added by extending the type environment

euclideanTy=TypeForAll[{"a"}, {"a","a","a","a"}->"a"];
euclideanF=Function[{x1,y1,x2,y2},
(Sqrt[(((-x1+x2))^2)+(((-y1+y2))^2)])
]

env=$DefaultTypeEnvironment;
env["reopen"];
env["declareFunction", myDistance,Typed[euclideanF, euclideanTy]];
env["finalize"]

myNearestFun=Function[{Typed[pt1,"PackedArray"["Real64",1]],Typed[ptCloud,"PackedArray"["Real64",2]]},
Module[{minVal=First[ptCloud],minDistance=-1.0,curr,currDistance},
Do[
curr=ptCloud[[currIdx]];
currDistance=myDistance[pt1[[1]],pt1[[2]],curr[[1]],curr[[2]]];
If[minDistance==-1.0||currDistance<minDistance,
minDistance=currDistance;
minVal=curr
],
{currIdx,Length[ptCloud]}
];
minVal
]
];

distMatrix=FunctionCompile@myNearestFun;
distMatrix[{1.0,1.0},RandomReal[{-10,10},{10,2}]]

Adding Functions which Reference Libraries

Functions can reference external libraries

cFun=FunctionCompile[Function[{Typed[x,"Real64"]},
Native`PrimitiveFunction["LibC`sin"][x]
]];
cFun[3.0]

Functions can be written in assembly, etc...

With[{SSE3=BitShiftLeft[1,0]},
fun=Function[{},Module[{info,ecx},
info=Native`PrimitiveFunction["LLVMX86CPUID"][];
ecx=info[[2]];
If[BitGet[ecx,SSE3]=!=0,Print["SSE3"]];
]]
];
CompileToLLVMIR[fun,"InvocationMode"->"Standalone","LLVMOptimization"->"ClangOptimization"[3],"AbortHandling"->False]//syntaxHighlight

Adding Macros

Macros operate on the Wolfram Expression and use the Pattern language

CompileToAST[Function[{x}, HornerForm[11 x^3+4 x^2+7 x+2]]]["prettyPrint"]

Registering

Needs["Compile`AST`Macro`MacroEnvironment`"]

RegisterMacro[$DefaultMacroEnvironment, HornerForm,
       HornerForm[c3_*x_^3+c2_*x_^2+c1_*x_+c0_]:>c0+c1 x+x^2 *(c2+c3 x)
];

CompileToAST[Function[{x}, HornerForm[11 x^3+4 x^2+7 x+2]]]["prettyPrint"]

New Features

- Datastructures (see TWJ’s talk)
- Parallel Evaluation (experimental)

Parallel Evaluation

Want to write

func=Function[{Typed[f,{"MachineInteger"}->"Real64"],Typed[lst,"PackedArray"["Integer64",1]]},
ParallelMap[f,lst]
];

And want to use multiple threads rather than multiple processes.

Current Parallel Evaluation in Wolfram

Internally, code uses multi-threaded compuattion, but user code can not explicitly write multi-threaded code. There is ParallelMap, ParallelTable, ... but these are multi-process evaluation that emulate multi-threaded.

Current Parallel Evaluation in Wolfram

Current Parallel Evaluation in Wolfram

Current Parallel Evaluation in Wolfram

ParallelMap in Compiler

Uses an AST transformation to rewrite

ParallelMap in Compiler

Evaluation flow looks very similar to the multi-process ParallelMap case

ParallelMap in Compiler

Data is shared rather than copied however

Examples: Speedup

This is dummy example that does nothing useful

parallelFunc=Function[{},
Module[{f=Function[{Typed[e,"MachineInteger"]},Module[{z=0},
Do[z+=r,{n,1000},{r,e}];z]]},
ParallelDo[f[ii],{ii,1000}]]
];
parallelFun=FunctionCompile[parallelFunc]

parallelFun[];//AbsoluteTiming

Compare to sequential function

seqFunc=Function[{},
Module[{f=Function[{Typed[e,"MachineInteger"]},Module[{z=0},
Do[z+=r,{n,1000},{r,e}];z]]},
Do[f[ii],{ii,1000}]]
];
seqFun=FunctionCompile[seqFunc]

seqFun[];//AbsoluteTiming

Compare to ParallelDo function (73s)

parallelFunc[];//AbsoluteTiming

Takes too long, let’s try a smaller input (n=100 rather than n=1000)

parallelDoFunc=Function[{},
Module[{f=Function[{Typed[e,"MachineInteger"]},Module[{z=0},
Do[z+=r,{n,100},{r,e}];z]]},
ParallelDo[f[ii],{ii,1000}]]
];
parallelDoFunc[];//AbsoluteTiming

Multiple Talks on Compiler or Related Technology

- Finance Data Feeds in Wolfram Finance Platform (Thur 10:30)
- Advances in the Wolfram Compiler (Thur 1:30)
- Parsing the Wolfram Language (Thur 2:30)