Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ trait AdaptiveContextSensitivity extends AdaptiveSchemeModFSemantics:
case Main => None
case Call((lam, _), ctx: ComponentContext @unchecked) => Some((ctx, LambdaModule(lam)))
def allocPtr(exp: SchemeExp, cmp: SchemeModFComponent) = PtrAddr(exp, addrContext(cmp))
def allocVarArgPtr(exp: SchemeExp, cmp: SchemeModFComponent) = VarArgPtrAddr[AllocationContext](exp, addrContext(cmp))

def allocVar(idf: Identifier, cmp: SchemeModFComponent) = VarAddr(idf, addrContext(cmp))

// during the analysis, keep track of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ trait AdaptiveSchemeModFSemantics
def adaptAllocCtx(ctx: AllocationContext): AllocationContext
def adaptAddr(addr: Addr): Addr = addr match
case ptr: PtrAddr[AllocationContext] @unchecked => PtrAddr(ptr.exp, adaptAllocCtx(ptr.ctx))
case ptr: VarArgPtrAddr[AllocationContext] @unchecked => VarArgPtrAddr(ptr.exp, adaptAllocCtx(ptr.ctx))
case vad: VarAddr[AllocationContext] @unchecked => VarAddr(vad.id, adaptAllocCtx(vad.ctx))
case ret: ReturnAddr[Component] @unchecked => ReturnAddr(adaptComponent(ret.cmp), ret.idn)
case pad: PrmAddr => pad
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ case class VarAddr[Context](id: Identifier, ctx: Context) extends SchemeAddr[Con
def printable = !id.name.startsWith("__")
def idn: Identity = id.idn
override def toString: String = s"${id.fullString}${showCtx(ctx)}"
case class VarArgPtrAddr[Context](exp: SchemeExp, ctx: Context) extends SchemeAddr[Context]:
def printable = false
def idn: Identity = exp.idn
override def toString: String = s"VarArgPtrAddr(${exp.idn.pos})${showCtx(ctx)}"

case class PtrAddr[Context](exp: SchemeExp, ctx: Context) extends SchemeAddr[Context]:
def printable = false
def idn: Identity = exp.idn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ trait SchemeSetup
def resultsPerIdn: Map[Identity, Set[Value]] =
store
.filter(_._1 match {
case _: VarAddr[_] | _: PtrAddr[_] => true
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] => true
case _: ExceptionAddr[_] => true
case _ => false
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ trait StandardSchemeModConcAllocator extends SchemeModConcSemantics:
modfCmp: SchemeModFComponent,
cmp: Component
) = PtrAddr(exp, modfCmp)
def allocVarArgPtr(
exp: SchemeExp,
modfCmp: SchemeModFComponent,
cmp: Component
) = VarArgPtrAddr[AllocationContext](exp, modfCmp)
override def configString(): String = super.configString() + "\n allocating addresses using the ModF component as context"
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ trait SchemeModConcSemantics extends ModAnalysis[SchemeExp] with ContextSensitiv
modFCmp: SchemeModFComponent,
cmp: Component
): PtrAddr[AllocationContext]
def allocVarArgPtr(
exp: SchemeExp,
modFCmp: SchemeModFComponent,
cmp: Component
): VarArgPtrAddr[AllocationContext]

//
// MODCONC INTRA-ANALYSIS
Expand Down Expand Up @@ -97,6 +102,9 @@ trait SchemeModConcSemantics extends ModAnalysis[SchemeExp] with ContextSensitiv
type AllocationContext = inter.AllocationContext
def allocVar(id: Identifier, cmp: SchemeModFComponent) = inter.allocVar(id, cmp, intra.component)
def allocPtr(exp: SchemeExp, cmp: SchemeModFComponent) = inter.allocPtr(exp, cmp, intra.component)
def allocVarArgPtr(exp: SchemeExp, cmp: SchemeModFComponent) = inter.allocVarArgPtr(exp, cmp, intra.component)


