Skip to content
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

Class Lifter #266

Open
wants to merge 101 commits into
base: hkmc2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
66edd88
infrastructure
CAG2Mark Jan 26, 2025
464e5a2
some variable analysis
CAG2Mark Jan 27, 2025
9444ad4
Merge hkmc2
CAG2Mark Jan 27, 2025
bc172c6
Merge branch 'hkmc2' into class-lifter
CAG2Mark Jan 27, 2025
3e59e8e
create closure class
CAG2Mark Jan 27, 2025
0d56edf
add infrastructure to later distinguish mutable variables
CAG2Mark Jan 27, 2025
2ff423b
Function lifting working
CAG2Mark Jan 27, 2025
93eb9dc
fix typos
CAG2Mark Jan 27, 2025
f57b038
update test
CAG2Mark Jan 27, 2025
7aa6e0f
fix log and add test
CAG2Mark Jan 27, 2025
babd710
refactor
CAG2Mark Jan 27, 2025
19f43d1
Add auxiliary parameters
CAG2Mark Jan 28, 2025
7a3ae4e
small refactor
CAG2Mark Jan 28, 2025
4e2f4a3
capture at BlockMemberSymbol refs instead of at the start of the func…
CAG2Mark Jan 28, 2025
41bf24b
small optimization
CAG2Mark Jan 28, 2025
d7c7bef
pass immutable variables as args
CAG2Mark Jan 28, 2025
2a45a20
fix bug
CAG2Mark Jan 28, 2025
f97182f
add broken test
CAG2Mark Jan 28, 2025
9cbad67
refactor and fix huge bug
CAG2Mark Jan 28, 2025
f3bf942
small progress and fix typo
CAG2Mark Jan 28, 2025
cc55d1e
re-organization and some documentation
CAG2Mark Jan 29, 2025
f09b1a6
progess on class lifting
CAG2Mark Jan 29, 2025
a12869d
Fix ctor generation
CAG2Mark Jan 29, 2025
27f390f
Fix ctor generation
CAG2Mark Jan 29, 2025
3982756
classes almost working, refactor
CAG2Mark Jan 29, 2025
eb63e8c
revert weird whitespace change
CAG2Mark Jan 29, 2025
b7f3e41
can now lift classes
CAG2Mark Jan 29, 2025
175ced8
lift lambdas and adapt handler code to work with class lifting
CAG2Mark Jan 29, 2025
71e48dd
Fix bug in capture instantiation
AnsonYeung Jan 29, 2025
ce089fe
Remove extra semicolon
AnsonYeung Jan 29, 2025
7ee2c45
Allow paths in class name
AnsonYeung Jan 29, 2025
8aac8cd
optimize function calls, fix tests
CAG2Mark Jan 30, 2025
3e04854
Merge hkmc2
CAG2Mark Jan 31, 2025
0830d1d
Merge hkmc2
CAG2Mark Jan 31, 2025
bce89f9
Update blocktransformer and other code to support dynamic fields
CAG2Mark Jan 31, 2025
206df9d
update tests
CAG2Mark Jan 31, 2025
ae7aef9
Bug fixes
AnsonYeung Jan 31, 2025
09eb9b7
add more advanced analysis
CAG2Mark Feb 1, 2025
406058b
add test
CAG2Mark Feb 1, 2025
4dd502d
merge
CAG2Mark Feb 1, 2025
8c5e8dc
Fix bugs
CAG2Mark Feb 1, 2025
6f2350a
Add broken test
CAG2Mark Feb 1, 2025
38bba86
Merge branch 'hkmc2' into class-lifter
CAG2Mark Feb 1, 2025
ef2bf3d
Update tests
CAG2Mark Feb 1, 2025
7695366
Update tests
CAG2Mark Feb 1, 2025
3c353d2
add scc partioning algo
CAG2Mark Feb 2, 2025
3aceba7
re-organize tests
CAG2Mark Feb 4, 2025
af4f6d7
Merge branch 'hkmc2' into class-lifter
CAG2Mark Feb 4, 2025
239b40c
Update tests and fix params
CAG2Mark Feb 4, 2025
b69a8cb
fix some things
CAG2Mark Feb 4, 2025
688187a
refactor some things
CAG2Mark Feb 4, 2025
9a9229a
fix newline
CAG2Mark Feb 4, 2025
e49cdaa
fix newline
CAG2Mark Feb 4, 2025
0fcdd5c
fix whitespace
CAG2Mark Feb 4, 2025
b025916
fix whitespace
CAG2Mark Feb 4, 2025
b736d61
merge
CAG2Mark Feb 8, 2025
ae64a5d
update tests
CAG2Mark Feb 8, 2025
88cb254
finish sccsWithInfo function
CAG2Mark Feb 8, 2025
420174c
Fix analysis
CAG2Mark Feb 8, 2025
0d6059f
Fix instantiate
CAG2Mark Feb 8, 2025
306f1b1
add instance check test
CAG2Mark Feb 8, 2025
e7653af
merge hkmc2
CAG2Mark Feb 8, 2025
0a6379d
merge hkmc2
CAG2Mark Feb 8, 2025
24b690b
remove useless import
CAG2Mark Feb 8, 2025
046fd7b
refactor floatoutdefns
CAG2Mark Feb 8, 2025
dab24e4
refactor floatoutdefns and update ctx
CAG2Mark Feb 8, 2025
21219d3
add instance check test
CAG2Mark Feb 8, 2025
c8e53f0
optimize and fix many problems
CAG2Mark Feb 9, 2025
5ba2623
fix test and add test
CAG2Mark Feb 9, 2025
d2129d5
properly handle ignored classes
CAG2Mark Feb 9, 2025
8bde300
Add todo
AnsonYeung Feb 9, 2025
beca977
modules almost working
CAG2Mark Feb 9, 2025
c831ae9
most things working
CAG2Mark Feb 9, 2025
b6edbcf
Merge branch 'class-lifter' of github.com:CAG2Mark/mlscript into clas…
CAG2Mark Feb 9, 2025
e4f5487
update test
CAG2Mark Feb 9, 2025
2509dfe
Lift objects, add warning for modules, fix instantiating parameter-le…
CAG2Mark Feb 10, 2025
87edceb
Update warning
CAG2Mark Feb 10, 2025
d0799ef
Merge branch 'hkmc2' into class-lifter
CAG2Mark Feb 10, 2025
b7bdbcc
update tests
CAG2Mark Feb 10, 2025
ad5a7b6
Fix tarjan's algorithm
CAG2Mark Feb 11, 2025
ac33e6b
specialize cont class, improve analysis, always desugar lambdas
CAG2Mark Feb 11, 2025
94a3c0f
fix whitespace
CAG2Mark Feb 11, 2025
d8274bc
Fix bug with ctors
CAG2Mark Feb 11, 2025
34da46a
add broken extends tests
CAG2Mark Feb 12, 2025
761e686
fix many problems with modules, basic extends working for nested clas…
CAG2Mark Feb 12, 2025
26ea02c
Prepare for PR merge
CAG2Mark Feb 12, 2025
507344e
Merge branch 'hkmc2' into class-lifter
CAG2Mark Feb 12, 2025
52f8844
merge and update tests
CAG2Mark Feb 12, 2025
70f673c
pr changes
CAG2Mark Feb 12, 2025
50f3de6
add comments and unused stuff
CAG2Mark Feb 12, 2025
fc8e400
restore the test
CAG2Mark Feb 12, 2025
632ca98
move the test to the correct place
CAG2Mark Feb 12, 2025
e3e989e
Fix line numbers
CAG2Mark Feb 12, 2025
b93112f
update compile tests
CAG2Mark Feb 12, 2025
3ee9e4c
address pr comments
CAG2Mark Feb 13, 2025
22bb3bf
Merge branch 'hkmc2' into class-lifter
CAG2Mark Feb 13, 2025
5099394
Update
CAG2Mark Feb 13, 2025
b9e30e6
Don't lift classes directly in a module, fix defns not being lifted o…
CAG2Mark Feb 13, 2025
ca2cdfc
don't rewrite ignored objects
CAG2Mark Feb 13, 2025
d47ce44
remove unneeded case
CAG2Mark Feb 13, 2025
19cab27
fix imports
CAG2Mark Feb 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions core/shared/main/scala/utils/algorithms.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mlscript.utils
import scala.annotation.tailrec
import scala.collection.immutable.SortedMap


