Skip to content

Commit

Permalink
Update Rust interop from master (#1110)
Browse files Browse the repository at this point in the history
  • Loading branch information
yannbolliger authored Jul 15, 2021
1 parent a3196b1 commit 3c34688
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 43 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ val scriptSettings: Seq[Setting[_]] = Seq(
def ghProject(repo: String, version: String) = RootProject(uri(s"${repo}#${version}"))

// lazy val inox = RootProject(file("../inox"))
lazy val inox = ghProject("https://github.com/epfl-lara/inox.git", "e477160170d83e6fa011dd3b413dab94a4918a1d")
lazy val inox = ghProject("https://github.com/epfl-lara/inox.git", "2f66f5ab53f2ff63ef71bd67140b9532ee237e8b")
//lazy val dotty = ghProject("git://github.com/lampepfl/dotty.git", "b3194406d8e1a28690faee12257b53f9dcf49506")

// Allow integration test to use facilities from regular tests
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/stainless/MainHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ trait MainHelpers extends inox.MainHelpers { self =>
reporter.debug(e)(frontend.DebugSectionStack)
reporter.error(e.getMessage)
} finally {
compiler = newCompiler()
reporter.reset()
compiler = newCompiler()
}

if (isWatchModeOn(ctx)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ trait AntiAliasing
for (target1 <- targets1)
for ((arg2, targets2) <- otherTargets)
for (target2 <- targets2)
if (target1.prefixOf(target2) ||
target2.prefixOf(target1))
if (target1.maybePrefixOf(target2) ||
target2.maybePrefixOf(target1))
throw MalformedStainlessCode(expr,
s"Illegal passing of aliased parameters ${arg1.asString} (with target: ${target1.asString}) " +
s"and ${arg2.asString} (with target: ${target2.asString})"
Expand Down Expand Up @@ -603,6 +603,9 @@ trait AntiAliasing
case Target(receiver, None, path) =>
rec(receiver, path.toSeq)

case Target(receiver, Some(condition), path) if effects(condition).nonEmpty =>
throw FatalError(s"Effects are not allowed in condition of effects: ${condition.asString}")

case Target(receiver, Some(condition), path) =>
Annotated(
AsInstanceOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,30 +161,37 @@ trait EffectsAnalyzer extends oo.CachingPhase {

sealed abstract class Accessor {
def asString(implicit ctx: inox.Context): String
def bind(x: ValDef, e: Expr): Accessor
}

case class ADTFieldAccessor(selector: Identifier) extends Accessor {
def asString(implicit ctx: inox.Context) = s"ADTFieldAccessor(${selector.asString})"
def bind(x: ValDef, e: Expr): Accessor = this
}

case class ClassFieldAccessor(selector: Identifier) extends Accessor {
def asString(implicit ctx: inox.Context) = s"ClassFieldAccessor(${selector.asString})"
def bind(x: ValDef, e: Expr): Accessor = this
}

case class ArrayAccessor(index: Expr) extends Accessor {
def asString(implicit ctx: inox.Context) = s"ArrayAccessor(${index.asString})"
def bind(x: ValDef, e: Expr): Accessor = ArrayAccessor(bindNonValue(x, e, index))
}

case object UnknownArrayAccessor extends Accessor {
def asString(implicit ctx: inox.Context) = s"UnknownArrayAccessor"
def bind(x: ValDef, e: Expr): Accessor = this
}

case class MutableMapAccessor(index: Expr) extends Accessor {
def asString(implicit ctx: inox.Context) = s"MutableMapAccessor(${index.asString})"
def bind(x: ValDef, e: Expr): Accessor = ArrayAccessor(bindNonValue(x, e, index))
}

case class TupleFieldAccessor(index: Int) extends Accessor {
def asString(implicit ctx: inox.Context) = s"TupleFieldAccessor($index)"
def bind(x: ValDef, e: Expr): Accessor = this
}

case class Path(path: Seq[Accessor]) {
Expand All @@ -197,7 +204,11 @@ trait EffectsAnalyzer extends oo.CachingPhase {

def wrap(expr: Expr)(implicit symbols: Symbols) = Path.wrap(expr, path)

def prefixOf(that: Path): Boolean = {
def bind(x: ValDef, e: Expr) = Path(path.map(_.bind(x, e)))

// can return `true` even if `this` is not really a prefix of `that`
// (because of array and mutable map accessors)
def maybePrefixOf(that: Path): Boolean = {
// TODO: more expressions can be added to be "provably different"
def provablyDifferent(index1: Expr, index2: Expr): Boolean = (index1, index2) match {
case (x1: Variable, Plus(x2: Variable, BVLiteral(_, _, size)))
Expand Down Expand Up @@ -227,6 +238,26 @@ trait EffectsAnalyzer extends oo.CachingPhase {
rec(path, that.path)
}

// can return `false` even if `this` is a prefix of `that`
def definitelyPrefixOf(that: Path): Boolean = {
def rec(p1: Seq[Accessor], p2: Seq[Accessor]): Boolean = (p1, p2) match {
case (Seq(), _) => true
case (ArrayAccessor(index1) +: xs1, ArrayAccessor(index2) +: xs2) if index1 == index2 =>
rec(xs1, xs2)
case (ADTFieldAccessor(id1) +: xs1, ADTFieldAccessor(id2) +: xs2) if id1 == id2 =>
rec(xs1, xs2)
case (ClassFieldAccessor(id1) +: xs1, ClassFieldAccessor(id2) +: xs2) if id1 == id2 =>
rec(xs1, xs2)
case (TupleFieldAccessor(id1) +: xs1, TupleFieldAccessor(id2) +: xs2) if id1 == id2 =>
rec(xs1, xs2)
case (MutableMapAccessor(k1) +: xs1, MutableMapAccessor(k2) +: xs2) if k1 == k2 =>
rec(xs1, xs2)
case _ => false
}

rec(path, that.path)
}

def toSeq: Seq[Accessor] = path

def asString(implicit printerOpts: PrinterOptions): String =
Expand Down Expand Up @@ -277,9 +308,29 @@ trait EffectsAnalyzer extends oo.CachingPhase {

}

// values that do not need binding
private def isValue(e: Expr): Boolean = e match {
case _: Variable => true
case UnitLiteral() => true
case BooleanLiteral(_) => true
case IntegerLiteral(_) => true
case BVLiteral(_, _, _) => true
case Tuple(es) => es.forall(isValue)
case ADT(_, _, args) => args.forall(isValue)
case _ => false
}

def bindNonValue(x: ValDef, e: Expr, body: Expr): Expr = {
if (isValue(e)) exprOps.replaceFromSymbols(Map(x -> e), body)
else if (exprOps.variablesOf(body).contains(x.toVariable)) Let(x, e, body)
else body
}

case class Target(receiver: Variable, condition: Option[Expr], path: Path) {
def +(elem: Accessor): Target = Target(receiver, condition, path :+ elem)

def bind(x: ValDef, e: Expr) = Target(receiver, condition.map(cond => bindNonValue(x, e, cond)), path.bind(x, e))

def append(that: Target): Target = (condition, that.condition) match {
case (condition, None) =>
Target(receiver, condition, path ++ that.path)
Expand All @@ -298,8 +349,11 @@ trait EffectsAnalyzer extends oo.CachingPhase {
case CombinedKind => CombinedEffect(receiver, path)
}

def prefixOf(that: Target): Boolean =
receiver == that.receiver && (path prefixOf that.path)
def maybePrefixOf(that: Target): Boolean =
receiver == that.receiver && (path maybePrefixOf that.path)

def definitelyPrefixOf(that: Target): Boolean =
receiver == that.receiver && (path definitelyPrefixOf that.path)

def wrap(implicit symbols: Symbols): Option[Expr] = path.wrap(receiver)

Expand Down Expand Up @@ -387,8 +441,11 @@ trait EffectsAnalyzer extends oo.CachingPhase {
res
}

def prefixOf(that: Effect): Boolean =
receiver == that.receiver && (path prefixOf that.path)
def maybePrefixOf(that: Effect): Boolean =
receiver == that.receiver && (path maybePrefixOf that.path)

def definitelyPrefixOf(that: Effect): Boolean =
receiver == that.receiver && (path definitelyPrefixOf that.path)

def toTarget: Target = Target(receiver, None, path)

Expand Down Expand Up @@ -490,18 +547,17 @@ trait EffectsAnalyzer extends oo.CachingPhase {
And(cnd, e.setPos(cnd)).setPos(cnd)
} getOrElse(cnd)

for {
t <- getTargets(thn, kind, path)
e <- getTargets(els, kind, path)
target <- Set(
Target(t.receiver, Some(conj(cnd, t.condition)), t.path),
Target(e.receiver, Some(notConj(cnd, e.condition)), e.path)
) if target.isValid
} yield target
getTargets(thn, kind, path).map { t =>
Target(t.receiver, Some(conj(cnd, t.condition)), t.path)
}.filter(_.isValid) ++
getTargets(els, kind, path).map { t =>
Target(t.receiver, Some(notConj(cnd, t.condition)), t.path)
}.filter(_.isValid)

case fi: FunctionInvocation if !symbols.isRecursive(fi.id) =>
BodyWithSpecs(symbols.simplifyLets(fi.inlined))
.bodyOpt
val specced = BodyWithSpecs(symbols.simplifyLets(fi.inlined))
specced.bodyOpt
.map(specced.wrapLets)
.map(getTargets(_, kind, path))
.getOrElse(Set.empty)

Expand Down Expand Up @@ -538,7 +594,7 @@ trait EffectsAnalyzer extends oo.CachingPhase {
case Block(_, last) => getTargets(last, kind, path)

case Let(vd, e, b) if !symbols.isMutableType(vd.tpe) =>
getTargets(b, kind, path)
getTargets(b, kind, path).map(_.bind(vd, e))

case Let(vd, e, b) =>
val eEffects = getTargets(e, kind, path)
Expand Down Expand Up @@ -803,11 +859,11 @@ trait EffectsAnalyzer extends oo.CachingPhase {
* - a combined effect effect which is strict prefix of other effects remains a combined effect
*/
val combinedEffects = truncatedEffects.flatMap { e =>
if (truncatedEffects.exists(e2 => e.path != e2.path && e2.prefixOf(e)))
if (truncatedEffects.exists(e2 => e.path != e2.path && e2.definitelyPrefixOf(e)))
None // drop effects for which there is a strictly shorter (prefix) effect (regardless of effect kind)
else if (truncatedEffects.exists(e2 => e.receiver == e2.receiver && e.path == e2.path && e.kind != e2.kind))
Some(e.withKind(CombinedKind)) // different effects with the same receiver/path become combined effects
else if (truncatedEffects.exists(e2 => e.prefixOf(e2) && e.path != e2.path && e.kind == ReplacementKind))
else if (truncatedEffects.exists(e2 => e.definitelyPrefixOf(e2) && e.path != e2.path && e.kind == ReplacementKind))
Some(e.withKind(CombinedKind)) // a top-level replacement, strict prefix of another effect, becomes a combined effect
else
Some(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ trait FunctionInlining extends CachingPhase with IdentitySorts { self =>
case _ => super.transform(expr)
}

// values that can be inlined directly, without being let-bound
// function bodies that can be inlined directly, without bindings
private def isValue(e: Expr): Boolean = e match {
case UnitLiteral() => true
case BooleanLiteral(_) => true
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/stainless/genc/CPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CPrinter(
// We need to convert Set to Seq in order to use nary.
val typeDefs = typeDefs0.toSeq
val enums = enums0.toSeq.sortBy(_.id.name)
val functions = functions0.toSeq.sortBy(_.id.name)
val functions = functions0.toSeq.sortBy(_.id.name).filter(_.body != Right(""))

def separator(s: String) = {
"/* " + "-" * (43 - s.length) + " " + s + " " + "----- */\n\n"
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/stainless/genc/ir/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private[genc] sealed trait IR { ir =>
// Define a function body as either a regular AST or a manually defined
// function using @cCode.function
sealed abstract class FunBody
case object FunDropped extends FunBody // for @cCode.drop
case class FunBodyAST(body: Expr) extends FunBody
case class FunBodyManual(includes: Seq[String], body: String) extends FunBody // NOTE `body` is actually the whole function!

Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/stainless/genc/ir/Transformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ abstract class Transformer[From <: IR, To <: IR](final val from: From, final val
}

protected def rec(fb: FunBody)(implicit env: Env): to.FunBody = (fb: @unchecked) match {
case FunDropped => to.FunDropped
case FunBodyAST(body) => to.FunBodyAST(rec(body))
case FunBodyManual(includes, body) => to.FunBodyManual(includes, body)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,6 @@ trait ComputeDependenciesPhase extends LeonPipeline[Symbols, Dependencies] {
def validateFunAnnotations(fd: FunDef): Unit = {
val pos = fd.getPos

// Those next three tests could be carried out on all functions, not just dependencies
if (fd.isExtern && !fd.isManuallyDefined && !fd.isDropped)
reporter.fatalError(
pos,
s"Extern functions (${fd.id.asString}) need to be either manually defined or dropped"
)

if (fd.isManuallyDefined && fd.isDropped)
reporter.fatalError(
pos,
Expand All @@ -106,13 +99,6 @@ trait ComputeDependenciesPhase extends LeonPipeline[Symbols, Dependencies] {
pos,
s"Generic functions (${fd.id.asString}) cannot be exported"
)

// This last test is specific to dependencies.
if (fd.isDropped)
reporter.fatalError(
pos,
s"Dropped functions (${fd.id.asString}) shall not be used"
)
}

val finder = new ComputeDependenciesImpl(context)
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/stainless/genc/phases/ExtraOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ private[genc] object ExtraOps {

implicit class FunAbsOps(val fa: FunAbstraction) {
def isManuallyDefined = hasAnnotation(manualDefAnnotation)
def isExtern = fa.flags contains Extern
def isDropped = hasAnnotation("cCode.drop") || fa.flags.contains(Ghost)

def extAnnotations: Map[String, Seq[Any]] = fa.flags.collect {
case Annotation(s, args) => s -> args
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/stainless/genc/phases/IR2CPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ private class IR2CImpl(val ctx: inox.Context) {
case FunBodyManual(includes, body) =>
includes foreach { i => register(C.Include(i)) }
Right(body)

case FunDropped => Right("")
}

private def rec(cd: ClassDef): Unit = {
Expand Down
10 changes: 7 additions & 3 deletions core/src/main/scala/stainless/genc/phases/Scala2IRPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private class S2IRImpl(val context: inox.Context, val ctxDB: FunCtxDB, val deps:
// Include the "nesting path" in case of generic functions to avoid ambiguity
private def buildId(fa: FunAbstraction, tps: Seq[Type])(implicit tm: TypeMapping): CIR.Id = {
val exported = fa.isInstanceOf[Outer] && fa.asInstanceOf[Outer].fd.isExported
rec(fa.id, withUnique = !exported) + (if (tps.nonEmpty) buildIdPostfix(tps) else buildIdFromTypeMapping(tm))
rec(fa.id, withUnique = !exported && !fa.isDropped) + (if (tps.nonEmpty) buildIdPostfix(tps) else buildIdFromTypeMapping(tm))
}

private def buildId(ct: ClassType)(implicit tm: TypeMapping): CIR.Id = {
Expand Down Expand Up @@ -511,6 +511,8 @@ private class S2IRImpl(val context: inox.Context, val ctxDB: FunCtxDB, val deps:
if (fa.isManuallyDefined) {
val impl = fa.getManualDefinition
CIR.FunBodyManual(impl.includes, impl.code)
} else if (fa.isDropped) {
CIR.FunDropped
} else {
// Build the new environment from context and parameters
val ctxKeys: Seq[(ValDef, Type)] = ctxDBAbs map { c => c.vd -> instantiateType(c.typ, tm1) }
Expand Down Expand Up @@ -725,7 +727,9 @@ private class S2IRImpl(val context: inox.Context, val ctxDB: FunCtxDB, val deps:
CIR.Assign(CIR.Binding(vd), rec(expr))

case ct: ClassType =>
CIR.Assign(CIR.FieldAccess(rec(obj), rec(fieldId, withUnique = !ct.tcd.cd.isExported)), rec(expr))
val cd = ct.tcd.cd
val mangling = !cd.isExported && !cd.isManuallyTyped
CIR.Assign(CIR.FieldAccess(rec(obj), rec(fieldId, withUnique = mangling)), rec(expr))

case typ =>
reporter.fatalError(e.getPos, s"Unexpected type $typ. Only class type are expected to update fields")
Expand Down Expand Up @@ -822,7 +826,7 @@ private class S2IRImpl(val context: inox.Context, val ctxDB: FunCtxDB, val deps:
val vd = CIR.ValDef(rec(fieldId, withUnique = false), field2.typ, isVar = field2.isVar)
CIR.Binding(vd)
} else {
CIR.FieldAccess(rec(obj), rec(fieldId, withUnique = !cd.isExported))
CIR.FieldAccess(rec(obj), rec(fieldId, withUnique = !cd.isExported && !cd.isManuallyTyped))
}

case tuple @ Tuple(args0) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,11 @@ trait AssertionInjector extends transformers.TreeTransformer {

case BVTyped(signed, size, BVShift(rhs, recons)) if strictArithmetic =>
bindIfCannotDuplicate(rhs, "rhs") { rhsx =>
val lt = t.LessThan(rhsx, t.BVLiteral(signed, size, size).copiedFrom(rhs)).copiedFrom(rhs)
val leq = t.LessEquals(rhsx, t.BVLiteral(signed, size, size).copiedFrom(rhs)).copiedFrom(rhs)
// positivity check is only relevant for signed bitvectors
val pos = t.GreaterEquals(rhsx, zero(true, size, rhs.getPos)).copiedFrom(rhs)
// TODO: explain why `checkOverflow` here and `strictArithmetic` in the guard?
val range = if (signed && checkOverflow) t.And(pos, lt).copiedFrom(rhs) else lt
val range = if (signed && checkOverflow) t.And(pos, leq).copiedFrom(rhs) else leq
// Ensure the operation doesn't shift more bits than there are.
t.Assert(range, Some("Shift semantics"), recons(rhsx)).copiedFrom(e)
}
Expand Down
15 changes: 15 additions & 0 deletions frontends/benchmarks/extraction/invalid/IllegalAliasing2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
object IllegalAliasing2 {

case class A(var x: Int)
def f(a1: A, a2: A): Unit = {
}

def zero: Int = 0

def g(a: Array[A]) = {
require(a.length > 0)

f(a(0), a(zero)) // Illegal passing of aliased parameters
}

}
9 changes: 9 additions & 0 deletions frontends/benchmarks/imperative/valid/ArrayAccess.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
object ArrayAccess {
def zero: Int = 0

def reset(a: Array[Int]): Unit = {
require(a.length > 0)
a(zero) = 0
a(0) = 0
}
}
Loading

0 comments on commit 3c34688

Please sign in to comment.