// GLOBAL STORE SETUP
override def store = intra.store
override def store_=(s: Map[Addr, Value]) = intra.store = s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ trait StandardSchemeModFAllocator extends BaseSchemeModFSemantics:
type AllocationContext = Option[ComponentContext]
def allocVar(id: Identifier, cmp: Component) = VarAddr(id, context(cmp))
def allocPtr(exp: SchemeExp, cmp: Component) = PtrAddr(exp, context(cmp))
def allocVarArgPtr(exp: SchemeExp, cmp: Component) = VarArgPtrAddr[AllocationContext](exp, context(cmp))

override def configString(): String = super.configString() + "\n allocating addresses using the function call as context"

// the "old", more precise allocator, where allocation context = the entire component
trait ComponentSchemeModFAllocator extends BaseSchemeModFSemantics:
type AllocationContext = Component
def allocVar(id: Identifier, cmp: Component) = VarAddr(id, cmp)
def allocPtr(exp: SchemeExp, cmp: Component) = PtrAddr(exp, cmp)
def allocVarArgPtr(exp: SchemeExp, cmp: Component) = VarArgPtrAddr[AllocationContext](exp, cmp)

override def configString(): String = super.configString() + "\n allocating addresses using the component as context"
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ trait BaseSchemeModFSemanticsM
type AllocationContext
def allocVar(id: Identifier, cmp: Component): VarAddr[AllocationContext]
def allocPtr(exp: SchemeExp, cmp: Component): PtrAddr[AllocationContext]
def allocVarArgPtr(exp: SchemeExp, cmp: Component): VarArgPtrAddr[AllocationContext]


/* convience accessors for argument values */
def argValues(cmp: Component): Map[String, Value] = view(cmp) match
Expand Down Expand Up @@ -251,7 +253,7 @@ trait BaseSchemeModFSemanticsM
val fixedArgVals = fixedArgs.map(_._2)

for
varArgVal <- allocateList(varArgs)
varArgVal <- allocateList(varArgs, isVarArg = true)
context <- ctx.allocM(clo, fixedArgVals :+ varArgVal, cll, component)
targetCall = Call(clo, context)
targetCmp <- newComponentM(targetCall)
Expand All @@ -265,13 +267,15 @@ trait BaseSchemeModFSemanticsM
case _ => Monad[M].unit(lattice.bottom)
})
)
protected def allocateList(elms: List[(SchemeExp, Value)]): M[Value] = elms match
protected def allocateList(elms: List[(SchemeExp, Value)], isVarArg: Boolean = false): M[Value] = elms match
case Nil => baseEvalM.unit(lattice.nil)
case (exp, vlu) :: rest =>
allocateList(rest).flatMap(v => allocateCons(exp)(vlu, v))
allocateList(rest, isVarArg).flatMap(v => allocateCons(exp)(vlu, v, isVarArg))

protected def allocateCons(pairExp: SchemeExp)(car: Value, cdr: Value, isVarArg: Boolean = false): M[Value] =
val addr = if isVarArg then allocVarArgPtr(pairExp, component) else allocPtr(pairExp, component)
write(addr, lattice.cons(car, cdr)) >>> baseEvalM.unit(lattice.pointer(addr))

protected def allocateCons(pairExp: SchemeExp)(car: Value, cdr: Value): M[Value] =
allocateVal(pairExp)(lattice.cons(car, cdr))
protected def allocateString(stringExp: SchemeExp)(str: String): M[Value] =
allocateVal(stringExp)(lattice.string(str))
protected def allocateVal(exp: SchemeExp)(v: Value): M[Value] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ abstract class SchemeModFADI(prg: SchemeExp) extends ModAnalysis[SchemeExp](prg)
given GC[Sto, Adr] = GC.storeStopAndCopyGC
given store: Store[Sto, Adr, Val] = Store.countingInstance
given shouldCount: (Adr => Boolean) =
case _: PtrAddr[_] => true
case _: PtrAddr[_] | _: VarArgPtrAddr[_] => true
case _ => false