object algorithms {
final class CyclicGraphError(message: String) extends Exception(message)

Expand All @@ -29,4 +30,145 @@ object algorithms {
}
sort(toPred, Seq())
}

/**
* Partitions a graph into its strongly connected components. The input type must be able to
* be hashed efficiently as it will be used as a key.
*
* @param edges The edges of the graph.
* @param nodes Any additional nodes that are not necessarily in the edges list. (Overlap is fine)
* @return A list of strongly connected components of the graph.
*/
def partitionScc[A](edges: Iterable[(A, A)], nodes: Iterable[A]): List[List[A]] = {
case class SccNode[A](
val node: A,
val id: Int,
var num: Int = -1,
var lowlink: Int = -1,
var visited: Boolean = false,
var onStack: Boolean = false
)

// pre-process: assign each node an id
val edgesSet = edges.toSet
val nodesUniq = (edgesSet.flatMap { case (a, b) => Set(a, b) } ++ nodes.toSet).toList
val nodesN = nodesUniq.zipWithIndex.map { case (node, idx) => SccNode(node, idx) }
val nodeToIdx = nodesN.map(node => node.node -> node.id).toMap
val nodesIdx = nodesN.map { case node => node.id -> node }.toMap

val neighbours = edges
.map { case (a, b) => (nodeToIdx(a), nodesIdx(nodeToIdx(b))) }
.groupBy(_._1)
.map { case (a, b) => a -> b.map(_._2) }
.withDefault(_ => Nil)

// Tarjan's algorithm

var stack: List[SccNode[A]] = List.empty
var sccs: List[List[A]] = List.empty
var i = 0

def dfs(node: SccNode[A], depth: Int = 0): Unit = {
def printlnsp(s: String) = {
println(s)
}

node.num = i
node.lowlink = node.num
node.visited = true
stack = node :: stack
i += 1
for (n <- neighbours(node.id)) {
if (!n.visited) {
dfs(n, depth + 1)
node.lowlink = n.lowlink.min(node.lowlink)
} else if (!n.onStack) {
node.lowlink = n.num.min(node.lowlink)
}
}
if (node.lowlink == node.num) {
var scc: List[A] = List.empty
var cur = stack.head
stack = stack.tail
cur.onStack = true
while (cur.id != node.id) {
scc = cur.node :: scc
cur = stack.head
stack = stack.tail
cur.onStack = true
}
scc = cur.node :: scc
sccs = scc :: sccs
}
}

for (n <- nodesN) {
if (!n.visited) dfs(n)
}
sccs
}


