Skip to content

Commit

Permalink
feature: Allow to create overload of extern method (#3809)
Browse files Browse the repository at this point in the history
* Allow to create overload of extern method using a method that is only calling the method with the same name, but with different set of arguments
* Fix generation of extern methods in object extending extern trait
* Fix generation of bridge extern methods forwarders
  • Loading branch information
WojciechMazur committed Mar 6, 2024
1 parent cd2c477 commit 082b204
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 64 deletions.
33 changes: 32 additions & 1 deletion clib/src/main/scala/scala/scalanative/libc/stdlib.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package scala.scalanative
package libc

import scalanative.unsafe._
import scala.scalanative.unsafe._
import scala.scalanative.unsigned._

@extern object stdlib extends stdlib

Expand All @@ -15,6 +16,20 @@ import scalanative.unsafe._
def free(ptr: CVoidPtr): Unit = extern
def aligned_alloc(alignment: CSize, size: CSize): Unit = extern

def malloc(size: Int): Ptr[Byte] = malloc(size.toCSize)
def malloc(size: Long): Ptr[Byte] = malloc(size.toCSize)
def calloc(num: Int, size: Int): Ptr[Byte] = calloc(num.toCSize, size.toCSize)
def calloc(num: Long, size: Long): Ptr[Byte] =
calloc(num.toCSize, size.toCSize)
def realloc[T](ptr: Ptr[T], newSize: Int): Ptr[T] =
realloc(ptr, newSize.toCSize)
def realloc[T](ptr: Ptr[T], newSize: Long): Ptr[T] =
realloc(ptr, newSize.toCSize)
def aligned_alloc(alignment: Int, size: Int): Unit =
aligned_alloc(alignment.toCSize, size.toCSize)
def aligned_alloc(alignment: Long, size: Long): Unit =
aligned_alloc(alignment.toCSize, size.toCSize)

// Program utilities

def abort(): Unit = extern
Expand All @@ -33,6 +48,7 @@ import scalanative.unsafe._

def rand(): CInt = extern
def srand(seed: CUnsignedInt): Unit = extern
def srand(seed: Int): Unit = srand(seed.toUInt)

// Conversions to numeric formats

Expand Down Expand Up @@ -68,13 +84,28 @@ import scalanative.unsafe._
comparator: CFuncPtr2[CVoidPtr, CVoidPtr, CInt]
): Unit = extern

def bsearch(
key: CVoidPtr,
data: CVoidPtr,
num: Int,
size: Int,
comparator: CFuncPtr2[CVoidPtr, CVoidPtr, CInt]
): Unit = bsearch(key, data, num.toCSize, size.toCSize, comparator)

def qsort[T](
data: Ptr[T],
num: CSize,
size: CSize,
comparator: CFuncPtr2[CVoidPtr, CVoidPtr, CInt]
): Unit = extern

def qsort[T](
data: Ptr[T],
num: Int,
size: Int,
comparator: CFuncPtr2[CVoidPtr, CVoidPtr, CInt]
): Unit = qsort(data, num.toCSize, size.toCSize, comparator)

// Macros

@name("scalanative_exit_success")
Expand Down
2 changes: 1 addition & 1 deletion nir/src/main/scala/scala/scalanative/nir/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ object Type {
unreachable
}.toSeq

