-
Notifications
You must be signed in to change notification settings - Fork 32
User guided specialisation stage #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: hkmc2
Are you sure you want to change the base?
Conversation
@@ -0,0 +1,87 @@ | |||
|
|||
|
|||
:spt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be helpful to also show the JS code, which is much easier to read.
84db949
to
2a42dd2
Compare
Co-authored-by: Anson Yeung <[email protected]>
Ported specialisation keyword from previous commits to new HEAD, as it was easier than rebasing, as other components had significantly changed.
As part of moving specialiser code to the new head rewrote logic to pass the specialisation flags through the compiler - now has a much smaller footprint.
2a42dd2
to
b91aa15
Compare
case Primitive(name: Str) | ||
case Function(lhs: SimpleType, rhs: SimpleType) | ||
case Record(fields: Ls[(Str, SimpleType)]) | ||
case ClassType(sym: ClassSymbol, supers: Ls[SimpleType], members: Map[Str, SimpleType]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more like ClassInfo
(which is not a SimpleType
). The types of actual class instances should be a different constructor (which points back to the ClassInfo
).
TODO: Write specification/architecture of algorithm first. You need to attach the flows of normal parameters to the flows of specialized ones. Example:
Another way of thinking about this: specialized functions are polymorphic, but the polymorphic instances are indexed not by the individual call sites but by the list of concrete shapes flowing into their specialized parameters.
In this approach, we remember whatever flows into Note: Similarly, |
Another interesting thing: the calls to specialized functions happening in non-specialized contexts may have to be turned into pattern matches. Reusing the previous example: fun foo(spec x, p1, p2) = ...
fun bar(x, arg) =
foo(x, arg, 123)
bar(Int, 0)
bar(Str, true) This should basically specialize into: fun foo_Int(x, p1, p2) = ...
fun foo_Str(x, p1, p2) = ...
fun bar(x, arg) =
if x is
Int then foo_Int(x, arg, 123)
Str then foo_Str(x, arg, 123)
bar(Int, 0)
bar(Str, true) In fact, at least conceptually, we should probably always generate pattern matching by default and possibly optimize it later. So even a direct call like foo(42, true, 0) should be turned into let $arg = 42
if $arg is
Int then foo_Int($arg, true, 0) which will be optimized the the expected let $arg = 42
foo_Int($arg, true, 0) So this looks pretty similar to the earlier defunctionalization/demethodization project! |
231334e
to
73b350a
Compare
I'm not sure this is desired behaviour but I added this in case a user specialises a parameter which takes in a lambda, which currently at least, requires checking if a value is of type function
Added small features: specialisation in specialised functions being more specific, lambda support Fixed many bugs including not properly hoisting functions, issues with application specialisation, issues in the constraining of some types
The approach I took to specialisation works by taking the SimpleType that a parameter would be and then wrapping it in a SpecialisableType SimpleType, which stores the underlying SimpleType, along with a SpecPoint instance, which stores more details about the specialisation. Specifically I store any known concrete types, any variables that flow into it, and any flows out of it into other SpecialisableTypes. With this, I can do one pass of the program, and constrain the types, and while I constrain I fill in the rest of the information about the specialised parameter. Then with all of the SpecialisableTypes I can figure out what each function should be specialised to. Then on the next pass I case use that table to actually specialise the functions and the applications of the functions. The reason I did it this way over the linked list was just that I was struggling to get the linked list method working, and I had this idea so tried it quickly and it worked better than anything I'd managed with the first approach so I decided to keep at it for a bit longer. |
@@ -307,6 +307,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: | |||
case Elaborator.ctx.builtins.Num => doc"typeof $sd === 'number'" | |||
case Elaborator.ctx.builtins.Bool => doc"typeof $sd === 'boolean'" | |||
case Elaborator.ctx.builtins.Int => doc"globalThis.Number.isInteger($sd)" | |||
case Elaborator.ctx.builtins.Function => doc"typeof $sd === 'function'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this as I had a test where I specialise a parameter which I pass a lambda to, so when I generate the pattern matching for the application I needed to be able to match the lambda, but I'd just like to check that this is okay to do since I'm not very familiar with the codegen side of mlscript.
val checker = new semantics.SimpleSub(curCtx, stl) | ||
val spt = checker.analyzeTermTypes(e).asInstanceOf[semantics.Term.Blk] | ||
val checker = new semantics.Specialiser(curCtx, stl) | ||
val spt = checker.topLevel(e).asInstanceOf[semantics.Term.Blk] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the cast needed here but not in MLsCompiler
? And if we really need a Blk
, couldn't you just make topLevel
take and return one?
Draft PR for implementing a user-guided specialisation pass in the mlscript compiler, very much still WIP