/**
* Info about a graph partitioned into its strongly-connected sets. The input type must be able to
* be hashed efficiently as it will be used as a key.
*
* @param sccs The strongly connected sets.
* @param edges The edges of the strongly-connected sets. Together with `sccs`, this forms an acyclic graph.
* @param inDegs The in-degrees of the above described graph.
* @param outDegs The out-degrees of the above described graph.
*/
case class SccsInfo[A](
sccs: Map[Int, List[A]],
edges: Map[Int, Iterable[Int]],
inDegs: Map[Int, Int],
outDegs: Map[Int, Int],
)

/**
* Partitions a graph into its strongly connected components and returns additional information
* about the partition. The input type must be able to be hashed efficiently as it will be used as a key.
*
* @param edges The edges of the graph.
* @param nodes Any additional nodes that are not necessarily in the edges list. (Overlap is fine)
* @return The partitioned graph and info about it.
*/
def sccsWithInfo[A](edges: Iterable[(A, A)], nodes: Iterable[A]): SccsInfo[A] = {
val sccs = partitionScc(edges, nodes)
val withIdx = sccs.zipWithIndex.map(_.swap).toMap
val lookup = (
for {
(id, scc) <- withIdx
node <- scc
} yield node -> id
).toMap

val notInSccEdges = edges.map {
case (a, b) => (lookup(a), lookup(b))
}.filter {
case (a, b) => a != b
}

val outs = notInSccEdges.groupBy {
case (a, b) => a
}

val sccEdges = withIdx.map {
case (a, _) => a -> Nil // add default case
} ++ outs.map {
case (a, edges) => a -> edges.map(_._2)
}.toMap

val inDegs = notInSccEdges.groupBy {
case (a, b) => b
}.map {
case (b, edges) => b -> edges.size
}

val outDegs = outs.map {
case (a, edges) => a -> edges.size
}

SccsInfo(withIdx, sccEdges, inDegs, outDegs)
}
}
4 changes: 4 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def config(using Config): Config = summon
case class Config(
sanityChecks: Opt[SanityChecks],
effectHandlers: Opt[EffectHandlers],
liftDefns: Opt[LiftDefns],
):

def stackSafety: Opt[StackSafety] = effectHandlers.flatMap(_.stackSafety)
Expand All @@ -24,6 +25,7 @@ object Config:
sanityChecks = N, // TODO make the default S
// sanityChecks = S(SanityChecks(light = true)),
effectHandlers = N,
liftDefns = N
CAG2Mark marked this conversation as resolved.
Show resolved Hide resolved
)

