Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix runtime problems on MacOS using M1 chips #2579

Merged
merged 12 commits into from Mar 2, 2022
9 changes: 8 additions & 1 deletion javalib/src/main/scala/java/lang/System.scala
Expand Up @@ -71,7 +71,14 @@ object System {
} else {
sysProps.setProperty("file.separator", "/")
sysProps.setProperty("path.separator", ":")
sysProps.setProperty("java.io.tmpdir", "/tmp")
// MacOS uses TMPDIR to specify tmp directory, other formats are also used in the Unix system
def env(name: String): Option[String] = Option(envVars.get(name))
val tmpDirectory = env("TMPDIR")
.orElse(env("TEMPDIR"))
.orElse(env("TMP"))
.orElse(env("TEMP"))
.getOrElse("/tmp")
sysProps.setProperty("java.io.tmpdir", tmpDirectory)
}

sysProps
Expand Down
14 changes: 11 additions & 3 deletions javalib/src/main/scala/java/nio/file/Files.scala
Expand Up @@ -64,9 +64,10 @@ object Files {
else throw new UnsupportedOperationException()

val targetFile = target.toFile()
val targetExists = targetFile.exists()

val out =
if (!targetFile.exists() || (targetFile.isFile() && replaceExisting)) {
if (!targetExists || (targetFile.isFile() && replaceExisting)) {
new FileOutputStream(targetFile, append = false)
} else if (targetFile.isDirectory() &&
targetFile.list().isEmpty &&
Expand All @@ -81,8 +82,15 @@ object Files {
throw new FileAlreadyExistsException(targetFile.getAbsolutePath())
}

try copy(in, out)
finally out.close()
try {
val copyResult = copy(in, out)
// Make sure that created file has correct permissions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On MacOS M1 the files created by copying were given empty permissions, this should match the permission of files created by the JVM

if (!targetExists) {
targetFile.setReadable(true, ownerOnly = false)
targetFile.setWritable(true, ownerOnly = true)
}
copyResult
} finally out.close()
}

def copy(source: Path, out: OutputStream): Long = {
Expand Down
Expand Up @@ -7,13 +7,21 @@
#include <sys/wait.h>
#include <sys/time.h>
#include <unordered_map>
#ifdef __APPLE__
// Semaphores on OSX are deprectated
#include <dispatch/dispatch.h>
#else
#include <semaphore.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

// The +1 accounts for the null char at the end of the name
#ifdef __APPLE__
#include <sys/posix_sem.h>
#define SEM_MAX_LENGTH PSEMNAMLEN + 1
#else
#include <limits.h>
#define SEM_MAX_LENGTH _POSIX_PATH_MAX + 1
Comment on lines +18 to +24
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is and creation of semaphore is copy-pasted from Phase.c in Commix impl. In both cases this is needed due to problems on MacOs - usage of sem_create would return -1 and errno would mention not implemented error.
Also because previous dispatch_semaphore_t was leading to deadlocks on Arm64 we do replace both linux and mac implementation with shared named semaphore

#endif

struct Monitor {
Expand All @@ -33,24 +41,16 @@ struct Monitor {
static pthread_mutex_t shared_mutex;
static std::unordered_map<int, std::shared_ptr<Monitor>> waiting_procs;
static std::unordered_map<int, int> finished_procs;
#ifdef __APPLE__
static dispatch_semaphore_t active_procs;
#else
static sem_t *active_procs;
#endif

static void *wait_loop(void *arg) {
while (1) {

// Wait until there is at least 1 active process
#ifdef __APPLE__
dispatch_semaphore_wait(active_procs, DISPATCH_TIME_FOREVER);
#else
// Wait until there is at least 1 active process
int wait_result;
do {
wait_result = sem_wait(active_procs);
} while (wait_result == -1 && errno == EINTR);
#endif

int status;
const int pid = waitpid(-1, &status, 0);
Expand Down Expand Up @@ -87,13 +87,7 @@ static int check_result(const int pid, pthread_mutex_t *lock) {

extern "C" {
/* Notify process monitor about spawning new process */
void scalanative_process_monitor_notify() {
#ifdef __APPLE__
dispatch_semaphore_signal(active_procs);
#else
sem_post(active_procs);
#endif
}
void scalanative_process_monitor_notify() { sem_post(active_procs); }

int scalanative_process_monitor_check_result(const int pid) {
pthread_mutex_lock(&shared_mutex);
Expand Down Expand Up @@ -128,14 +122,22 @@ int scalanative_process_monitor_wait_for_pid(const int pid, timespec *ts,
void scalanative_process_monitor_init() {
pthread_t thread;
pthread_mutex_init(&shared_mutex, NULL);
#ifdef __APPLE__
active_procs = dispatch_semaphore_create(0);
#else
// place semaphore in shared memory (needed for arm64)
active_procs = (sem_t *)mmap(NULL, sizeof(sem_t *), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
sem_init(active_procs, 1, 0);
#endif

// MacOs might not allow for usage of anonymous semaphores (not implemented
// on M1 chips) leading to deadlocks
char semaphoreName[SEM_MAX_LENGTH];
snprintf(semaphoreName, SEM_MAX_LENGTH, "__sn_%d-process-monitor",
getpid());
active_procs = sem_open(semaphoreName, O_CREAT | O_EXCL, 0644, 0);
if (active_procs == SEM_FAILED) {
perror("Failed to create or open process monitor semaphore");
}
// Delete semaphore on exit
if (sem_unlink(semaphoreName) != 0) {
fprintf(stderr, "Unlinking process monitor semaphore failed\n");
exit(errno);
}

pthread_create(&thread, NULL, wait_loop, NULL);
pthread_detach(thread);
}
Expand Down
Expand Up @@ -71,6 +71,8 @@ object CVarArgList {
)(implicit z: Zone): CVarArgList = {
if (isWindows)
toCVarArgList_X86_64_Windows(varargs)
else if (PlatformExt.isArm64 && Platform.isMac())
toCVarArgList_Arm64_MacOS(varargs)
else
toCVarArgList_Unix(varargs)
}
Expand Down Expand Up @@ -216,4 +218,48 @@ object CVarArgList {
new CVarArgList(resultStorage)
}

private def toCVarArgList_Arm64_MacOS(
varargs: Seq[CVarArg]
)(implicit z: Zone) = {
val alignedArgs = varargs.map { arg =>
arg.value match {
case value: Byte =>
value.toLong: CVarArg
case value: Short =>
value.toLong: CVarArg
case value: Int =>
value.toLong: CVarArg
case value: UByte =>
value.toULong: CVarArg
case value: UShort =>
value.toULong: CVarArg
case value: UInt =>
value.toULong: CVarArg
case value: Float =>
value.toDouble: CVarArg
case o => arg
}
}
Comment on lines +221 to +242
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation is exactly the same as for the x86 (in 32bit support branch), the only difference comes from mapping to Int64 instead of Int32


var totalSize = 0.toULong
alignedArgs.foreach { vararg =>
val tag = vararg.tag
totalSize = Tag.align(totalSize, tag.alignment) + tag.size
}

val argListStorage = z.alloc(totalSize).asInstanceOf[Ptr[Byte]]
var currentIndex = 0.toULong
alignedArgs.foreach { vararg =>
val tag = vararg.tag
currentIndex = Tag.align(currentIndex, tag.alignment)
tag.store(
(argListStorage + currentIndex).asInstanceOf[Ptr[Any]],
vararg.value
)
currentIndex += tag.size
}

new CVarArgList(toRawPtr(argListStorage))
}

}
10 changes: 10 additions & 0 deletions posixlib/src/main/resources/scala-native/fcntl.c
Expand Up @@ -75,4 +75,14 @@ int scalanative_fcntl(int fd, int cmd, struct scalanative_flock *flock_struct) {
return fcntl(fd, cmd, &flock_buf);
}

// On MacOS Arm64 it is defined as macro taking varargs delegating to _fcntl
int scalanative_fcntl_i(int fd, int cmd, int flags) {
return fcntl(fd, cmd, flags);
}

// On MacOS Arm64 is's defined as macro taking varargs delagating to _open
int scalanative_open_m(const char *pathname, int flags, mode_t mode) {
return open(pathname, flags, mode);
}

#endif // Unix or Mac OS
2 changes: 2 additions & 0 deletions posixlib/src/main/scala/scala/scalanative/posix/fcntl.scala
Expand Up @@ -11,8 +11,10 @@ object fcntl {

def open(pathname: CString, flags: CInt): CInt = extern

@name("scalanative_open_m")
def open(pathname: CString, flags: CInt, mode: mode_t): CInt = extern

@name("scalanative_fcntl_i")
def fcntl(fd: CInt, cmd: CInt, flags: CInt): CInt = extern

@name("scalanative_fcntl")
Expand Down
Expand Up @@ -8,6 +8,7 @@ import java.io.IOException

import org.scalanative.testsuite.utils.Platform
import scala.scalanative.meta.LinktimeInfo.isWindows
import scala.scalanative.runtime.PlatformExt

import scalanative.libc.{errno => libcErrno, string}
import scala.scalanative.unsafe._
Expand Down Expand Up @@ -328,7 +329,7 @@ class TimeTest {
// different time zone.

val cp =
if (Platform.isFreeBSD)
if (Platform.isFreeBSD || (Platform.isMacOs && PlatformExt.isArm64))
strptime(c"Fri Mar 31 14:47:44 2017", c"%a %b %d %T %Y", tmPtr)
else
strptime(
Expand Down
Expand Up @@ -17,25 +17,36 @@ object ExternTest {
*/
@extern
object Ext1 {
def snprintf(buf: CString, size: CSize, format: CString, l: CString): Int =
// Previously snprintf method was used here, however on MacOs Arm64
// it is defined as a macro and takes some additional implicit arguments
def vsnprintf(
buf: CString,
size: CSize,
format: CString,
args: CVarArgList
): Int =
extern
}
@extern
object Ext2 {
@name("snprintf")
def p(buf: CString, size: CSize, format: CString, i: Int): Int = extern
@name("vsnprintf")
def p(buf: CString, size: CSize, format: CString, args: CVarArgList): Int =
extern
}

// workaround for CI
def runTest(): Unit = {
def runTest(): Unit = Zone { implicit z: Zone =>
import scalanative.libc.string
val bufsize = 10.toUInt
val buf1: Ptr[Byte] = stackalloc[Byte](bufsize)
val buf2: Ptr[Byte] = stackalloc[Byte](bufsize)
Ext1.snprintf(buf1, bufsize, c"%s", c"hello")
assertTrue(string.strcmp(buf1, c"hello") == 0)
Ext2.p(buf2, bufsize, c"%d", 1)
assertTrue(string.strcmp(buf2, c"1") == 0)

val arg1 = c"hello"
Ext1.vsnprintf(buf1, bufsize, c"%s", toCVarArgList(arg1))
assertEquals("case 1", 0, string.strcmp(buf1, arg1))

Ext2.p(buf2, bufsize, c"%d", toCVarArgList(1))
assertEquals("case 2", 0, string.strcmp(buf2, c"1"))
}
}

Expand Down