diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 35f73c1294..d55f29a291 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -28,6 +28,7 @@ case class Config( target: CompilationTarget, rewriteWhileLoops: Bool, etaExpansion: Opt[EtaExpansion], + dataRepFlatten: Opt[DataRepFlatten], qqEnabled: Bool, funcToCls: Bool, commentGeneratedCode: Bool, @@ -80,6 +81,7 @@ object Config: rewriteWhileLoops = false, stageCode = false, etaExpansion = S(EtaExpansion.default), + dataRepFlatten = N, qqEnabled = false, funcToCls = false, commentGeneratedCode = false, @@ -221,6 +223,13 @@ object Config: logAccumulator = false, )) val default: EtaExpansion = withDebug(debug = false) + + case class DataRepFlatten(debug: Bool, mono: Bool) + object DataRepFlatten: + val default = DataRepFlatten( + debug = false, + mono = false, + ) /** `altSmallThreshold` is the alternative threshold for inlining things into @inline functions. * Normally, we avoid inlining into @inline functions as that could lead to unexpected code bloat. */ @@ -606,6 +615,24 @@ object ConfigParser: case _ => expect("EtaExpansion(...)")(tree) N + + private def parseDataRepFlatten(tree: Tree, current: Opt[Config.DataRepFlatten])(using Raise): Opt[Config.DataRepFlatten] = + tree match + case Call("DataRepFlatten", args) => + val base = current.getOrElse(Config.DataRepFlatten.default) + var debug = base.debug + var mono = base.mono + args.foreach: + case NamedArg("debug", value) => + setFrom(value)(parseBool)(v => debug = v) + case NamedArg("mono", value) => + setFrom(value)(parseBool)(v => mono = v) + case other => + unsupported("DataRepFlatten", other) + S(Config.DataRepFlatten(debug, mono)) + case _ => + expect("DataRepFlatten(...)")(tree) + N /** Parse a single field override like `tailRecOpt: false`. */ private def parseField(name: Str, value: Tree)(using Raise): Config => Config = name match @@ -642,6 +669,10 @@ object ConfigParser: optionalFieldWithCurrent(value)(_.etaExpansion)( (tree, current) => parseEtaExpansion(tree, current) )(v => _.copy(etaExpansion = v)) + case "dataRepFlatten" => + optionalFieldWithCurrent(value)(_.dataRepFlatten)( + (tree, current) => parseDataRepFlatten(tree, current) + )(v => _.copy(dataRepFlatten = v)) case "deadParamElim" => optionalFieldWithCurrent(value)(_.deadParamElim)( (tree, current) => parseDeadParamElim(tree, current) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index d326dc1188..56accf2b38 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -266,7 +266,7 @@ sealed abstract class Block extends Product: private def flatten(k: End => Block): Block = this match - case Match(scrut, arms, dflt, rest) => + case m @ Match(scrut, arms, dflt, rest) => val newRest = rest.flatten(k) val newArms = arms.mapConserve: arm => val newBody = arm._2.flattened @@ -274,7 +274,7 @@ sealed abstract class Block extends Product: val newDflt = dflt.mapConserve(_.flattened) if (newRest is rest) && (newArms is arms) && (newDflt is dflt) then this - else Match(scrut, newArms, newDflt, newRest) + else Match(scrut, newArms, newDflt, newRest)(m.annotations) case Label(label, loop, body, rest) => val newBody = body.flattened @@ -371,7 +371,9 @@ case class Match( arms: Ls[Case -> Block], dflt: Opt[Block], rest: Block, -) extends Block with ProductWithTail with NonBlockTail +)(val annotations: Ls[Annot]) extends Block with ProductWithTail with NonBlockTail: + def matchShapes: Opt[Annot.MatchShapes] = annotations.collectFirst: + case annotation: Annot.MatchShapes => annotation case class Return(res: Result) extends BlockTail @@ -462,6 +464,10 @@ object Define: case _ => new Define(defn, rest) object Match: + def apply(scrut: Path, arms: Ls[Case -> Block], dflt: Opt[Block], rest: Block)(annotations: Ls[Annot]): Block = + if annotations.nonEmpty then new Match(scrut, arms, dflt, rest)(annotations) + else apply(scrut, arms, dflt, rest) + def apply(scrut: Path, _arms: Ls[Case -> Block], _dflt: Opt[Block], rest: Block): Block = val emptyDflt = _dflt.forall(_.isEmpty) val dflt = if emptyDflt then N else _dflt @@ -470,7 +476,7 @@ object Match: else dflt match case S(Unreachable(_)) if scrut.isPure && arms.sizeCompare(1) === 0 => Begin(arms.head._2, rest) - case S(Match(`scrut`, arms2, dflt2, _: End)) => // TODO: also handle non-End rest (may require a join point) + case S(m @ Match(`scrut`, arms2, dflt2, _: End)) if m.annotations.isEmpty => // TODO: also handle non-End rest (may require a join point) // * Currently, this branch does not seem used often (or at all?), // * because the UCS and (especially) MergeMatchArmTransformer already do a good job at merging matches Match(scrut, arms ::: arms2, dflt2, rest) @@ -480,8 +486,8 @@ object Match: case S(d) => S(if d.isAbortive then d else Begin(d, rest)) case N => S(rest) if numNonAbortive === 0 then - if rest.isEmpty then new Match(scrut, arms, mapDflt, rest) - else new Match(scrut, arms, mapDflt, End("(Unreachable:) rest of abortive match")) + if rest.isEmpty then new Match(scrut, arms, mapDflt, rest)(Nil) + else new Match(scrut, arms, mapDflt, End("(Unreachable:) rest of abortive match"))(Nil) else if numNonAbortive === 1 && dflt.exists(_.isAbortive) || rest.size <= 1 then new Match(scrut, arms.map: a => @@ -491,10 +497,10 @@ object Match: // * Indeed, `L: { match scrut { C => break L }; end }` can no longer be optimized // * if we replace `end` with `unreachable`, since the break is no longer jumping over nothing, // * ie no longer in tail position of the label (trying to treat it as such is unsound). - End("Rest moved to non-abortive branch(es)")) + End("Rest moved to non-abortive branch(es)"))(Nil) else rest match case Scoped(syms, body) => Scoped(syms, Match(scrut, arms, dflt, body)) - case _ => new Match(scrut, arms, dflt, rest) + case _ => new Match(scrut, arms, dflt, rest)(Nil) object Begin: def apply(sub: Block, rest: Block): Block = @@ -511,7 +517,8 @@ object Begin: "overlapping symbols when trying to merge Scoped blocks") Scoped(symsSub ++ symsRest, Begin(bodySub, bodyRest)) case _ => Scoped(symsSub, Begin(bodySub, rest)) - case Match(scrut, arms, dflt, rst) => Match(scrut, arms, dflt, Begin(rst, rest)) + case m @ Match(scrut, arms, dflt, rst) => + Match(scrut, arms, dflt, Begin(rst, rest))(m.annotations) case Label(lbl, loop, body, rst) => Label(lbl, loop, body, Begin(rst, rest)) case TryBlock(sub, fin, rst) => TryBlock(sub, fin, Begin(rst, rest)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, Begin(rst, rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 6c4dadafce..3763872f42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -937,7 +937,7 @@ class BlockSimplifier makeImpossibleAfter: super.applyBlock(b) - case Match(scrut, arms, dflt, rest) => + case m @ Match(scrut, arms, dflt, rest) => applyPath(scrut): scrut2 => @@ -1079,7 +1079,7 @@ class BlockSimplifier val restRewritten = applySubBlock(rest) if (scrut2 is scrut) && (newArms is arms) && (newDflt is dflt) && (restRewritten is rest) then b - else Match(scrut2, newArms, newDflt, restRewritten) + else Match(scrut2, newArms, newDflt, restRewritten)(m.annotations) case _ => super.applyBlock(b) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index b5c57cd61c..09e5a83981 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -46,7 +46,7 @@ class BlockTransformer(subst: SymbolSubst): case Throw(exc) => applyResult(exc): exc2 => if exc2 is exc then b else Throw(exc2) - case Match(scrut, arms, dflt, rst) => + case m @ Match(scrut, arms, dflt, rst) => def applySub(b: Block) = if rst.isEmpty then applySubBlock(b) else applySubBlockNonTail(b) applyPath(scrut): scrut2 => applyListOf( @@ -62,7 +62,7 @@ class BlockTransformer(subst: SymbolSubst): if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) - then b else Match(scrut2, arms2, dflt2, rst2) + then b else Match(scrut2, arms2, dflt2, rst2)(m.annotations) case Label(lbl, loop, bod, rst) => val lbl2 = lbl.subst val bod2 = if loop then applyScopedBlock(bod) else applySubBlock(bod) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala index 9870cfa2a0..d731ada88e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala @@ -62,6 +62,7 @@ class CompilationPipeline(using Config, Raise, State, Ctx, SymbolPrinter): else prog runPass("ClassParamFlattener")(ClassParamFlattener.apply) runPass("ReflectionInstrumenter")(ReflectionInstrumenter(using summon).apply) + runPass("DataRepFlattener")(DataRepFlattener.apply) preOptimizeHook(result) // * We run this pass here first, before inlining so that the @tailrec/@tailcall annotations diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala new file mode 100644 index 0000000000..db44e285e3 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -0,0 +1,388 @@ +package hkmc2 +package codegen + +import hkmc2.utils.*, shorthands.* +import utils.* +import Message.MessageContext + +import semantics.* +import flowAnalysis.* + +import hkmc2.semantics.Elaborator.State + +import scala.collection.mutable.{Set as MutSet, Map as MutMap} +import scala.collection.mutable.ListBuffer + +type Web = FlowWebComputation.Result[Ctor, ConcreteCtorConsumer] + +private object DataRepFlattenDebug: + def showCtor(ctor: CtorCls): Str = ctor match + case cls: ClassLikeSymbol => cls.nme + case size: Int => s"tup(size $size)" + + def showField(field: SelField): Str = field match + case sym: TermSymbol => sym.nme + case index: Int => index.toString + + def showProducer(producer: Ctor): Str = + s"${showCtor(producer.ctor)}@${producer.exprId}" + + def showFieldAccess(access: FieldSel): Str = + s"${{showCtor(access.selectsFrom)}}.${showField(access.field)}@${access.exprId}" + + def showPatternMatch(patternMatch: Dtor): Str = + s"match@${patternMatch.exprId}" + + def showConsumer(consumer: ConcreteCtorConsumer): Str = consumer match + case access: FieldSel => showFieldAccess(access) + case patternMatch: Dtor => showPatternMatch(patternMatch) + +class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: + private given fState: FlowAnalysis.State = flowRes.fState + private given eState: State = flowRes.eState + + private val entryPoints = ListBuffer.empty[ProducersCollector.EntryPoints] + private val concreteCtorsByResultId = MutMap.empty[ResultId, Ctor] + for ctor <- flowRes.ctorsWithDests do + concreteCtorsByResultId.addOne(ctor.exprId, ctor) + private val concreteConsumersByResultId = MutMap.empty[ResultId, ListBuffer[ConcreteCtorConsumer]] + for consumer <- flowRes.consumersWithSrcs do + concreteConsumersByResultId.getOrElseUpdate(consumer.exprId, ListBuffer.empty) += consumer + + private class AllocationCollector extends BlockTraverserShallow: + val allocations: ListBuffer[ResultId -> CtorCls] = ListBuffer.empty + val resultIds: ListBuffer[ResultId] = ListBuffer.empty + + override def applyResult(r: Result): Unit = + resultIds += r.uid + r match + case CtorProducer(ctor, _, _) => allocations += r.uid -> ctor + case _ => () + super.applyResult(r) + end AllocationCollector + + override def applyFunDefn(fun: FunDefn): Unit = + if fun.visibility is Visibility.Public then + val funName = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + val collector = new AllocationCollector() + collector.applyBlock(fun.body) + + val seenProducerEntryPoints = MutSet.empty[Ctor] + for + (allocationId, _) <- collector.allocations + ctor <- concreteCtorsByResultId.get(allocationId) + if !ctor.dests.contains(UnknownCons) + do seenProducerEntryPoints.add(ctor) + + if !seenProducerEntryPoints.isEmpty then + tl.log(s"track construction of ${seenProducerEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in $funName") + + val seenConsumerEntryPoints = MutSet.empty[ConcreteCtorConsumer] + for + resultId <- collector.resultIds + consumer <- concreteConsumersByResultId.getOrElse(resultId, Nil) + if !consumer.srcs.contains(UnknownProd) + if consumer.srcs.exists: + case _: Ctor => true + case _ => false + do seenConsumerEntryPoints.add(consumer) + + if !seenConsumerEntryPoints.isEmpty then + tl.log(s"track consumption at ${seenConsumerEntryPoints.map(DataRepFlattenDebug.showConsumer).mkString(", ")} in $funName") + + entryPoints += ProducersCollector.EntryPoints( + seenProducerEntryPoints.toList, + seenConsumerEntryPoints.toList, + ) + + override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = + defn.companion.foreach(applyCompanionModule) + + def result: (List[ProducersCollector.EntryPoints], Map[ResultId, Ctor]) = + (entryPoints.toList, concreteCtorsByResultId.toMap) + +object ProducersCollector: + case class EntryPoints( + producers: List[Ctor], + consumers: List[ConcreteCtorConsumer], + ) + + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[EntryPoints], Map[ResultId, Ctor]) = + val collector = new ProducersCollector(flowRes) + collector.applyProgram(p) + collector.result + + +private sealed abstract class Shape: + def show: Str + +private case class LitShape(lit: Value.Lit) extends Shape: + def show: Str = lit match + case Value.Lit(lit) => lit.idStr + +private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Shape]) extends Shape: + def show: Str = + if fields.isEmpty then DataRepFlattenDebug.showCtor(ctor) + else + val shownFields = fields.iterator + .map((field, shape) => s"${DataRepFlattenDebug.showField(field)}: ${shape.show}") + s"${DataRepFlattenDebug.showCtor(ctor)}${shownFields.mkString("(", ", ", ")")}" + +private case class TupleShape(length: Int, elements: Ls[Shape]) extends Shape: + require(elements.length === length) + def show: Str = + if elements.isEmpty then DataRepFlattenDebug.showCtor(length) + else s"${DataRepFlattenDebug.showCtor(length)}${elements.map(_.show).mkString("(", ", ", ")")}" + +private case class UnionShape(subshapes: List[Shape]) extends Shape: + def show: Str = subshapes.map(_.show).mkString("(", " | ", ")") + +private object DynamicShape extends Shape: + def show: Str = "_" + +class DataRepFlattener( + val webs: List[Web], + val concreteCtorsByResultId: Map[ResultId, Ctor], + val flowRes: FlowConstraintSolver, + val debug: Bool, +)(using State, TL, Raise) extends BlockTransformer(SymbolSubst.Id): + private given fState: FlowAnalysis.State = flowRes.fState + + private val producersInWeb = webs.iterator.flatMap(_.markedProducers).toSet + + private val shapeTags = MutMap.empty[Shape, Int] + + private val tagField = new syntax.Tree.Ident("__tag") + + private def getCtorArgs(producer: Ctor) = + producer.exprId.getResult match + case CtorProducer(_, args, _) => + softAssert( + args.size === producer.args.size, + s"Mismatched constructor arguments for ${DataRepFlattenDebug.showProducer(producer)}", + ) + args + case result => + softAssert( + false, + s"Missing constructor result for ${DataRepFlattenDebug.showProducer(producer)}: ${result.showDbg}", + ) + Nil + + private def shapeOfProducer(producer: Ctor): Shape = + val args = getCtorArgs(producer) + val fieldsOrElements = producer.args.zipWithIndex.map: + case ((field, value), index) => + val original = args.lift(index).map(_.value) + field -> shapeOf(value, original) + producer.ctor match + case cls: ClassLikeSymbol => + val fields = fieldsOrElements.collect: + case (field: TermSymbol, shape) => field -> shape + softAssert( + fields.size === fieldsOrElements.size, + s"Unexpected class fields in ${DataRepFlattenDebug.showProducer(producer)}", + ) + ClassShape(cls, fields.toMap) + case length: Int => + softAssert( + fieldsOrElements.size === length, + s"Mismatched tuple arity for ${DataRepFlattenDebug.showProducer(producer)}", + ) + TupleShape(length, fieldsOrElements.map(_._2)) + + private def shapeOf(producer: ProdStrat, original: Opt[Path]): Shape = + original match + case S(lit: Value.Lit) => LitShape(lit) + case _ => producer match + case ctor: Ctor => shapeOfProducer(ctor) + case variable: StratVar => + DataRepFlattener.mkUnion: + variable.lowerBounds.map: lowerBound => + shapeOf(lowerBound, N) + case _ => DynamicShape + + private def containsUnion(shape: Shape): Bool = shape match + case ClassShape(_, fields) => fields.valuesIterator.exists(containsUnion) + case TupleShape(_, elements) => elements.exists(containsUnion) + case _: UnionShape => true + case _ => false + + private def allocateShape(fun: FunDefn, producer: Ctor) = + val shape = shapeOfProducer(producer) + shape match + case shape: ClassShape if !containsUnion(shape) => + val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) + if debug then + val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " + + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", + ) + S(tag) + case _ => N + + private def insertTag(result: Result, tag: Int)(k: Path => Block): Block = + val instance = new TempSymbol(N, "tmp") + val instanceRef = instance.asSimpleRef.withLocOf(result) + Scoped(Set.single(instance), Assign( + instance, result, AssignField( + instanceRef, tagField, Value.Lit(syntax.Tree.IntLit(tag)), k(instanceRef), + )(N))) + + override def applyProgram(program: Program): Program = + if debug then + summon[TL].emitDbg(">>> start data-rep-flatten transform-phase") + val result = super.applyProgram(program) + if debug then + summon[TL].emitDbg("<<< end data-rep-flatten transform-phase") + result + + override def applyFunDefn(fun: FunDefn): FunDefn = + val transformer = new BlockTransformerShallow(SymbolSubst.Id): + override def applyResult(result: Result)(k: Result => Block): Block = + result match + case CtorProducer(_, _, _) => + concreteCtorsByResultId.get(result.uid).filter(producersInWeb) match + case S(ctor) => + super.applyResult(result): transformed => + allocateShape(fun, ctor) match + case S(tag) => insertTag(transformed, tag)(k) + case N => k(transformed) + case N => super.applyResult(result)(k) + case _ => super.applyResult(result)(k) + val body = transformer.applyFunBodyLikeBlock(fun.body) + val transformed = + if body is fun.body then fun + else FunDefn(fun.owner, fun.sym, fun.dSym, fun.params, body)(fun.configOverride, fun.annotations) + super.applyFunDefn(transformed) +end DataRepFlattener + + +object DataRepFlattener: + private def mkUnion(shapes: Iterable[Shape]): Shape = + val flattened = shapes.iterator.flatMap: + case UnionShape(subshapes) if subshapes.nonEmpty => subshapes + case shape => shape :: Nil + val normalized = flattened.toList.distinct.sortBy(_.show) + normalized match + case Nil => DynamicShape + case shape :: Nil => shape + case shapes => UnionShape(shapes) + + private def mkShapeByPattern(pattern: Pattern)(using raise: Raise): Shape = + pattern match + case ctorPattern @ Pattern.Constructor(_, arguments) => + ctorPattern.symbol.flatMap(_.asClsLike) match + case S(cls: ClassSymbol) => + cls.tree.clsParams match + case fields :: Nil => + val argumentShapes = arguments match + case S(patterns) => patterns.map(mkShapeByPattern) + case N => Nil + softAssert( + argumentShapes.size === fields.size, + s"Mismatched arity for class pattern $pattern.", + ) + ClassShape(cls, fields.zip(argumentShapes).toMap) + case _ => + raise(ErrorReport( + msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + case S(obj: ModuleOrObjectSymbol) => + ClassShape(obj, Map.empty) + case _ => DynamicShape + case Pattern.Tuple(leading, N) => + TupleShape(leading.size, leading.map(mkShapeByPattern)) + case Pattern.Literal(literal) => LitShape(Value.Lit(literal)) + case Pattern.Wildcard() => DynamicShape + case _ => + raise(ErrorReport( + msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + + private def mkWeb(entries: ProducersCollector.EntryPoints): Web = + FlowWebComputation[Ctor, ConcreteCtorConsumer]( + producer => producer.dests.collect: + case consumer: ConcreteCtorConsumer => consumer, + consumer => consumer.srcs.collect: + case producer: Ctor => producer, + entries.producers, + entries.consumers, + ) + + private def mkWebs(entryPoints: List[ProducersCollector.EntryPoints]) = + val coveredProducers = MutSet.empty[Ctor] + val coveredConsumers = MutSet.empty[ConcreteCtorConsumer] + val webs = ListBuffer.empty[Web] + for entries <- entryPoints do + if + (entries.producers.nonEmpty || entries.consumers.nonEmpty) + && !entries.producers.exists(coveredProducers) + && !entries.consumers.exists(coveredConsumers) + then + val web = mkWeb(entries) + coveredProducers ++= web.markedProducers + coveredConsumers ++= web.markedConsumers + webs += web + webs.toList + + private def logWebs(webs: List[Web])(using tl: TL): Unit = + if webs.nonEmpty then + tl.emitDbg(">>> start data-rep-flatten web-computation-phase") + for (web, index) <- webs.zipWithIndex do + val producers = web.markedProducers.toList.sortBy(_.exprId.uid) + val fieldAccesses = web.markedConsumers.collect: + case access: FieldSel => access + val patternMatches = web.markedConsumers.collect: + case patternMatch: Dtor => patternMatch + tl.emitDbg(s"data-rep-flatten web-computation-phase > web $index:") + tl.emitDbg(s"data-rep-flatten web-computation-phase > producers: ${producers.map(DataRepFlattenDebug.showProducer).mkString(", ")}") + if fieldAccesses.nonEmpty then + tl.emitDbg(s"data-rep-flatten web-computation-phase > field accesses: ${fieldAccesses.toList.sortBy(_.exprId.uid).map(DataRepFlattenDebug.showFieldAccess).mkString(", ")}") + if patternMatches.nonEmpty then + tl.emitDbg(s"data-rep-flatten web-computation-phase > pattern matches: ${patternMatches.toList.sortBy(_.exprId.uid).map(DataRepFlattenDebug.showPatternMatch).mkString(", ")}") + tl.emitDbg("<<< end data-rep-flatten web-computation-phase") + + def apply(p: Program)(using + cfg: Config, + tl: TL, + raise: Raise, + eState: State, + symbolPrinter: SymbolPrinter, + ): Program = + cfg.dataRepFlatten match + case N => p + case S(dCfg) => + val flowCfg = Config.FlowAnalysisConfig( + debug = false, + mono = dCfg.mono, + trackNonAffine = false, + trackAccumulator = false, + logNonAffine = false, + logAccumulator = false, + ) + val flowAnalysisRes = + FlowAnalysis.mkTraceLogger(flowCfg, "data-rep-flatten flow-analysis-phase > ", tl).givenIn: + FlowAnalysis( + p, + mono = flowCfg.mono, + nonAffineTracking = false, + accumulatorTracking = false, + ) + val collectorTl = new TraceLogger(using tl.debugPrinter): + override def doTrace: Bool = dCfg.debug + override def emitDbg(str: Str): Unit = + tl.emitDbg(s"data-rep-flatten collection-phase > $str") + val (entryPoints, concreteCtorsByResultId) = collectorTl.givenIn: + if dCfg.debug then tl.emitDbg(">>> start data-rep-flatten collection-phase") + val result = ProducersCollector(p, flowAnalysisRes) + if dCfg.debug then tl.emitDbg("<<< end data-rep-flatten collection-phase") + result + val webs = mkWebs(entryPoints) + if dCfg.debug then logWebs(webs) + new DataRepFlattener(webs, concreteCtorsByResultId, flowAnalysisRes, dCfg.debug).applyProgram(p) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index dde820df53..e9b80b2935 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -245,11 +245,11 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R blk match - case Match(scrut, arms, dflt, rest) => + case m @ Match(scrut, arms, dflt, rest) => val restId = RestLazyId(rest) val newArms = arms.map((cse, blkk) => (cse, go(blkk)(using afterEnd = S(restId)))) val newDflt = dflt.map(blkk => go(blkk)(using afterEnd = S(restId))) - Match(scrut, newArms, newDflt, restId.transitionSoft) + Match(scrut, newArms, newDflt, restId.transitionSoft)(m.annotations) case Label(label, loop, body, rest) => val restId = RestLazyId(rest) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 28780e5911..4fdeb05bbd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -482,7 +482,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // We store already-created closures in a set in the BlockRewriter class. // This set needs to be reset after processing an if-else branch or while loop, // since closures nested inside each branch may not be re-used elsewhere. - case Match(scrut, arms, dflt, rst) => + case m @ Match(scrut, arms, dflt, rst) => applyPath(scrut): scrut2 => applyListOf( arms, @@ -497,7 +497,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) - then rewritten else Match(scrut2, arms2, dflt2, rst2) + then rewritten else Match(scrut2, arms2, dflt2, rst2)(m.annotations) case Label(lbl, false, bod, rst) => val lbl2 = lbl.subst diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 90f9200e7f..acee4c6fb0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1015,7 +1015,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): k(lamDef.asPath)) - case iftrm: st.IfLike => ucs.Normalization(this)(iftrm)(k) + case iftrm: st.IfLike => ucs.Normalization(this)(iftrm, annots)(k) case iftrm: st.SynthIf => ucs.Normalization(this)(iftrm)(k) @@ -1468,6 +1468,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): annotations.foreach: case Annot.Untyped => () + case Annot.MatchShapes(_) if receiver.isInstanceOf[st.IfLike] => () case annot: Annot.Trm => receiver match case st.App(Ref(_: BuiltinSymbol), _) => warn(annot) case st.App(_, _) | New(_, _, _) | DynNew(_, _) | Mut(_: New | _: DynNew) => () @@ -1588,8 +1589,9 @@ object MergeMatchArmTransformer extends BlockTransformer(SymbolSubst.Id): override def applyBlock(b: Block): Block = super.applyBlock(b) match case m @ Match(scrut, arms, Some(dflt), rest) => dflt match - case TrivialStatementsAndMatch(k, Match(scrutRewritten, armsRewritten, dfltRewritten, restRewritten)) - if (scrutRewritten === scrut) && (restRewritten.size * armsRewritten.length) < 10 => + case TrivialStatementsAndMatch(k, inner @ Match(scrutRewritten, armsRewritten, dfltRewritten, restRewritten)) + if inner.annotations.isEmpty + && (scrutRewritten === scrut) && (restRewritten.size * armsRewritten.length) < 10 => val newArms = restRewritten match case _: End => armsRewritten case _ => armsRewritten.map: @@ -1597,6 +1599,6 @@ object MergeMatchArmTransformer extends BlockTransformer(SymbolSubst.Id): cse -> Begin(body, restRewritten) k.getOrElse(identity[Block]): Match(scrut, arms ::: newArms, - dfltRewritten.fold(restRewritten)(Begin(_, restRewritten)) |> some, rest) + dfltRewritten.fold(restRewritten)(Begin(_, restRewritten)) |> some, rest)(m.annotations) case _ => m case b => b diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d10aa79d29..f784060e4c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -297,6 +297,7 @@ object Elaborator: val buffered = assumeObject("buffered") val bufferable = assumeObject("bufferable") val mayNotRaiseEffects = assumeObject("mayNotRaiseEffects") + val matchShapes = assumeObject("matchShapes") object handlers extends VirtualModule(assumeBuiltinMod("handlers")): val await = assumeObject("await").asTrm.get object scope extends VirtualModule(assumeBuiltinMod("scope")): @@ -604,6 +605,8 @@ extends Importer: case App(Ident("config"), Tup(args)) => val modify = ConfigParser.parseOverrides(args) S(Annot.Config(modify)) + case App(Ident("matchShapes"), Tup(patterns)) => + S(Annot.MatchShapes(patterns.map(pattern))) case _ => term(tree) match case Term.Error() => N case trm => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 5ebd0b8d26..410f683afc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -33,6 +33,7 @@ enum Annot extends AutoLocated: case RaiseEffects // Whether the function is guaranteed to not raise effects. case MayNotRaiseEffects + case MatchShapes(patterns: Ls[Pattern]) case Config(modify: hkmc2.Config => hkmc2.Config) // Marks if a function or lambda is one-shot, i.e. called at most once. // Functions with multiple parameter lists are considered here as a chain of @@ -52,11 +53,13 @@ enum Annot extends AutoLocated: def subTerms: Vector[Term] = this match case Trm(trm) => Vector.single(trm) + case MatchShapes(patterns) => patterns.iterator.flatMap(_.subTerms).toVector case _: Modifier | Untyped | TailRec | TailCall | Inline | NoInline | Generator | Async | RaiseEffects | MayNotRaiseEffects | _: Config | _: Affine => Vector.empty def children: Vector[Located] = this match case Trm(trm) => Vector.single(trm) + case MatchShapes(patterns) => patterns.toVector // case Modifier(kw) => Vector.single(kw) // TODO: make `kw` a `Keywrd` case _: Modifier | Untyped | TailRec | TailCall | Inline | NoInline | Generator | Async | RaiseEffects | MayNotRaiseEffects | _: Config | _: Affine => Vector.empty @@ -73,6 +76,7 @@ enum Annot extends AutoLocated: case Affine(n) => doc"@affine($n)" case Modifier(mod) => doc"@${mod.name}" case MayNotRaiseEffects => doc"@mayNotRaiseEffects" + case MatchShapes(_) => doc"@matchShapes" case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" @@ -88,6 +92,7 @@ enum Annot extends AutoLocated: case Async => Async case RaiseEffects => RaiseEffects case MayNotRaiseEffects => MayNotRaiseEffects + case a: MatchShapes => a case c: Config => c case a: Affine => a diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index e3312450fe..2ef5626709 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -317,7 +317,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C import codegen.*, lowering.{term_nonTail, subTerm_nonTail, unreachableFn} private def lowerSplit - (split: Split, cont: Result => Block) + (split: Split, cont: Result => Block, topLevelAnnotations: Ls[Annot]) (using form: IfLikeForm) (using LoweringCtx) : Block = @@ -325,16 +325,16 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C case Split.Let(sym, trm, tl) => LoweringCtx.loweringCtx.collectScopedSym(sym) term_nonTail(trm): r => - Assign(sym, r, lowerSplit(tl, cont)) + Assign(sym, r, lowerSplit(tl, cont, topLevelAnnotations)) case Split.Cons(Branch(scrut, pat, tail), restSplit) => subTerm_nonTail(scrut): sr => tl.log(s"Binding scrut $scrut to $sr (${summon[LoweringCtx].map})") - def mkMatch(cse: Case -> Block) = Match(sr, cse :: Nil, - S(lowerSplit(restSplit, cont)), + def mkMatch(cse: Case -> Block, matchAnnotations: Ls[Annot]) = Match(sr, cse :: Nil, + S(lowerSplit(restSplit, cont, Nil)), End() - ) + )(matchAnnotations) pat match - case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont)) + case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont, Nil), topLevelAnnotations) case FlatPattern.ClassLike(ctor, symbol, argsOpt, _refined) => for args <- argsOpt; (arg, _) <- args do LoweringCtx.loweringCtx.collectScopedSym(arg) /** Make a continuation that creates the match. */ @@ -345,11 +345,11 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C assert(argsOpt.isEmpty || args.length <= clsParams.length, (argsOpt, clsParams)) def mkArgs(args: Ls[TermSymbol -> LocalVarSymbol])(using LoweringCtx): Case -> Block = args match case Nil => - Case.Cls(ctorSym, st) -> lowerSplit(tail, cont) + Case.Cls(ctorSym, st) -> lowerSplit(tail, cont, Nil) case (param, arg) :: args => val (cse, blk) = mkArgs(args) (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param))(false), blk)) - mkMatch(mkArgs(clsParams.iterator.zip(args).toList)) + mkMatch(mkArgs(clsParams.iterator.zip(args).toList), topLevelAnnotations) symbol match case cls: ClassSymbol if ctx.builtins.virtualClasses contains cls => // [invariant:0] Some classes (e.g., `Int`) from `Prelude` do @@ -365,18 +365,19 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C )) case mod: ModuleOrObjectSymbol => subTerm_nonTail(ctor)(k(mod, Nil)) - case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont)) + case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont, Nil), topLevelAnnotations) case FlatPattern.Record(entries) => for (_, s) <- entries do LoweringCtx.loweringCtx.collectScopedSym(s) val objectSym = ctx.builtins.Object mkMatch( // checking that we have an object - Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)), - entries.foldRight(lowerSplit(tail, cont)): + Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)) -> + entries.foldRight(lowerSplit(tail, cont, Nil)): case ((fieldName, fieldSymbol), blk) => mkMatch( - Case.Field(fieldName, safe = true), // we know we have an object, no need to check again - Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk) - ) + Case.Field(fieldName, safe = true) -> // we know we have an object, no need to check again + Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk), + Nil), + topLevelAnnotations ) case Split.Else(els) => term_nonTail(els, inStmtPos = form.isImperative)(cont) @@ -395,8 +396,8 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C if transfersControl then // Ret/Thrw emit `return`/`throw`, which transfer control out of the block // unconditionally; passing them through preserves tail-call position. - val bodyBlock = lowerSplit(sym.body, cont) - Label(joinLabel, false, lowerSplit(tail, cont), bodyBlock) + val bodyBlock = lowerSplit(sym.body, cont, Nil) + Label(joinLabel, false, lowerSplit(tail, cont, topLevelAnnotations), bodyBlock) else // Other continuations (including ImplctRet, which generates `expr;` without `return`) can fall through // the Label body into the rest. Wrap with an exit label and temp variable so every path stores its @@ -405,13 +406,13 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C val tmp = new TempSymbol(N) LoweringCtx.loweringCtx.collectScopedSym(tmp) val exitCont: Result => Block = r => Assign(tmp, r, Break(exitLabel)) - val bodyBlock = lowerSplit(sym.body, exitCont) - val tailBlock = lowerSplit(tail, exitCont) + val bodyBlock = lowerSplit(sym.body, exitCont, Nil) + val tailBlock = lowerSplit(tail, exitCont, topLevelAnnotations) Label(exitLabel, false, Label(joinLabel, false, tailBlock, bodyBlock), cont(tmp.asSimpleRef)) case Split.UseSplit(sym) => sym.label match case S(label) => Break(label) - case N => lowerSplit(sym.body, cont) // fallback: inline if no label + case N => lowerSplit(sym.body, cont, topLevelAnnotations) // fallback: inline if no label /** * Make a block that throws the match error. We might add the information of @@ -423,18 +424,18 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C import syntax.Keyword.{`if`, `while`} - def apply(t: Term.IfLike)(k: Result => Block)(using config: Config)(using LoweringCtx): Block = + def apply(t: Term.IfLike, annotations: Ls[Annot])(k: Result => Block)(using config: Config)(using LoweringCtx): Block = val newSplit = t.split.getExpandedSplit scoped("ucs:desugared"): log(s"Split with nested patterns:\n${t.split.prettyPrint(t.kw)}") log(s"Expanded split with flattened patterns:\n${newSplit.prettyPrint}") - this(newSplit, t.form, S(t), k) + this(newSplit, t.form, S(t), annotations, k) def apply(t: Term.SynthIf)(k: Result => Block)(using Config, LoweringCtx): Block = - this(t.split, IfLikeForm.ReturningIf, S(t), k) + this(t.split, IfLikeForm.ReturningIf, S(t), Nil, k) def apply(split: Split)(k: Result => Block)(using Config, LoweringCtx): Block = - this(split, IfLikeForm.ReturningIf, N, k) + this(split, IfLikeForm.ReturningIf, N, Nil, k) /** Lower a synthesized `while` loop: branch consequents are evaluated for * their effects and the loop is re-entered; the loop exits when no branch @@ -442,9 +443,9 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C * are created by `ups.FixedPointCompiler` to drive the generated matcher * machine. */ def apply(t: Term.SynthWhile)(k: Result => Block)(using Config, LoweringCtx): Block = - this(t.split, IfLikeForm.While, N, k) + this(t.split, IfLikeForm.While, N, Nil, k) - private def apply(inputSplit: Split, form: IfLikeForm, t: Opt[Term], k: Result => Block)(using cfg: Config, outerCtx: LoweringCtx) = + private def apply(inputSplit: Split, form: IfLikeForm, t: Opt[Term], annotations: Ls[Annot], k: Result => Block)(using cfg: Config, outerCtx: LoweringCtx) = // if it's `while`, we always make sure that loop bodies are proper nested scoped // see https://github.com/hkust-taco/mlscript/pull/356#discussion_r2588412258 val useNestedScoped = form is IfLikeForm.While @@ -495,7 +496,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C else assignResult val mainBlock = given IfLikeForm = form - lowerSplit(normalized, cont) + lowerSplit(normalized, cont, annotations) val body = Scoped( if useNestedScoped then LoweringCtx.loweringCtx.getCollectedSym else Set.empty, diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index d19a2e192f..6b196bf647 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -631,7 +631,7 @@ abstract class Parser( consume val a = annot(new Ident(id).withLoc(S(l0 ++ l1))) exprCont( - Annotated(a, simpleExpr(AnnotBodyPrec, allowNewlines = allowNewlines)), + Annotated(a, expr(AnnotBodyPrec, allowNewlines = allowNewlines)), prec, allowNewlines = allowNewlines) case (ESC_IDENT(name), loc) :: _ => consume diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls new file mode 100644 index 0000000000..73c5fa60e2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls @@ -0,0 +1,26 @@ +class C(val x) +class D(val x) +class E(val x) +class F(val x) + +fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) if x is + C(D(y)) then y + 1 + C(E(y)) then y - 1 + F(y) then y + + +:w +@matchShapes(C(_)) 1 +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.13: @matchShapes(C(_)) 1 +//│ ║ ^^^ +//│ ╟── This annotation is not supported on integer literal terms. +//│ ║ l.13: @matchShapes(C(_)) 1 +//│ ╙── ^ + + +:w +@matchShapes(C(_)) fun foo(x) = x +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.23: @matchShapes(C(_)) fun foo(x) = x +//│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls new file mode 100644 index 0000000000..146f9a4c68 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -0,0 +1,337 @@ +:dataRepFlatten debug mono +:js +:noFreeze + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +class Foo(val x, val y) +class Bar(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +fun foo(x, y) = new Foo(x, y) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +fun foo(x, y) = + let f = new Foo(x, y) + @matchShapes(Foo(_, _)) + if f is + Foo(x, y) then new Bar(x + y) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@3 in foo +//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@3 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@3 in foo +//│ <<< end data-rep-flatten transform-phase + + +private fun bar(x) = new Bar(x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +module Baz with + fun bar(x) = new Bar(x) + private fun barr(x) = + let f = new Foo(x, x) + if f is + Foo(x, y) then new Bar(x + y) + fun baz(x) = + let f = new Foo(x, x) + @matchShapes(Foo(_, _)) + if f is + Foo(x, y) then new Bar(x + y) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@10 in Baz.baz +//│ data-rep-flatten collection-phase > track consumption at Foo.x@3, Foo.y@4 in Baz.baz +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@10 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@3, Foo.y@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@5 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@10 in Baz.baz +//│ <<< end data-rep-flatten transform-phase + + +fun foo(x, y) = + let f = new Foo(x, y) + bar(f) * baz(f) +private fun bar(f) = + @matchShapes(Foo(_, _)) + if f is + Foo(x, y) then x + y +private fun baz(f) = + @matchShapes(Foo(_, _)) + if f is + Foo(x, y) then x - y +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@6 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@6 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@3, Foo.y@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2, match@5 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@6 in foo +//│ <<< end data-rep-flatten transform-phase + + +:ssjs +fun foo(x, y) = + let f = new Foo(x, y) + baz(f) +fun bar(x) = + let b = new Bar(x) + baz(b) +private fun baz(t) = + @matchShapes(Foo(_, _), Bar(_)) + if t is + Foo(x, y) then x + y + Bar(x) then x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@4 in foo +//│ data-rep-flatten collection-phase > track construction of Bar@6 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@4, Bar@6 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@3 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@4 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) at Bar@6 in bar +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar2, foo3, baz1; +//│ foo3 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp, inlinedVal, arg$Foo$0$, arg$Foo$1$; +//│ tmp = (new Foo1.class(x, y)); +//│ tmp.__tag = 0; +//│ if (tmp instanceof Foo1.class) { +//│ arg$Foo$0$ = tmp.x; +//│ arg$Foo$1$ = tmp.y; +//│ inlinedVal = arg$Foo$0$ + arg$Foo$1$; +//│ } else if (tmp instanceof Bar1.class) { +//│ inlinedVal = tmp.x; +//│ } else { +//│ throw (new globalThis.Error("match error")) +//│ } +//│ return inlinedVal +//│ }; +//│ bar2 = function bar(x) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let tmp, inlinedVal, arg$Foo$0$, arg$Foo$1$; +//│ tmp = (new Bar1.class(x)); +//│ tmp.__tag = 1; +//│ if (tmp instanceof Foo1.class) { +//│ arg$Foo$0$ = tmp.x; +//│ arg$Foo$1$ = tmp.y; +//│ inlinedVal = arg$Foo$0$ + arg$Foo$1$; +//│ } else if (tmp instanceof Bar1.class) { +//│ inlinedVal = tmp.x; +//│ } else { +//│ throw (new globalThis.Error("match error")) +//│ } +//│ return inlinedVal +//│ }; +//│ baz1 = function baz(t) { +//│ runtime.checkArgs("baz", 1, true, arguments.length); +//│ let arg$Foo$0$, arg$Foo$1$; +//│ if (t instanceof Foo1.class) { +//│ arg$Foo$0$ = t.x; +//│ arg$Foo$1$ = t.y; +//│ return arg$Foo$0$ + arg$Foo$1$ +//│ } else if (t instanceof Bar1.class) { return t.x } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +class Some(val x) +object None +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +:ssjs +private fun foo(x) = + if x > 0 then new Some(x) else None +fun bar(x) = + @matchShapes(Some(_), None) + if foo(x) is + Some(y) then y + None then 0 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Some@4, None@5 +//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar3, foo4; +//│ foo4 = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ let scrut; +//│ scrut = x > 0; +//│ if (scrut === true) { +//│ let tmp; +//│ tmp = (new Some1.class(x)); +//│ tmp.__tag = 0; +//│ return tmp +//│ } +//│ None1.__tag = 1; +//│ return None1; +//│ }; +//│ bar3 = function bar(x) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let scrut; +//│ scrut = x > 0; +//│ if (scrut === true) { +//│ let tmp; +//│ tmp = (new Some1.class(x)); +//│ tmp.__tag = 0; +//│ if (tmp instanceof Some1.class) { +//│ return tmp.x +//│ } else if (tmp instanceof None1.class) { +//│ return 0 +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ None1.__tag = 1; +//│ return 0; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +fun bar(x) = + fun foo(x) = + if x > 0 then new Some(x) else None + @matchShapes(Some(_), None) + if foo(x) is + Some(y) then y + else 0 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Some@4, None@5 +//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo +//│ <<< end data-rep-flatten transform-phase + + + +:ssjs +fun callCtor(x, y) = + let f = Foo(x, y) + @matchShapes(Foo(_, _)) + if f is + Foo(a, b) then a + b +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@3 in callCtor +//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in callCtor +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@3 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@3 in callCtor +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let callCtor; +//│ callCtor = function callCtor(x, y) { +//│ runtime.checkArgs("callCtor", 2, true, arguments.length); +//│ let arg$Foo$0$, arg$Foo$1$, tmp; +//│ tmp = runtime.checkCall(Foo1(x, y)); +//│ tmp.__tag = 0; +//│ if (tmp instanceof Foo1.class) { +//│ arg$Foo$0$ = tmp.x; +//│ arg$Foo$1$ = tmp.y; +//│ return arg$Foo$0$ + arg$Foo$1$ +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun foo(x, y) = + let f = [1, 2, 3] + if f is + [x, y, z] then new Bar(x + y + z) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of tup(size 3)@4 in foo +//│ data-rep-flatten collection-phase > track consumption at tup(size 3).2@2, tup(size 3).1@1, tup(size 3).0@0 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: tup(size 3)@4 +//│ data-rep-flatten web-computation-phase > field accesses: tup(size 3).0@0, tup(size 3).1@1, tup(size 3).2@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@3 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo5; +//│ foo5 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let f, element2$, element1$, element0$, tmp, tmp1; +//│ f = ([ +//│ 1, +//│ 2, +//│ 3 +//│ ]); +//│ if (runtime.Tuple.isArrayLike(f) && f.length === 3) { +//│ element0$ = runtime.checkCall(runtime.Tuple.get(f, 0)); +//│ element1$ = runtime.checkCall(runtime.Tuple.get(f, 1)); +//│ element2$ = runtime.checkCall(runtime.Tuple.get(f, 2)); +//│ tmp = element0$ + element1$; +//│ tmp1 = tmp + element2$; +//│ return (new Bar1.class(tmp1)) +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls new file mode 100644 index 0000000000..d0a21f5cfc --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -0,0 +1,180 @@ +:dataRepFlatten debug mono +:js +:noFreeze + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +class Cons(val x, val xs) +object Nil +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +:ssjs +fun foo(x, y, z) = + let ls = new Cons(1, new Cons(2, new Cons(3, new Cons(x, new Cons(y, new Cons(z, Nil)))))) + bar(ls) +private fun bar(ls) = + @matchShapes(Cons(1, Cons(2, Cons(3, Cons(_, Cons(_, Cons(_, Nil))))))) + if ls is + Cons(1, Cons(2, Cons(3, Cons(x, Cons(y, Cons(z, Nil)))))) then x + y + z +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Cons@27, Cons@28, Cons@25, Cons@26, Cons@23, Cons@24 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Cons@23, Cons@24, Cons@25, Cons@26, Cons@27, Cons@28 +//│ data-rep-flatten web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.x@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.x@6, Cons.xs@7, Cons.x@8, Cons.xs@9, Cons.x@10, Cons.xs@11 +//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@14, match@15, match@17, match@19, match@21 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: _, xs: Nil) at Cons@23 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Cons(x: _, xs: Nil)) at Cons@24 in foo +//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) at Cons@25 in foo +//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) at Cons@26 in foo +//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) at Cons@27 in foo +//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) at Cons@28 in foo +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar, foo; +//│ foo = function foo(x, y, z) { +//│ runtime.checkArgs("foo", 3, true, arguments.length); +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ tmp = (new Cons1.class(z, Nil1)); +//│ tmp.__tag = 0; +//│ tmp1 = (new Cons1.class(y, tmp)); +//│ tmp1.__tag = 1; +//│ tmp2 = (new Cons1.class(x, tmp1)); +//│ tmp2.__tag = 2; +//│ tmp3 = (new Cons1.class(3, tmp2)); +//│ tmp3.__tag = 3; +//│ tmp4 = (new Cons1.class(2, tmp3)); +//│ tmp4.__tag = 4; +//│ tmp5 = (new Cons1.class(1, tmp4)); +//│ tmp5.__tag = 5; +//│ return runtime.checkCall(bar(tmp5)) +//│ }; +//│ bar = function bar(ls) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let arg$Cons$0$, arg$Cons$1$, arg$Cons$0$1, arg$Cons$1$1, arg$Cons$0$2, arg$Cons$1$2, arg$Cons$0$3, arg$Cons$1$3, arg$Cons$0$4, arg$Cons$1$4, arg$Cons$0$5, arg$Cons$1$5, tmp; +//│ if (ls instanceof Cons1.class) { +//│ arg$Cons$0$ = ls.x; +//│ arg$Cons$1$ = ls.xs; +//│ if (arg$Cons$0$ === 1) { +//│ if (arg$Cons$1$ instanceof Cons1.class) { +//│ arg$Cons$0$1 = arg$Cons$1$.x; +//│ arg$Cons$1$1 = arg$Cons$1$.xs; +//│ if (arg$Cons$0$1 === 2) { +//│ if (arg$Cons$1$1 instanceof Cons1.class) { +//│ arg$Cons$0$2 = arg$Cons$1$1.x; +//│ arg$Cons$1$2 = arg$Cons$1$1.xs; +//│ if (arg$Cons$0$2 === 3) { +//│ if (arg$Cons$1$2 instanceof Cons1.class) { +//│ arg$Cons$0$3 = arg$Cons$1$2.x; +//│ arg$Cons$1$3 = arg$Cons$1$2.xs; +//│ if (arg$Cons$1$3 instanceof Cons1.class) { +//│ arg$Cons$0$4 = arg$Cons$1$3.x; +//│ arg$Cons$1$4 = arg$Cons$1$3.xs; +//│ if (arg$Cons$1$4 instanceof Cons1.class) { +//│ arg$Cons$0$5 = arg$Cons$1$4.x; +//│ arg$Cons$1$5 = arg$Cons$1$4.xs; +//│ if (arg$Cons$1$5 instanceof Nil1.class) { +//│ tmp = arg$Cons$0$3 + arg$Cons$0$4; +//│ return tmp + arg$Cons$0$5 +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +class C(val x) +class D(val x) +class E(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +:ssjs +fun foo(x, y) = + let t = new C(if y then new D(x) else new E(1)) + foofoo(t) +private fun foofoo(t) = + @matchShapes(C(D(_)), C(E(1))) + if t is + C(D(x)) then x + 1 + C(E(1)) then 0 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of D@7, E@8, C@9 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: D@7, E@8, C@9 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@1, D.x@2, E.x@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@3, match@6 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) at D@7 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) at E@8 in foo +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo1, foofoo; +//│ foo1 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let t, tmp; +//│ if (y === true) { +//│ let tmp1; +//│ tmp1 = (new D1.class(x)); +//│ tmp1.__tag = 0; +//│ tmp = tmp1; +//│ } else { +//│ let tmp1; +//│ tmp1 = (new E1.class(1)); +//│ tmp1.__tag = 1; +//│ tmp = tmp1; +//│ } +//│ t = (new C1.class(tmp)); +//│ return runtime.checkCall(foofoo(t)) +//│ }; +//│ foofoo = function foofoo(t) { +//│ runtime.checkArgs("foofoo", 1, true, arguments.length); +//│ let arg$C$0$, arg$E$0$, arg$D$0$; +//│ if (t instanceof C1.class) { +//│ arg$C$0$ = t.x; +//│ if (arg$C$0$ instanceof D1.class) { +//│ arg$D$0$ = arg$C$0$.x; +//│ return arg$D$0$ + 1 +//│ } else if (arg$C$0$ instanceof E1.class) { +//│ arg$E$0$ = arg$C$0$.x; +//│ if (arg$E$0$ === 1) { +//│ return 0 +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 544c614cb2..acb508c648 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -275,6 +275,7 @@ declare module annotations with object buffered object bufferable object mayNotRaiseEffects + object matchShapes object generator object async diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls index 2bf32486be..a2e92c52e0 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls @@ -90,6 +90,7 @@ declare module annotations with object buffered object bufferable object mayNotRaiseEffects + object matchShapes object generator object async diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 7a4bdd5aee..a71ad487de 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -16,74 +16,63 @@ fun foo(x) = if x is (@annotations.compile A(0)) as y then y // if 1 is (@annotations.compile A(0)) as y then y :pe -:w :e fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╔══[PARSE ERROR] Unexpected keyword 'then' in this position -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'then' keyword instead +//│ ║ l.20: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╔══[COMPILATION ERROR] Unrecognized pattern split (juxtaposition). +//│ ║ l.20: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^ -//│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Note: Patterns only support the `@compile` annotation. :e fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.40: fun foo(x) = if x is @annotations.compile {A(0) as y} then y +//│ ║ l.29: fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ :e fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.78: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y +//│ ║ l.67: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╙── ^ :fixme fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.84: fun foo(x) = if x is (@annotations.compile A(0) as y) then y +//│ ║ l.73: fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╙── ^ @@ -94,40 +83,40 @@ type Test :w @Test 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.95: @Test 1 +//│ ║ l.84: @Test 1 //│ ║ ^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.95: @Test 1 +//│ ║ l.84: @Test 1 //│ ╙── ^ //│ = 1 :w @Test 2 + 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.105: @Test 2 + 1 -//│ ║ ^^^^^ +//│ ║ l.94: @Test 2 + 1 +//│ ║ ^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.105: @Test 2 + 1 -//│ ╙── ^^^^^ +//│ ║ l.94: @Test 2 + 1 +//│ ╙── ^^^^^ //│ = 3 :w @Test 2 as Int //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.115: @Test 2 as Int +//│ ║ l.104: @Test 2 as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.115: @Test 2 as Int +//│ ║ l.104: @Test 2 as Int //│ ╙── ^^^^^^^^ //│ = 2 :w @Test id(2) as Int //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.125: @Test id(2) as Int +//│ ║ l.114: @Test id(2) as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.125: @Test id(2) as Int +//│ ║ l.114: @Test id(2) as Int //│ ╙── ^^^^^^^^^^^^ //│ = 2 @@ -135,10 +124,10 @@ type Test :re (@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.136: (@Test) +//│ ║ l.125: (@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.136: (@Test) +//│ ║ l.125: (@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -146,10 +135,10 @@ type Test :re print(@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.147: print(@Test) +//│ ║ l.136: print(@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.147: print(@Test) +//│ ║ l.136: print(@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index e614bbaf58..6262247736 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -82,6 +82,7 @@ abstract class MLsDiffMaker extends DiffMaker: val noTailRecOpt = NullaryCommand("noTailRec") val deforest = Command("deforest")(_.trim) val etaExpansion = Command("etaExpansion")(_.trim) + val dataRepFlatten = Command("dataRepFlatten")(_.trim) val patMatConsequentSharingThreshold = Command("patMatConsequentSharingThreshold")(_.trim.toInt) val deadParamElim = Command("deadParamElim")(_.trim) @@ -97,6 +98,7 @@ abstract class MLsDiffMaker extends DiffMaker: "noLogAccumulator", ) private val EtaExpansionKnownFlags = Set("debug", "on", "off") + private val DataRepFlattenKnownFlags = Set("debug", "mono") private val DeadParamElimKnownFlags = Set("debug", "mono", "poly", "off") def mkConfig: Config = @@ -166,6 +168,13 @@ abstract class MLsDiffMaker extends DiffMaker: reportExclusiveFlagConflict(":etaExpansion", etaExpansionFlags, "on", "off") if etaExpansionFlags.contains("off") then N else S(EtaExpansion.withDebug(etaExpansionFlags.contains("debug"))), + dataRepFlatten = Opt.when(dataRepFlatten.isSet): + val flags = parseFlags(dataRepFlatten.get) + reportUnknownFlags(":dataRepFlatten", flags, DataRepFlattenKnownFlags) + DataRepFlatten( + debug = flags.contains("debug"), + mono = flags.contains("mono"), + ), qqEnabled = importQQ.isSet, funcToCls = funcToCls.isSet, commentGeneratedCode = debug.isSet,