case class SanityChecks(light: Bool)
Expand All @@ -35,6 +37,8 @@ object Config:
val default: StackSafety = StackSafety(
stackLimit = 500,
)

case class LiftDefns() // there may be other settings in the future, having it as a case class now

end Config

Expand Down
74 changes: 66 additions & 8 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ sealed abstract class Block extends Product with AutoLocated:
case TryBlock(sub, fin, rst) => 1 + sub.size + fin.size + rst.size
case Label(_, bod, rst) => 1 + bod.size + rst.size
case HandleBlock(lhs, res, par, args, cls, handlers, bdy, rst) => 1 + handlers.map(_.body.size).sum + bdy.size + rst.size
case AssignDynField(lhs, fld, arrayIdx, rhs, rst) => 1 + rst.size

// TODO conserve if no changes
def mapTail(f: BlockTail => Block): Block = this match
Expand Down Expand Up @@ -91,12 +92,37 @@ sealed abstract class Block extends Product with AutoLocated:
case TryBlock(sub, finallyDo, rest) => sub.freeVars ++ finallyDo.freeVars ++ rest.freeVars
case Assign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVars ++ rest.freeVars
case AssignField(lhs, nme, rhs, rest) => lhs.freeVars ++ rhs.freeVars ++ rest.freeVars
case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVarsLLIR ++ fld.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR
case Define(defn, rest) => defn.freeVars ++ rest.freeVars
case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) =>
(bod.freeVars - lhs) ++ rst.freeVars ++ hdr.flatMap(_.freeVars)
case HandleBlockReturn(res) => res.freeVars
case End(msg) => Set.empty

// TODO: freeVarsLLIR skips `fun` and `cls` in `Call` and `Instantiate` respectively, which is needed in some
// other places. However, adding them breaks some LLIR tests. Supposedly, once the IR uses the new symbol system,
// this should no longer happen. This version should be removed once that is resolved.
lazy val freeVarsLLIR: Set[Local] = this match
case Match(scrut, arms, dflt, rest) =>
scrut.freeVarsLLIR ++ dflt.toList.flatMap(_.freeVarsLLIR) ++ rest.freeVarsLLIR
++ arms.flatMap:
(pat, arm) => arm.freeVarsLLIR -- pat.freeVars
case Return(res, implct) => res.freeVarsLLIR
case Throw(exc) => exc.freeVarsLLIR
case Label(label, body, rest) => (body.freeVarsLLIR - label) ++ rest.freeVarsLLIR
case Break(label) => Set(label)
case Continue(label) => Set(label)
case Begin(sub, rest) => sub.freeVarsLLIR ++ rest.freeVarsLLIR
case TryBlock(sub, finallyDo, rest) => sub.freeVarsLLIR ++ finallyDo.freeVarsLLIR ++ rest.freeVarsLLIR
case Assign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR
case AssignField(lhs, nme, rhs, rest) => lhs.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR
case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVarsLLIR ++ fld.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR
case Define(defn, rest) => defn.freeVarsLLIR ++ rest.freeVarsLLIR
case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) =>
(bod.freeVarsLLIR - lhs) ++ rst.freeVarsLLIR ++ hdr.flatMap(_.freeVars)
case HandleBlockReturn(res) => res.freeVarsLLIR
case End(msg) => Set.empty

lazy val subBlocks: Ls[Block] = this match
case Match(p, arms, dflt, rest) => p.subBlocks ++ arms.map(_._2) ++ dflt.toList :+ rest
case Begin(sub, rest) => sub :: rest :: Nil
Expand All @@ -122,15 +148,18 @@ sealed abstract class Block extends Product with AutoLocated:
// Note that this returns the definitions in reverse order, with the bottommost definiton appearing
// last. This is so that using defns.foldLeft later to add the definitions to the front of a block,
// we don't need to reverse the list again to preserve the order of the definitions.
def floatOutDefns =
def floatOutDefns(
ignore: Defn => Bool = _ => false,
preserve: Defn => Bool = _ => false) =
var defns: List[Defn] = Nil
val transformer = new BlockTransformerShallow(SymbolSubst()):
override def applyBlock(b: Block): Block = b match
case Define(defn, rest) => defn match
case Define(defn, rest) if !ignore(defn) => defn match
case v: ValDefn => super.applyBlock(b)
case _ =>
defns ::= defn
applyBlock(rest)
if preserve(defn) then super.applyBlock(b)
else applyBlock(rest)
case _ => super.applyBlock(b)