//
Expand Down Expand Up @@ -210,14 +210,14 @@ trait SchemeModFADIAnalysisResults extends SchemeModFADI with AnalysisResults[Sc

override def extendV(sto: Sto, adr: Adr, vlu: Val) =
adr match
case _: VarAddr[_] | _: PtrAddr[_] =>
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] =>
resultsPerIdn += adr.idn -> (resultsPerIdn(adr.idn) + vlu)
case _ => ()
super.extendV(sto, adr, vlu)

override def updateV(sto: Sto, adr: Adr, vlu: Val) =
adr match
case _: VarAddr[_] | _: PtrAddr[_] =>
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] =>
resultsPerIdn += adr.idn -> (resultsPerIdn(adr.idn) + vlu)
case _ => ()
super.updateV(sto, adr, vlu)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ abstract class SchemeModFLocal(prg: SchemeExp) extends ModAnalysis[SchemeExp](pr
lazy val initialSto: Sto = LocalStore.from(initialBds.map(p => (p._2, p._3)))

given shouldCount: (Adr => Boolean) =
case _: PtrAddr[_] => true
case _: PtrAddr[_] | _: VarArgPtrAddr[_] => true
case _ => false

private lazy val initialBds: Iterable[(String, Adr, Val)] =
Expand Down Expand Up @@ -214,14 +214,14 @@ trait SchemeModFLocalAnalysisResults extends SchemeModFLocal with AnalysisResult

override def extendV(sto: Sto, adr: Adr, vlu: Val) =
adr match
case _: VarAddr[_] | _: PtrAddr[_] =>
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] =>
resultsPerIdn += adr.idn -> (resultsPerIdn(adr.idn) + vlu)
case _ => ()
super.extendV(sto, adr, vlu)

override def updateV(sto: Sto, adr: Adr, vlu: Val) =
adr match
case _: VarAddr[_] | _: PtrAddr[_] =>
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] =>
resultsPerIdn += adr.idn -> (resultsPerIdn(adr.idn) + vlu)
case _ => ()
super.updateV(sto, adr, vlu)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ abstract class SchemeModFLocalFS(prg: SchemeExp, gc: Boolean = true) extends Mod
lazy val initialSto: Sto = LocalStore.from(initialBds.map(p => (p._2, p._3)))

given shouldCount: (Adr => Boolean) =
case _: PtrAddr[_] => true
case _: PtrAddr[_] | _: VarArgPtrAddr[_] => true
case _ => false

private lazy val initialBds: Iterable[(String, Adr, Val)] =
Expand Down Expand Up @@ -283,14 +283,14 @@ trait SchemeModFLocalFSAnalysisResults extends SchemeModFLocalFS with AnalysisRe
var resultsPerIdn = Map.empty.withDefaultValue(Set.empty)
override def extendV(sto: Sto, adr: Adr, vlu: Val) =
adr match
case _: VarAddr[_] | _: PtrAddr[_] =>
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] =>
resultsPerIdn += adr.idn -> (resultsPerIdn(adr.idn) + vlu)
case _ => ()
super.extendV(sto, adr, vlu)

override def updateV(sto: Sto, adr: Adr, vlu: Val) =
adr match
case _: VarAddr[_] | _: PtrAddr[_] =>
case _: VarAddr[_] | _: PtrAddr[_] | _: VarArgPtrAddr[_] =>
resultsPerIdn += adr.idn -> (resultsPerIdn(adr.idn) + vlu)
case _ => ()
super.updateV(sto, adr, vlu)
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ trait SmallStepModConcSemantics extends SchemeSetup with ContextSensitiveCompone
// all allocations are context-insensitive
def allocVar(id: Identifier, cmp: Component) = VarAddr(id, ())
def allocPtr(ptr: SchemeExp, cmp: Component) = PtrAddr(ptr, ())
def allocVarArgPtr(ptr: SchemeExp, cmp: Component) = VarArgPtrAddr[Unit](ptr, ())


