Skip to content

Commit

Permalink
When generating top-level extern methods check its annotations for `l…
Browse files Browse the repository at this point in the history
…ink`/`define` (#3604)

* When generating top-level extern methods check it's annonations for @link/@define
* Port the change to Scala2 plugin to allow for marking with @link/@define only selected methods
  • Loading branch information
WojciechMazur committed Nov 17, 2023
1 parent b79049f commit a724248
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -940,27 +940,47 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] =>
}
}
}

def genMethodAttrs(sym: Symbol, isExtern: Boolean): nir.Attrs = {
val inlineAttrs =
if (sym.isBridge || sym.hasFlag(ACCESSOR)) Seq(nir.Attr.AlwaysInline)
else Nil

val annotatedAttrs =
sym.annotations.map(_.symbol).collect {
case NoInlineClass => nir.Attr.NoInline
case AlwaysInlineClass => nir.Attr.AlwaysInline
case InlineClass => nir.Attr.InlineHint
case StubClass => nir.Attr.Stub
case NoOptimizeClass => nir.Attr.NoOpt
case NoSpecializeClass => nir.Attr.NoSpecialize
private def genMethodAttrs(
sym: Symbol,
isExtern: Boolean
): nir.Attrs = {
val attrs = Seq.newBuilder[nir.Attr]

if (sym.isBridge || sym.hasFlag(ACCESSOR))
attrs += nir.Attr.AlwaysInline
if (isExtern)
attrs += nir.Attr.Extern(sym.isBlocking || sym.owner.isBlocking)

def requireLiteralStringAnnotation(
annotation: Annotation
): Option[String] =
annotation.tree match {
case Apply(_, Seq(Literal(Constant(name: String)))) => Some(name)
case tree =>
reporter.error(
tree.pos,
s"Invalid usage of ${annotation.symbol}, expected literal constant string argument, got ${tree}"
)
None
}
val externAttrs =
if (isExtern)
Seq(nir.Attr.Extern(sym.isBlocking || sym.owner.isBlocking))
else Nil

nir.Attrs.fromSeq(inlineAttrs ++ annotatedAttrs ++ externAttrs)
sym.annotations.foreach { ann =>
ann.symbol match {
case NoInlineClass => attrs += nir.Attr.NoInline
case AlwaysInlineClass => attrs += nir.Attr.AlwaysInline
case InlineClass => attrs += nir.Attr.InlineHint
case NoOptimizeClass => attrs += nir.Attr.NoOpt
case NoSpecializeClass => attrs += nir.Attr.NoSpecialize
case StubClass => attrs += nir.Attr.Stub
case LinkClass =>
requireLiteralStringAnnotation(ann)
.foreach(attrs += nir.Attr.Link(_))
case DefineClass =>
requireLiteralStringAnnotation(ann)
.foreach(attrs += nir.Attr.Define(_))
case _ => ()
}
}
nir.Attrs.fromSeq(attrs.result())
}

def genMethodBody(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import scala.scalanative.util.unsupported
import dotty.tools.FatalError
import dotty.tools.dotc.report
import dotty.tools.dotc.core.NameKinds
import dotty.tools.dotc.core.Annotations.Annotation

trait NirGenStat(using Context) {
self: NirCodeGen =>
Expand Down Expand Up @@ -315,26 +316,43 @@ trait NirGenStat(using Context) {

private def genMethodAttrs(
sym: Symbol,
isExtern: => Boolean
isExtern: Boolean
): nir.Attrs = {
val inlineAttrs =
if (sym.is(Bridge) || sym.is(Accessor)) Seq(nir.Attr.AlwaysInline)
else Nil

val annotatedAttrs =
sym.annotations.map(_.symbol).collect {
case defnNir.NoInlineClass => nir.Attr.NoInline
case defnNir.AlwaysInlineClass => nir.Attr.AlwaysInline
case defnNir.InlineClass => nir.Attr.InlineHint
case defnNir.NoOptimizeClass => nir.Attr.NoOpt
case defnNir.NoSpecializeClass => nir.Attr.NoSpecialize
case defnNir.StubClass => nir.Attr.Stub
val attrs = Seq.newBuilder[nir.Attr]

if (sym.is(Bridge) || sym.is(Accessor))
attrs += nir.Attr.AlwaysInline
if (isExtern)
attrs += nir.Attr.Extern(sym.isBlocking || sym.owner.isBlocking)

def requireLiteralStringAnnotation(annotation: Annotation): Option[String] =
annotation.tree match {
case Apply(_, Seq(Literal(Constant(name: String)))) => Some(name)
case tree =>
report.error(
s"Invalid usage of ${annotation.symbol.show}, expected literal constant string argument, got ${tree}",
tree.srcPos
)
None
}
sym.annotations.foreach { ann =>
ann.symbol match {
case defnNir.NoInlineClass => attrs += nir.Attr.NoInline
case defnNir.AlwaysInlineClass => attrs += nir.Attr.AlwaysInline
case defnNir.InlineClass => attrs += nir.Attr.InlineHint
case defnNir.NoOptimizeClass => attrs += nir.Attr.NoOpt
case defnNir.NoSpecializeClass => attrs += nir.Attr.NoSpecialize
case defnNir.StubClass => attrs += nir.Attr.Stub
case defnNir.LinkClass =>
requireLiteralStringAnnotation(ann)
.foreach(attrs += nir.Attr.Link(_))
case defnNir.DefineClass =>
requireLiteralStringAnnotation(ann)
.foreach(attrs += nir.Attr.Define(_))
case _ => ()
}
val externAttrs = Option.when(isExtern) {
nir.Attr.Extern(sym.isBlocking || sym.owner.isBlocking)
}

nir.Attrs.fromSeq(inlineAttrs ++ annotatedAttrs ++ externAttrs)
nir.Attrs.fromSeq(attrs.result())
}

protected val curExprBuffer = ScopedVar[ExprBuffer]()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package scala.scalanative
package linker

import org.junit.Test
import org.junit.Assert._

class TopLevelExternsTest {

@Test def topLevelExternAnnotations(): Unit = {
val PackageModule = nir.Global.Top("Main$package$")
val ExternFunctionSymbol =
PackageModule.member(nir.Sig.Extern("externFunction"))

compileAndLoad(
"Main.scala" -> """
|import scala.scalanative.unsafe.{link, define, extern}
|@extern
|@link("MyCustomLink")
|@define("MyCustomDefn")
|def externFunction(): Unit = extern
""".stripMargin
) { defns =>
defns
.find(_.name == ExternFunctionSymbol)
.orElse { fail("Not found extern function definition"); None }
.foreach { defn =>
assertTrue("isExtern", defn.attrs.isExtern)
assertEquals(
"link",
Some(nir.Attr.Link("MyCustomLink")),
defn.attrs.links.headOption
)
assertEquals(
"define",
Some(nir.Attr.Define("MyCustomDefn")),
defn.attrs.preprocessorDefinitions.headOption
)
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package scala.scalanative
package linker

import org.junit.Test
import org.junit.Assert._

class MethodAttributesTest {

@Test def explicitLinkOrDefine(): Unit = {
compileAndLoad(
"Test.scala" ->
"""
|import scala.scalanative.unsafe.{extern, link, define}
|@link("common-lib")
|@define("common-define")
|@extern object Foo {
| @link("custom-lib") def withLink(): Int = extern
| @define("custom-define") def withDefine(): Int = extern
| def default(): Int = extern
|}
""".stripMargin
) { defns =>
val Module = nir.Global.Top("Foo$")
val WithLinkMethod = Module.member(nir.Sig.Extern("withLink"))
val WithDefineMethod = Module.member(nir.Sig.Extern("withDefine"))
val DefaultMethod = Module.member(nir.Sig.Extern("default"))

val CommonLink = nir.Attr.Link("common-lib")
val CustomLink = nir.Attr.Link("custom-lib")
val CommonDefine = nir.Attr.Define("common-define")
val CustomDefine = nir.Attr.Define("custom-define")

val expected =
Seq(Module, WithLinkMethod, WithDefineMethod, DefaultMethod)
val found = defns.filter { defn =>
def checkLink(value: nir.Attr.Link, expected: Boolean) = assertEquals(
s"${defn.name} - ${value}",
expected,
defn.attrs.links.contains(value)
)
def checkDefine(value: nir.Attr.Define, expected: Boolean) =
assertEquals(
s"${defn.name} - ${value}",
expected,
defn.attrs.preprocessorDefinitions.contains(value)
)

defn.name match {
case Module =>
checkLink(CommonLink, true)
checkLink(CustomLink, false)
checkDefine(CommonDefine, true)
checkDefine(CustomDefine, false)
case WithLinkMethod =>
checkLink(CommonLink, false) // defined in module
checkLink(CustomLink, true)
checkDefine(CommonDefine, false) // defined in module
checkDefine(CustomDefine, false)
case WithDefineMethod =>
checkLink(CommonLink, false) // defined in module
checkLink(CustomLink, false)
checkDefine(CommonDefine, false) // defined in module
checkDefine(CustomDefine, true)
case DefaultMethod =>
checkLink(CommonLink, false) // defined in module
checkLink(CustomLink, false)
checkDefine(CommonDefine, false) // defined in module
checkDefine(CustomDefine, false)
case _ => ()
}
expected.contains(defn.name)
}
assertTrue(
s"not found some defns, ${found.map(_.name)}",
found.size == expected.size
)
}
}

}

0 comments on commit a724248

Please sign in to comment.