(transformer.applyBlock(this), defns)
Expand Down Expand Up @@ -238,10 +267,20 @@ sealed abstract class Defn:
lazy val freeVars: Set[Local] = this match
case FunDefn(own, sym, params, body) => body.freeVars -- params.flatMap(_.paramSyms) - sym
case ValDefn(owner, k, sym, rhs) => rhs.freeVars
case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) =>
case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentSym,
methods, privateFields, publicFields, preCtor, ctor) =>
preCtor.freeVars
++ ctor.freeVars ++ methods.flatMap(_.freeVars)
-- privateFields -- publicFields.map(_.sym)
-- privateFields -- publicFields.map(_.sym) -- auxParams.flatMap(_.paramSyms)

lazy val freeVarsLLIR: Set[Local] = this match
case FunDefn(own, sym, params, body) => body.freeVarsLLIR -- params.flatMap(_.paramSyms) - sym
case ValDefn(owner, k, sym, rhs) => rhs.freeVarsLLIR
case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentSym,
methods, privateFields, publicFields, preCtor, ctor) =>
preCtor.freeVarsLLIR
++ ctor.freeVarsLLIR ++ methods.flatMap(_.freeVarsLLIR)
-- privateFields -- publicFields.map(_.sym) -- auxParams.flatMap(_.paramSyms)

final case class FunDefn(
owner: Opt[InnerSymbol],
Expand All @@ -261,10 +300,11 @@ final case class ValDefn(

final case class ClsLikeDefn(
owner: Opt[InnerSymbol],
isym: MemberSymbol[? <: ClassLikeDef],
isym: MemberSymbol[? <: ClassLikeDef] & InnerSymbol,
sym: BlockMemberSymbol,
k: syntax.ClsLikeKind,
paramsOpt: Opt[ParamList],
auxParams: List[ParamList],
parentPath: Opt[Path],
methods: Ls[FunDefn],
privateFields: Ls[TermSymbol],
Expand All @@ -282,6 +322,7 @@ final case class Handler(
params: Ls[ParamList],
body: Block,
):
lazy val freeVarsLLIR: Set[Local] = body.freeVarsLLIR -- params.flatMap(_.paramSyms) - sym - resumeSym
lazy val freeVars: Set[Local] = body.freeVars -- params.flatMap(_.paramSyms) - sym - resumeSym

/* Represents either unreachable code (for functions that must return a result)
Expand All @@ -298,6 +339,11 @@ enum Case:
case Cls(_, path) => path.freeVars
case Tup(_, _) => Set.empty

lazy val freeVarsLLIR: Set[Local] = this match
case Lit(_) => Set.empty
case Cls(_, path) => path.freeVarsLLIR
case Tup(_, _) => Set.empty

sealed abstract class Result:

// TODO rm Lam from values and thus the need for this method
Expand All @@ -310,14 +356,26 @@ sealed abstract class Result:
case _ => Nil

CAG2Mark marked this conversation as resolved.
Show resolved Hide resolved
lazy val freeVars: Set[Local] = this match
case Call(fun, args) => args.flatMap(_.value.freeVars).toSet
case Instantiate(cls, args) => args.flatMap(_.freeVars).toSet
case Call(fun, args) => fun.freeVars ++ args.flatMap(_.value.freeVars).toSet
case Instantiate(cls, args) => cls.freeVars ++ args.flatMap(_.freeVars).toSet
case Select(qual, name) => qual.freeVars
case Value.Ref(l) => Set(l)
case Value.This(sym) => Set.empty
case Value.Lit(lit) => Set.empty
case Value.Lam(params, body) => body.freeVars -- params.paramSyms
case Value.Arr(elems) => elems.flatMap(_.value.freeVars).toSet
case DynSelect(qual, fld, arrayIdx) => qual.freeVars ++ fld.freeVars

lazy val freeVarsLLIR: Set[Local] = this match
case Call(fun, args) => args.flatMap(_.value.freeVarsLLIR).toSet
case Instantiate(cls, args) => args.flatMap(_.freeVarsLLIR).toSet
case Select(qual, name) => qual.freeVarsLLIR
case Value.Ref(l) => Set(l)
case Value.This(sym) => Set.empty
case Value.Lit(lit) => Set.empty
case Value.Lam(params, body) => body.freeVarsLLIR -- params.paramSyms
case Value.Arr(elems) => elems.flatMap(_.value.freeVarsLLIR).toSet
case DynSelect(qual, fld, arrayIdx) => qual.freeVarsLLIR ++ fld.freeVarsLLIR

// type Local = LocalSymbol
type Local = Symbol
Expand Down
Loading
Loading