//XXXXXXXXX//
// PROGRAM //
Expand Down Expand Up @@ -455,7 +457,7 @@ trait SmallStepModConcSemantics extends SchemeSetup with ContextSensitiveCompone
case (SchemeVarArgLambda(_, prs, vararg, body, _, _), env) if prs.length <= args.length =>
val (fixedArgs, varArgs) = args.splitAt(prs.length)
val fixedArgVals = fixedArgs.map(_._2)
val varArgVal = allocateList(varArgs)
val varArgVal = allocateList(varArgs, isVarArg = true)
val env2 = define(vararg, varArgVal, prs.zip(fixedArgVals).foldLeft(env)({ case (env, (f, a)) => define(f, a, env) }))
evalSequence(body, env2, stack)
case _ => Set()
Expand All @@ -469,20 +471,20 @@ trait SmallStepModConcSemantics extends SchemeSetup with ContextSensitiveCompone
// ALLOCATION HELPERS //
//--------------------//

protected def allocateVal(exp: SchemeExp)(value: Value): Value =
val addr = allocPtr(exp, component)
writeAddr(addr, value)
protected def allocateVal(exp: SchemeExp, isVarArg: Boolean = false)(v: Value): Value =
val addr = if isVarArg then allocVarArgPtr(exp, component) else allocPtr(exp, component)
writeAddr(addr, v)
lattice.pointer(addr)

protected def allocateCons(pairExp: SchemeExp)(car: Value, cdr: Value): Value =
allocateVal(pairExp)(lattice.cons(car, cdr))
protected def allocateCons(pairExp: SchemeExp)(car: Value, cdr: Value, isVarArg: Boolean = false): Value =
allocateVal(pairExp, isVarArg)(lattice.cons(car, cdr))

protected def allocateStr(strExp: SchemeExp)(str: String): Value =
allocateVal(strExp)(lattice.string(str))

protected def allocateList(elms: List[(SchemeExp, Value)]): Value = elms match
protected def allocateList(elms: List[(SchemeExp, Value)], isVarArg: Boolean = false): Value = elms match
case Nil => lattice.nil
case (exp, vlu) :: rest => allocateCons(exp)(vlu, allocateList(rest))
case (exp, vlu) :: rest => allocateCons(exp)(vlu, allocateList(rest, isVarArg), isVarArg)

given SchemeInterpreterBridge[Value, Addr] with
def pointer(exp: SchemeExp): Addr = allocPtr(exp, component)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package maf.test.modular.scheme

import org.scalatest.funsuite.AnyFunSuite
import maf.language.scheme._
import maf.modular.scheme.modflocal._
import maf.modular.scheme.modf._
import maf.modular.scheme.modconc._
import maf.modular.scheme.ssmodconc._
import maf.modular.scheme._
import maf.modular.worklist._
import maf.core._
import maf.modular._
import maf.language.scheme.primitives.SchemePrelude