private def isBoxOf(primitiveType: Type)(boxType: Type) =
private[scalanative] def isBoxOf(primitiveType: Type)(boxType: Type) =
unbox
.get(normalize(boxType))
.contains(primitiveType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -845,26 +845,19 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] =>
def genExternMethod(
attrs: nir.Attrs,
name: nir.Global.Member,
origSig: nir.Type,
origSig: nir.Type.Function,
dd: DefDef
): Option[nir.Defn] = {
val rhs = dd.rhs
def externMethodDecl() = {
val externSig = genExternMethodSig(curMethodSym)
def externMethodDecl(methodSym: Symbol) = {
val externSig = genExternMethodSig(methodSym)
val externDefn = nir.Defn.Declare(attrs, name, externSig)(rhs.pos)

Some(externDefn)
}

def isCallingExternMethod(sym: Symbol) =
sym.isExtern

def isExternMethodAlias(target: Symbol) =
(name, genName(target)) match {
case (nir.Global.Member(_, lsig), nir.Global.Member(_, rsig)) =>
lsig == rsig
case _ => false
}
val defaultArgs = dd.symbol.paramss.flatten.filter(_.hasDefault)
rhs match {
case _ if defaultArgs.nonEmpty =>
Expand All @@ -874,21 +867,58 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] =>
)
None
case Apply(ref: RefTree, Seq()) if ref.symbol == ExternMethod =>
externMethodDecl()
externMethodDecl(curMethodSym.get)

case _ if curMethodSym.hasFlag(ACCESSOR) => None

case Apply(target, _) if isCallingExternMethod(target.symbol) =>
val sym = target.symbol
if (isExternMethodAlias(sym)) externMethodDecl()
else {
val (hasSameName: Boolean, hasMatchingSignature: Boolean) =
(name, genName(sym)) match {
case (nir.Global.Member(_, lsig), nir.Global.Member(_, rsig)) =>
val nameMatch = lsig == rsig
val sigMatch = {
val externSig = genExternMethodSig(sym)
externSig == origSig || {
val nir.Type.Function(externArgs, externRet) = externSig
val nir.Type.Function(origArgs, origRet) = origSig
val usesVarArgs =
externArgs.nonEmpty && externArgs.last == nir.Type.Vararg
val argsMatch =
if (usesVarArgs)
externArgs.size == origArgs.size && externArgs.init == origArgs.init
else
externArgs == origArgs
val retTyMatch = externRet == origRet ||
nir.Type.isBoxOf(externRet)(origRet)
argsMatch && retTyMatch
}
}
(nameMatch, sigMatch)
case _ => (false, false)
}
def isExternMethodForwarder = hasSameName && hasMatchingSignature
def isExternMethodRuntimeOverload =
hasSameName && !hasMatchingSignature
if (isExternMethodForwarder) externMethodDecl(target.symbol)
else if (isExternMethodRuntimeOverload) {
dd.symbol.addAnnotation(NonExternClass)
genMethod(dd)
} else {
reporter.error(
target.pos,
"Referencing other extern symbols in not supported"
)
None
}

case Apply(target @ Select(Super(_, _), _), _)
if dd.symbol.isSynthetic && dd.symbol.isBridge &&
target.symbol.name == dd.symbol.name &&
genMethodSig(target.symbol) == origSig =>
dd.symbol.addAnnotation(NonExternClass)
genMethod(dd)

case _ =>
reporter.error(
rhs.pos,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,11 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] =>

private def genMethodSigParamsImpl(
sym: Symbol,
isExtern: Boolean
isExternHint: Boolean
): Seq[nir.Type] = {
val params = sym.tpe.params
if (!isExtern && !sym.isExtern)
val isExtern = isExternHint || sym.isExtern
if (!isExtern)
params.map { p => genType(p.tpe) }
else {
val wereRepeated = exitingPhase(currentRun.typerPhase) {
Expand All @@ -259,8 +260,9 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] =>
}.toMap

params.map { p =>
if (wereRepeated(p.name)) nir.Type.Vararg
else genExternType(p.tpe)
if (isExtern && wereRepeated(p.name)) nir.Type.Vararg
else if (isExtern) genExternType(p.tpe)
else genType(p.tpe)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ trait NirGenStat(using Context) {

private def genMethod(dd: DefDef): Option[nir.Defn] = {
implicit val pos: nir.SourcePosition = dd.span
if (pos.isEmpty) println(dd.name -> dd.span)
val fresh = nir.Fresh()
val freshScope = initFreshScope(dd.rhs)
val scopes = mutable.Set.empty[DebugInfo.LexicalScope]
Expand Down Expand Up @@ -592,13 +591,13 @@ trait NirGenStat(using Context) {
def genExternMethod(
attrs: nir.Attrs,
name: nir.Global.Member,
origSig: nir.Type,
origSig: nir.Type.Function,
dd: DefDef
): Option[nir.Defn] = {
val rhs: Tree = dd.rhs
given nir.SourcePosition = rhs.span
def externMethodDecl() = {
val externSig = genExternMethodSig(curMethodSym)
def externMethodDecl(methodSym: Symbol) = {
val externSig = genExternMethodSig(methodSym)
val externDefn = nir.Defn.Declare(attrs, name, externSig)
Some(externDefn)
}
Expand All @@ -616,18 +615,37 @@ trait NirGenStat(using Context) {
report.error("extern method cannot have default argument")
None

case ApplyExtern() => externMethodDecl()
case ApplyExtern() => externMethodDecl(curMethodSym.get)

case _ if curMethodSym.get.isOneOf(Accessor | Synthetic) => None
case _
if curMethodSym.get.isOneOf(Accessor | Synthetic, butNot = Bridge) =>
None

case Apply(target, args) if target.symbol.isExtern =>
val sym = target.symbol
val nir.Global.Member(_, selfSig) = name: @unchecked
def isExternMethodForwarder =
genExternSig(sym) == selfSig &&
genExternMethodSig(sym) == origSig

if isExternMethodForwarder then externMethodDecl()
val hasSameName = genExternSig(sym).mangle == selfSig.mangle
val externSig = genExternMethodSig(sym)
val hasMatchingSignature = externSig == origSig || {
val nir.Type.Function(externArgs, externRet) = externSig
val nir.Type.Function(origArgs, origRet) = origSig
val usesVarArgs =
externArgs.nonEmpty && externArgs.last == nir.Type.Vararg
val argsMatch =
if (usesVarArgs)
externArgs.size == origArgs.size && externArgs.init == origArgs.init
else
externArgs == origArgs
val retTyMatch =
externRet == origRet || nir.Type.isBoxOf(externRet)(origRet)
argsMatch && retTyMatch
}
def isExternMethodForwarder = hasSameName && hasMatchingSignature
def isExternMethodRuntimeOverload = hasSameName && !hasMatchingSignature
if isExternMethodForwarder then externMethodDecl(target.symbol)
else if isExternMethodRuntimeOverload then
dd.symbol.addAnnotation(defnNir.NonExternClass)
return genMethod(dd)
else {
report.error(
"Referencing other extern symbols in not supported",
Expand All @@ -636,6 +654,13 @@ trait NirGenStat(using Context) {
None
}

case Apply(target @ Select(Super(_, _), _), _)
if dd.symbol.isAllOf(Synthetic | Bridge) &&
target.symbol.name == dd.symbol.name &&
genMethodSig(target.symbol) == origSig =>
dd.symbol.addAnnotation(defnNir.NonExternClass)
genMethod(dd)

case _ =>
report.error(
s"methods in extern objects must have extern body",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ trait NirGenType(using Context) {
sym.owner.isExternType ||
sym.hasAnnotation(defnNir.ExternClass) ||
(sym.is(Accessor) && sym.field.isExtern)
} && !sym.hasAnnotation(
defnNir.NonExternClass
) // Added in PrepNativeInterop
// NonExtern is added PrepNativeInterop
} && !sym.hasAnnotation(defnNir.NonExternClass)

def isExtensionMethod: Boolean =
sym.flags.isAllOf(Extension | Method) || {
Expand Down Expand Up @@ -347,10 +346,11 @@ trait NirGenType(using Context) {

private def genMethodSigParamsImpl(
sym: Symbol,
isExtern: Boolean
isExternHint: Boolean
)(using Context): Seq[nir.Type] = {
import core.Phases._
val repeatedParams = if (sym.isExtern) {
val isExtern = isExternHint || sym.isExtern
val repeatedParams = if (isExtern) {
atPhase(typerPhase) {
sym.paramInfo.stripPoly match {
// @extern def foo(a: Int): Int
Expand Down
27 changes: 20 additions & 7 deletions nscplugin/src/test/scala/scala/scalanative/NIRCompilerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ class NIRCompilerTest {
)
}

@Test def externMemberOverload(): Unit = {
val code =
"""import scala.scalanative.unsafe.extern
|
|@extern object Dummy {
| def foo(v: Long): Int = extern
| def foo(v: Int): Int = foo(v.toLong)
|}
|
|""".stripMargin

NIRCompiler(_.compile(code))
}

@Test def externExternTrait(): Unit = {
val code =
"""import scala.scalanative.unsafe.extern
Expand Down Expand Up @@ -176,13 +190,12 @@ class NIRCompilerTest {
() => NIRCompiler(_.compile(code))
)

assertTrue(
err
.getMessage()
.contains(
"Extern object can only extend extern traits"
)
)
// Order of error might differ
val expectedMsg =
if (scalaVersion.startsWith("3."))
"methods in extern objects must have extern body"
else "Extern object can only extend extern traits"
assertTrue(err.getMessage().contains(expectedMsg))
}

@Test def mixExternObjectWithNonExternClass(): Unit = {
Expand Down

0 comments on commit 082b204

Please sign in to comment.