Skip to content

Commit 864bd99

Browse files
authored
Add more VM tests (#788)
I added very basic support for regexes in the VM so that we can run the case studies. I also had to move `regex.effekt` to common to do that, even though it is not supported by LLVM.
1 parent 719ae83 commit 864bd99

File tree

17 files changed

+330
-88
lines changed

17 files changed

+330
-88
lines changed

effekt/js/src/main/scala/effekt/context/VirtualModuleDB.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ trait VirtualModuleDB extends ModuleDB { self: Context =>
1717
* used by Namer to resolve FFI includes
1818
*/
1919
override def contentsOf(path: String): Option[String] = {
20-
val f = file(module.source.name).parent / path
21-
if (!f.exists) {
22-
None
23-
} else {
24-
Some(f.read)
20+
val parent = file(module.source.name).parent
21+
(parent :: config.includes().map(file)).collectFirst {
22+
case base if (base / path).exists => (base / path).read
2523
}
2624
}
2725

effekt/jvm/src/main/scala/effekt/context/IOModuleDB.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ trait IOModuleDB extends ModuleDB { self: Context =>
1313
* used by Namer to resolve FFI includes
1414
*/
1515
override def contentsOf(path: String): Option[String] = {
16-
val includeFile = file(module.source.name).parent / path
17-
if (!includeFile.exists) {
18-
None
19-
} else {
20-
Some(FileSource(includeFile.toString).content)
16+
val parent = file(module.source.name).parent
17+
(parent :: config.includes().map(file)).collectFirst {
18+
case base if (base / path).exists => FileSource((base / path).toString).content
2119
}
2220
}
2321

effekt/jvm/src/test/scala/effekt/core/VMTests.scala

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,54 @@ class VMTests extends munit.FunSuite {
674674
resumes = 7
675675
)),
676676

677+
examplesDir / "casestudies" / "lexer.effekt.md" -> Some(Summary(
678+
staticDispatches = 245,
679+
dynamicDispatches = 18,
680+
patternMatches = 298,
681+
branches = 405,
682+
pushedFrames = 703,
683+
poppedFrames = 703,
684+
allocations = 202,
685+
closures = 27,
686+
variableReads = 164,
687+
variableWrites = 51,
688+
resets = 31,
689+
shifts = 11,
690+
resumes = 11
691+
)),
692+
693+
examplesDir / "casestudies" / "parser.effekt.md" -> Some(Summary(
694+
staticDispatches = 8845,
695+
dynamicDispatches = 783,
696+
patternMatches = 13502,
697+
branches = 14892,
698+
pushedFrames = 28523,
699+
poppedFrames = 28499,
700+
allocations = 7923,
701+
closures = 521,
702+
variableReads = 6742,
703+
variableWrites = 1901,
704+
resets = 806,
705+
shifts = 855,
706+
resumes = 839
707+
)),
708+
709+
examplesDir / "casestudies" / "anf.effekt.md" -> Some(Summary(
710+
staticDispatches = 4775,
711+
dynamicDispatches = 443,
712+
patternMatches = 7272,
713+
branches = 8110,
714+
pushedFrames = 16275,
715+
poppedFrames = 16260,
716+
allocations = 4317,
717+
closures = 358,
718+
variableReads = 4080,
719+
variableWrites = 1343,
720+
resets = 481,
721+
shifts = 660,
722+
resumes = 644
723+
)),
724+
677725
examplesDir / "casestudies" / "inference.effekt.md" -> Some(Summary(
678726
staticDispatches = 1457444,
679727
dynamicDispatches = 3201452,
@@ -689,13 +737,80 @@ class VMTests extends munit.FunSuite {
689737
shifts = 297723,
690738
resumes = 9275
691739
)),
740+
741+
examplesDir / "pos" / "raytracer.effekt" -> Some(Summary(
742+
staticDispatches = 79696,
743+
dynamicDispatches = 0,
744+
patternMatches = 1014772,
745+
branches = 71995,
746+
pushedFrames = 223269,
747+
poppedFrames = 223269,
748+
allocations = 127533,
749+
closures = 0,
750+
variableReads = 77886,
751+
variableWrites = 26904,
752+
resets = 0,
753+
shifts = 0,
754+
resumes = 0
755+
)),
756+
)
757+
758+
val other: Seq[(File, Option[Summary])] = Seq(
759+
examplesDir / "benchmarks" / "other" / "emit.effekt" -> Some(Summary(
760+
staticDispatches = 11,
761+
dynamicDispatches = 0,
762+
patternMatches = 0,
763+
branches = 11,
764+
pushedFrames = 102,
765+
poppedFrames = 102,
766+
allocations = 0,
767+
closures = 0,
768+
variableReads = 61,
769+
variableWrites = 30,
770+
resets = 1,
771+
shifts = 10,
772+
resumes = 10
773+
)),
774+
775+
examplesDir / "benchmarks" / "other" / "church_exponentiation.effekt" -> Some(Summary(
776+
staticDispatches = 7,
777+
dynamicDispatches = 1062912,
778+
patternMatches = 0,
779+
branches = 5,
780+
pushedFrames = 531467,
781+
poppedFrames = 531467,
782+
allocations = 0,
783+
closures = 265750,
784+
variableReads = 0,
785+
variableWrites = 0,
786+
resets = 0,
787+
shifts = 0,
788+
resumes = 0
789+
)),
790+
791+
examplesDir / "benchmarks" / "other" / "variadic_combinators.effekt" -> Some(Summary(
792+
staticDispatches = 27057,
793+
dynamicDispatches = 9009,
794+
patternMatches = 30052,
795+
branches = 3003,
796+
pushedFrames = 54105,
797+
poppedFrames = 54105,
798+
allocations = 24060,
799+
closures = 12030,
800+
variableReads = 24048,
801+
variableWrites = 18036,
802+
resets = 0,
803+
shifts = 0,
804+
resumes = 0
805+
)),
692806
)
693807

694808
val testFiles: Seq[(File, Option[Summary])] =
695809
are_we_fast_yet ++
696810
duality_of_compilation ++
697811
effect_handlers_bench ++
698-
casestudies
812+
casestudies ++
813+
other
699814

700815
def runTest(f: File, expectedSummary: Option[Summary]): Unit =
701816
val path = f.getPath
@@ -711,6 +826,4 @@ class VMTests extends munit.FunSuite {
711826
}
712827

713828
testFiles.foreach(runTest)
714-
715-
716829
}

effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package core
33
package vm
44

55
import java.io.PrintStream
6+
import scala.util.matching as regex
7+
import scala.util.matching.Regex
68

79
trait Runtime {
810
def out: PrintStream
@@ -193,12 +195,56 @@ lazy val strings: Builtins = Map(
193195
},
194196

195197
builtin("effekt::inspect(Any)") {
196-
case any :: Nil => Value.String(inspect(any))
198+
case any :: Nil =>
199+
Runtime.out.println(inspect(any))
200+
Value.Unit()
197201
},
198202

199203
builtin("effekt::infixEq(String, String)") {
200204
case As.String(x) :: As.String(y) :: Nil => Value.Bool(x == y)
201205
},
206+
207+
builtin("effekt::length(String)") {
208+
case As.String(x) :: Nil => Value.Int(x.length)
209+
},
210+
211+
builtin("effekt::substring(String, Int, Int)") {
212+
case As.String(x) :: As.Int(from) :: As.Int(to) :: Nil => Value.String(x.substring(from.toInt, to.toInt))
213+
},
214+
215+
builtin("string::unsafeCharAt(String, Int)") {
216+
case As.String(x) :: As.Int(at) :: Nil => Value.Int(x.charAt(at.toInt).toLong)
217+
},
218+
219+
builtin("string::toInt(Char)") {
220+
case As.Int(n) :: Nil => Value.Int(n)
221+
},
222+
223+
builtin("string::toChar(Int)") {
224+
case As.Int(n) :: Nil => Value.Int(n)
225+
},
226+
227+
builtin("string::infixLte(Char, Char)") {
228+
case As.Int(x) :: As.Int(y) :: Nil => Value.Bool(x <= y)
229+
},
230+
231+
builtin("string::infixLt(Char, Char)") {
232+
case As.Int(x) :: As.Int(y) :: Nil => Value.Bool(x < y)
233+
},
234+
235+
builtin("string::infixGt(Char, Char)") {
236+
case As.Int(x) :: As.Int(y) :: Nil => Value.Bool(x > y)
237+
},
238+
239+
builtin("string::infixGte(Char, Char)") {
240+
case As.Int(x) :: As.Int(y) :: Nil => Value.Bool(x >= y)
241+
},
242+
)
243+
244+
lazy val chars: Builtins = Map(
245+
builtin("effekt::infixEq(Char, Char)") {
246+
case As.Int(x) :: As.Int(y) :: Nil => Value.Bool(x == y)
247+
},
202248
)
203249

204250
lazy val arrays: Builtins = Map(
@@ -216,6 +262,12 @@ lazy val arrays: Builtins = Map(
216262
},
217263
)
218264

265+
lazy val undefined: Builtins = Map(
266+
builtin("effekt::isUndefined[A](A)") {
267+
case Value.Literal(m) :: Nil => Value.Bool(m == null)
268+
},
269+
)
270+
219271
lazy val refs: Builtins = Map(
220272
builtin("ref::ref[T](T)") {
221273
case init :: Nil => Value.Ref(Reference(init))
@@ -228,7 +280,22 @@ lazy val refs: Builtins = Map(
228280
},
229281
)
230282

231-
lazy val builtins: Builtins = printing ++ integers ++ doubles ++ booleans ++ strings ++ arrays ++ refs
283+
lazy val regexes: Builtins = Map(
284+
builtin("regex::regex(String)") {
285+
case As.String(str) :: Nil => Value.Literal(new Regex(str))
286+
},
287+
builtin("regex::exec(Regex, String)") {
288+
case As.Regex(r) :: As.String(str) :: Nil => Value.Literal(r.findFirstMatchIn(str).orNull)
289+
},
290+
builtin("regex::matched(RegexMatch)") {
291+
case As.RegexMatch(m) :: Nil => Value.String(m.matched)
292+
},
293+
builtin("regex::index(RegexMatch)") {
294+
case As.RegexMatch(m) :: Nil => Value.Int(m.start)
295+
},
296+
)
297+
298+
lazy val builtins: Builtins = printing ++ integers ++ doubles ++ booleans ++ strings ++ arrays ++ refs ++ chars ++ regexes ++ undefined
232299

233300
protected object As {
234301
object String {
@@ -240,6 +307,8 @@ protected object As {
240307
object Int {
241308
def unapply(v: Value): Option[scala.Long] = v match {
242309
case Value.Literal(value: scala.Long) => Some(value)
310+
case Value.Literal(value: scala.Int) => Some(value.toLong)
311+
case Value.Literal(value: java.lang.Integer) => Some(value.toLong)
243312
case _ => None
244313
}
245314
}
@@ -267,4 +336,17 @@ protected object As {
267336
case _ => None
268337
}
269338
}
339+
object Regex {
340+
def unapply(v: Value): Option[regex.Regex] = v match {
341+
case Value.Literal(v: regex.Regex) => Some(v)
342+
case _ => None
343+
}
344+
}
345+
object RegexMatch {
346+
def unapply(v: Value): Option[regex.Regex.Match | Null] = v match {
347+
case Value.Literal(null) => Some(null)
348+
case Value.Literal(v: regex.Regex.Match) => Some(v)
349+
case _ => None
350+
}
351+
}
270352
}

effekt/shared/src/main/scala/effekt/core/vm/VM.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ class Interpreter(instrumentation: Instrumentation, runtime: Runtime) {
491491
val arguments = vargs.map(a => eval(a, env))
492492
instrumentation.builtin(name)
493493
try { impl(runtime)(arguments) } catch { case e => sys error s"Cannot call ${b} with arguments ${arguments.map {
494-
case Value.Literal(l) => s"${l}: ${l.getClass.getName}"
494+
case Value.Literal(l) => s"${l}: ${l.getClass.getName}\n${e.getMessage}"
495495
case other => other.toString
496496
}.mkString(", ")}" }
497497
}
@@ -503,7 +503,7 @@ class Interpreter(instrumentation: Instrumentation, runtime: Runtime) {
503503
val arguments = vargs.map(a => eval(a, env))
504504
instrumentation.builtin(name)
505505
try { impl(runtime)(arguments) } catch { case e => sys error s"Cannot call ${x} with arguments ${arguments.map {
506-
case Value.Literal(l) => s"${l}: ${l.getClass.getName}"
506+
case Value.Literal(l) => s"${l}: ${l.getClass.getName}\n${e.getMessage}"
507507
case other => other.toString
508508
}.mkString(", ")}" }
509509
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import examples/benchmarks/runner
2+
3+
effect emit(value: Double): Unit
4+
5+
def printDoubles { p: () => Unit / emit } =
6+
try { p() } with emit { d =>
7+
println(d)
8+
resume(())
9+
}
10+
11+
def sumDoubles { p: () => Unit / emit } = {
12+
var sum = 0.0
13+
try { p() } with emit { d =>
14+
sum = sum + d
15+
resume(())
16+
}
17+
sum
18+
}
19+
20+
def runningMean { stream: => Unit / emit }: Unit / emit = {
21+
var n = 0
22+
var mean = 0.0
23+
try { stream() }
24+
with emit { x =>
25+
n = n + 1
26+
mean = mean + ((x - mean) / n.toDouble)
27+
do emit(mean)
28+
resume(())
29+
}
30+
}
31+
32+
def generate(N: Int) = {
33+
each(0, N) { n =>
34+
do emit(n.toDouble)
35+
}
36+
}
37+
38+
def run(N: Int) = sumDoubles { runningMean { generate(N) } }.toInt
39+
40+
def main() = benchmark(10){run}

examples/casestudies/lexer.effekt.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Before we get started, we require a few imports to deal with strings and regular
1515
module examples/casestudies/lexer
1616
1717
import string
18-
import text/regex
18+
import regex
1919
```
2020

2121
## Tokens and Positions

examples/casestudies/parser.effekt.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Parsers can be expressed by using the lexer effect and process the token stream.
2323
```
2424
interface Nondet {
2525
def alt(): Bool
26-
def fail[A](msg: String): A
26+
def fail(msg: String): Nothing
2727
}
2828
2929
effect Parser = { Nondet, Lexer }
@@ -205,7 +205,7 @@ def parse[R](input: String) { p: => R / Parser }: ParseResult[R] = try {
205205
case Failure(msg) => resume(false)
206206
case Success(res) => Success(res)
207207
}
208-
def fail[A](msg) = Failure(msg)
208+
def fail(msg) = Failure(msg)
209209
} with LexerError { (msg, pos) =>
210210
Failure(msg)
211211
}

0 commit comments

Comments
 (0)