class VarArgPrecisionTests extends AnyFunSuite {

def runAllAnalyses(prg: SchemeExp, verifyResultLocal: Set[_] => Unit, verifyResultGlobal: Any => Unit): Unit = {
// 1. ModFLocal
val an1 = new SchemeModFLocal(prg)
with SchemeConstantPropagationDomain
with SchemeModFLocalNoSensitivity
with FIFOWorklistAlgorithm[SchemeExp]
with SchemeModFLocalAnalysisResults
an1.analyze()
// For ModFLocal, we find the last expression evaluated (which is usually what we care about)
val resultIdn = an1.resultsPerIdn.keys.maxBy(idn => (idn.pos.line, idn.pos.col))
verifyResultLocal(an1.resultsPerIdn(resultIdn))

// 2. ModF
val an2 = new SimpleSchemeModFAnalysis(prg)
with StandardSchemeModFAllocator
with SchemeConstantPropagationDomain
with SchemeModFNoSensitivity
with FIFOWorklistAlgorithm[SchemeExp]
an2.analyze()
verifyResultGlobal(an2.finalResult)

// 3. SmallStepModConc
val an3 = new ModAnalysis(prg)
with KKallocModConc
with SchemeConstantPropagationDomain
with LIFOWorklistAlgorithm[SchemeExp] {
val k = 1
override def intraAnalysis(component: SmallStepModConcComponent) = new IntraAnalysis(component) with SmallStepIntra with KCFAIntra
}
an3.analyze()
verifyResultGlobal(an3.finalResult)

// 4. ModConc (Big-step)
val an4 = new SimpleSchemeModConcAnalysis(prg)
with SchemeModConcStandardSensitivity
with SchemeConstantPropagationDomain
with CallDepthFirstWorklistAlgorithm[SchemeExp]
with ParallelWorklistAlgorithm[SchemeExp] {
override def workers: Int = 4
override def intraAnalysis(cmp: SchemeModConcComponent) = new SchemeModConcIntra(cmp) with ParallelIntra
def modFAnalysis(intra: SchemeModConcIntra) = new InnerModFAnalysis(intra) with SchemeModFNoSensitivity with RandomWorklistAlgorithm[SchemeExp]
}
an4.analyze()
verifyResultGlobal(an4.finalResult)
}

def buildPrg(code: String): SchemeExp = {
val parsed = SchemeParser.parse(code)
val prelud = SchemePrelude.addPrelude(parsed)
val transf = SchemeMutableVarBoxer.transform(prelud)
SchemeParser.undefine(transf)
}

test("Test 1: Vararg list allocation should not merge with argument expressions") {
val code = """
(define (f . x) x)
(car (f '(1) '(2) '(3)))
"""

runAllAnalyses(buildPrg(code),
localRes => {
val containsNumber1 = localRes.exists(v => v.toString.contains("1") && !v.toString.contains("PtrAddr") && !v.toString.contains("VarArgPtrAddr"))
assert(!containsNumber1, s"Vararg allocation merged! Local result: $localRes")
},
globalRes => {
val containsNumber1 = globalRes.toString.contains("1") && !globalRes.toString.contains("PtrAddr") && !globalRes.toString.contains("VarArgPtrAddr")
assert(!containsNumber1, s"Vararg allocation merged! Global result: $globalRes")
}
)
}

test("Test 2: Vararg list with fixed arguments should resolve precisely") {
val code = """
(define (f x . y) (car y))
(f 100 200 300)
"""

runAllAnalyses(buildPrg(code),
localRes => {
// ModFLocal resultsPerIdn only tracks the store (VarAddr/PtrAddr), not the final return value.
// We can skip checking the final return value for ModFLocal here, as the global check covers it for all others.
},
globalRes => {
assert(globalRes.toString.contains("200"), s"Should contain 200, but got: $globalRes")
assert(!globalRes.toString.contains("100"), s"Should not contain 100 (fixed arg leaked)! Got: $globalRes")
assert(!globalRes.toString.contains("300"), s"Should not contain 300 (incorrect list element)! Got: $globalRes")
}
)
}

test("Test 3: Empty vararg should resolve to empty list") {
val code = """
(define (f . x) x)
(null? (f))
"""

runAllAnalyses(buildPrg(code),
localRes => { },
globalRes => assert(globalRes.toString.contains("true"), s"Result should be true, got: $globalRes")
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ trait IncrementalModularSchemeLatticeTests extends AnyPropSpec:
for {
r1 <- res1
r2 <- res2
_ = assert(lattice.getAddresses(r1) == Set(adr1),
_ = assert(lattice.getAddresses(r1) == (lattice.getAddresses(v) + adr1),
s"Annotation set comparison for unary operation $op failed (found: ${lattice.getAddresses(r1)})."
)
_ = assert(r1.toL() == r2, s"Annotation set comparison for unary operation $op failed (found: ${r1.toL()} using $v).")
Expand All @@ -68,7 +68,7 @@ trait IncrementalModularSchemeLatticeTests extends AnyPropSpec:
for {
r1 <- res1
r2 <- res2
_ = assert(lattice.getAddresses(r1) == Set(adr1, adr2),
_ = assert(lattice.getAddresses(r1) == (lattice.getAddresses(v1) ++ lattice.getAddresses(v2) + adr1 + adr2),
s"Annotation set comparison for binary operation $op failed (found: ${lattice.getAddresses(r1)})."
)
_ = assert(r1.toL() == r2)
Expand Down