mirror of
https://github.com/autc04/Retro68.git
synced 2026-10-24 11:02:35 +00:00
343 lines
18 KiB
C
343 lines
18 KiB
C
/*
|
|
Copyright 2026 Wolfgang Thaller.
|
|
|
|
This file is part of Retro68.
|
|
|
|
Retro68 is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Retro68 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* Out-of-line implementations of the __atomic_* helper functions that GCC's C
|
|
* and C++ front ends emit for operations the target cannot perform inline.
|
|
*
|
|
* Classic Mac OS on both 68K and PowerPC is a cooperatively scheduled,
|
|
* single-threaded system: a task is only switched out at an explicit blocking
|
|
* call, so a plain read-modify-write sequence cannot be interleaved with
|
|
* another task. These helpers are therefore ordinary C expressions, without
|
|
* locks or interrupt-disabling critical sections.
|
|
*
|
|
* They are NOT safe to call from interrupt handlers. Interrupt handlers may
|
|
* only use naturally aligned loads and stores of at most four bytes, which GCC
|
|
* emits inline and which never reach this file. See
|
|
* docs/ai/2026-10-04-atomic-helpers.md.
|
|
*
|
|
* GCC recognises both the unsuffixed and the size-suffixed __atomic_* names as
|
|
* built-ins; defining functions with those exact C identifiers produces
|
|
* "conflicting types for built-in function" warnings. As GNU libatomic does,
|
|
* the implementations are defined under internal names and the required
|
|
* symbols are attached with asm labels.
|
|
*/
|
|
|
|
/* string.h first: on case-insensitive file systems it can pull in Apple's
|
|
Strings.h -> MacTypes.h, whose true/false enum clashes with stdbool.h. */
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#ifndef __GCC_ATOMIC_TEST_AND_SET_TRUEVAL
|
|
#define __GCC_ATOMIC_TEST_AND_SET_TRUEVAL 1
|
|
#endif
|
|
|
|
/* Arithmetic/bitwise operations used by the fetch_OP_N and OP_fetch_N
|
|
families. The caller casts the result back to the operand type, which also
|
|
takes care of the integer promotion of the narrow types. */
|
|
#define RETRO68_OP_ADD(a, b) ((a) + (b))
|
|
#define RETRO68_OP_SUB(a, b) ((a) - (b))
|
|
#define RETRO68_OP_AND(a, b) ((a) & (b))
|
|
#define RETRO68_OP_OR(a, b) ((a) | (b))
|
|
#define RETRO68_OP_XOR(a, b) ((a) ^ (b))
|
|
#define RETRO68_OP_NAND(a, b) (~((a) & (b)))
|
|
|
|
/* __atomic_fetch_OP_N: apply OP to *ptr, returning the previous value.
|
|
__atomic_OP_fetch_N: apply OP to *ptr, returning the new value. */
|
|
#define RETRO68_FETCH_OP(SZ, TYPE, OP, NAME) \
|
|
TYPE retro68_atomic_fetch_##NAME##_##SZ (TYPE *, TYPE, int) \
|
|
__asm__ ("__atomic_fetch_" #NAME "_" #SZ); \
|
|
TYPE retro68_atomic_fetch_##NAME##_##SZ (TYPE *ptr, TYPE value, int model) \
|
|
{ \
|
|
(void) model; \
|
|
TYPE old = *ptr; \
|
|
*ptr = (TYPE) (OP (old, value)); \
|
|
return old; \
|
|
} \
|
|
\
|
|
TYPE retro68_atomic_##NAME##_fetch_##SZ (TYPE *, TYPE, int) \
|
|
__asm__ ("__atomic_" #NAME "_fetch_" #SZ); \
|
|
TYPE retro68_atomic_##NAME##_fetch_##SZ (TYPE *ptr, TYPE value, int model) \
|
|
{ \
|
|
(void) model; \
|
|
TYPE result = (TYPE) (OP (*ptr, value)); \
|
|
*ptr = result; \
|
|
return result; \
|
|
}
|
|
|
|
#define RETRO68_ALL_FETCH_OPS(SZ, TYPE) \
|
|
RETRO68_FETCH_OP (SZ, TYPE, RETRO68_OP_ADD, add) \
|
|
RETRO68_FETCH_OP (SZ, TYPE, RETRO68_OP_SUB, sub) \
|
|
RETRO68_FETCH_OP (SZ, TYPE, RETRO68_OP_AND, and) \
|
|
RETRO68_FETCH_OP (SZ, TYPE, RETRO68_OP_OR, or) \
|
|
RETRO68_FETCH_OP (SZ, TYPE, RETRO68_OP_XOR, xor) \
|
|
RETRO68_FETCH_OP (SZ, TYPE, RETRO68_OP_NAND, nand)
|
|
|
|
/* __atomic_load_N / __atomic_store_N. */
|
|
#define RETRO68_LOAD_STORE(SZ, TYPE) \
|
|
TYPE retro68_atomic_load_##SZ (TYPE *, int) \
|
|
__asm__ ("__atomic_load_" #SZ); \
|
|
TYPE retro68_atomic_load_##SZ (TYPE *ptr, int model) \
|
|
{ \
|
|
(void) model; \
|
|
return *ptr; \
|
|
} \
|
|
\
|
|
void retro68_atomic_store_##SZ (TYPE *, TYPE, int) \
|
|
__asm__ ("__atomic_store_" #SZ); \
|
|
void retro68_atomic_store_##SZ (TYPE *ptr, TYPE value, int model) \
|
|
{ \
|
|
(void) model; \
|
|
*ptr = value; \
|
|
}
|
|
|
|
/* __atomic_exchange_N: store value and return the previous contents. */
|
|
#define RETRO68_EXCHANGE(SZ, TYPE) \
|
|
TYPE retro68_atomic_exchange_##SZ (TYPE *, TYPE, int) \
|
|
__asm__ ("__atomic_exchange_" #SZ); \
|
|
TYPE retro68_atomic_exchange_##SZ (TYPE *ptr, TYPE value, int model) \
|
|
{ \
|
|
(void) model; \
|
|
TYPE old = *ptr; \
|
|
*ptr = value; \
|
|
return old; \
|
|
}
|
|
|
|
/* __atomic_compare_exchange_N: if *ptr == *expected, store desired and return
|
|
true; otherwise write the actual value back to *expected and return false.
|
|
GCC drops the "weak" argument before calling this function. */
|
|
#define RETRO68_COMPARE_EXCHANGE(SZ, TYPE) \
|
|
bool retro68_atomic_compare_exchange_##SZ (TYPE *, TYPE *, TYPE, int, int) \
|
|
__asm__ ("__atomic_compare_exchange_" #SZ); \
|
|
bool retro68_atomic_compare_exchange_##SZ (TYPE *ptr, TYPE *expected, \
|
|
TYPE desired, int success_model, \
|
|
int failure_model) \
|
|
{ \
|
|
(void) success_model; \
|
|
(void) failure_model; \
|
|
if (*ptr == *expected) \
|
|
{ \
|
|
*ptr = desired; \
|
|
return true; \
|
|
} \
|
|
*expected = *ptr; \
|
|
return false; \
|
|
}
|
|
|
|
#define RETRO68_DEFINE_SIZE(SZ, TYPE) \
|
|
RETRO68_LOAD_STORE (SZ, TYPE) \
|
|
RETRO68_EXCHANGE (SZ, TYPE) \
|
|
RETRO68_COMPARE_EXCHANGE (SZ, TYPE) \
|
|
RETRO68_ALL_FETCH_OPS (SZ, TYPE)
|
|
|
|
RETRO68_DEFINE_SIZE (1, uint8_t)
|
|
RETRO68_DEFINE_SIZE (2, uint16_t)
|
|
RETRO68_DEFINE_SIZE (4, uint32_t)
|
|
RETRO68_DEFINE_SIZE (8, uint64_t)
|
|
|
|
/* __atomic_test_and_set_N: set *ptr to the target's true value and return
|
|
whether it was previously non-zero. GCC only emits the one-byte form; the
|
|
wider forms operate on the first byte, as in libatomic. */
|
|
bool retro68_atomic_test_and_set_1 (uint8_t *, int)
|
|
__asm__ ("__atomic_test_and_set_1");
|
|
bool retro68_atomic_test_and_set_1 (uint8_t *ptr, int model)
|
|
{
|
|
(void) model;
|
|
uint8_t old = *ptr;
|
|
*ptr = (uint8_t) __GCC_ATOMIC_TEST_AND_SET_TRUEVAL;
|
|
return old != 0;
|
|
}
|
|
|
|
#define RETRO68_TEST_AND_SET_WIDE(SZ, TYPE) \
|
|
bool retro68_atomic_test_and_set_##SZ (TYPE *, int) \
|
|
__asm__ ("__atomic_test_and_set_" #SZ); \
|
|
bool retro68_atomic_test_and_set_##SZ (TYPE *ptr, int model) \
|
|
{ \
|
|
return retro68_atomic_test_and_set_1 ((uint8_t *) ptr, model); \
|
|
}
|
|
|
|
RETRO68_TEST_AND_SET_WIDE (2, uint16_t)
|
|
RETRO68_TEST_AND_SET_WIDE (4, uint32_t)
|
|
RETRO68_TEST_AND_SET_WIDE (8, uint64_t)
|
|
|
|
/* Generic-size entry points, used for unaligned objects, non-power-of-two
|
|
sizes and types without a matching integer mode. On a single-threaded
|
|
system these are plain byte copies. */
|
|
void retro68_atomic_load_n (size_t, void *, void *, int)
|
|
__asm__ ("__atomic_load");
|
|
void retro68_atomic_load_n (size_t n, void *ptr, void *ret, int model)
|
|
{
|
|
(void) model;
|
|
memcpy (ret, ptr, n);
|
|
}
|
|
|
|
void retro68_atomic_store_n (size_t, void *, void *, int)
|
|
__asm__ ("__atomic_store");
|
|
void retro68_atomic_store_n (size_t n, void *ptr, void *value, int model)
|
|
{
|
|
(void) model;
|
|
memcpy (ptr, value, n);
|
|
}
|
|
|
|
void retro68_atomic_exchange_n (size_t, void *, void *, void *, int)
|
|
__asm__ ("__atomic_exchange");
|
|
void retro68_atomic_exchange_n (size_t n, void *ptr, void *value, void *ret,
|
|
int model)
|
|
{
|
|
(void) model;
|
|
memcpy (ret, ptr, n);
|
|
memcpy (ptr, value, n);
|
|
}
|
|
|
|
bool retro68_atomic_compare_exchange_n (size_t, void *, void *, void *, int, int)
|
|
__asm__ ("__atomic_compare_exchange");
|
|
bool retro68_atomic_compare_exchange_n (size_t n, void *ptr, void *expected,
|
|
void *desired, int success_model,
|
|
int failure_model)
|
|
{
|
|
(void) success_model;
|
|
(void) failure_model;
|
|
if (memcmp (ptr, expected, n) == 0)
|
|
{
|
|
memcpy (ptr, desired, n);
|
|
return true;
|
|
}
|
|
memcpy (expected, ptr, n);
|
|
return false;
|
|
}
|
|
|
|
/* __atomic_is_lock_free: report whether operations of the given size are
|
|
implemented with lock-free instructions. This mirrors what the compiler
|
|
itself inlines, so it is false for exactly those sizes that are routed
|
|
through this file.
|
|
|
|
Note that libretrocrt is compiled once, with the default (68000) multilib,
|
|
even when the application is linked for 68020/68040. The answer is
|
|
therefore conservative: it may report false for a size that a later CPU
|
|
would implement inline. Returning false when an operation actually is
|
|
lock-free is permitted and safe. */
|
|
bool retro68_atomic_is_lock_free (size_t, void *)
|
|
__asm__ ("__atomic_is_lock_free");
|
|
bool retro68_atomic_is_lock_free (size_t n, void *ptr)
|
|
{
|
|
(void) ptr;
|
|
switch (n)
|
|
{
|
|
case 1: return __atomic_always_lock_free (1, 0);
|
|
case 2: return __atomic_always_lock_free (2, 0);
|
|
case 4: return __atomic_always_lock_free (4, 0);
|
|
case 8: return __atomic_always_lock_free (8, 0);
|
|
case 16: return __atomic_always_lock_free (16, 0);
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------
|
|
* Legacy __sync_* built-ins
|
|
*
|
|
* GCC's pre-C++11 atomic built-ins lower to these out-of-line functions when
|
|
* the CPU cannot do the operation inline. Unlike the __atomic family they
|
|
* take no memory-order argument; every operation is a full barrier. The
|
|
* symbols are named __sync_fetch_and_OP_N and __sync_OP_and_fetch_N (note the
|
|
* "and"). __sync_synchronize, __sync_lock_test_and_set and
|
|
* __sync_lock_release are always expanded inline on these targets and so need
|
|
* no out-of-line form; they are provided anyway for completeness.
|
|
* ------------------------------------------------------------------------ */
|
|
|
|
#define RETRO68_SYNC_FETCH_OP(SZ, TYPE, OP, NAME) \
|
|
TYPE retro68_sync_fetch_and_##NAME##_##SZ (TYPE *, TYPE) \
|
|
__asm__ ("__sync_fetch_and_" #NAME "_" #SZ); \
|
|
TYPE retro68_sync_fetch_and_##NAME##_##SZ (TYPE *ptr, TYPE value) \
|
|
{ \
|
|
TYPE old = *ptr; \
|
|
*ptr = (TYPE) (OP (old, value)); \
|
|
return old; \
|
|
} \
|
|
\
|
|
TYPE retro68_sync_##NAME##_and_fetch_##SZ (TYPE *, TYPE) \
|
|
__asm__ ("__sync_" #NAME "_and_fetch_" #SZ); \
|
|
TYPE retro68_sync_##NAME##_and_fetch_##SZ (TYPE *ptr, TYPE value) \
|
|
{ \
|
|
TYPE result = (TYPE) (OP (*ptr, value)); \
|
|
*ptr = result; \
|
|
return result; \
|
|
}
|
|
|
|
#define RETRO68_SYNC_ALL_FETCH_OPS(SZ, TYPE) \
|
|
RETRO68_SYNC_FETCH_OP (SZ, TYPE, RETRO68_OP_ADD, add) \
|
|
RETRO68_SYNC_FETCH_OP (SZ, TYPE, RETRO68_OP_SUB, sub) \
|
|
RETRO68_SYNC_FETCH_OP (SZ, TYPE, RETRO68_OP_AND, and) \
|
|
RETRO68_SYNC_FETCH_OP (SZ, TYPE, RETRO68_OP_OR, or) \
|
|
RETRO68_SYNC_FETCH_OP (SZ, TYPE, RETRO68_OP_XOR, xor) \
|
|
RETRO68_SYNC_FETCH_OP (SZ, TYPE, RETRO68_OP_NAND, nand)
|
|
|
|
#define RETRO68_SYNC_COMPARE_EXCHANGE(SZ, TYPE) \
|
|
bool retro68_sync_bool_compare_and_swap_##SZ (TYPE *, TYPE, TYPE) \
|
|
__asm__ ("__sync_bool_compare_and_swap_" #SZ); \
|
|
bool retro68_sync_bool_compare_and_swap_##SZ (TYPE *ptr, TYPE oldval, \
|
|
TYPE newval) \
|
|
{ \
|
|
if (*ptr == oldval) \
|
|
{ \
|
|
*ptr = newval; \
|
|
return true; \
|
|
} \
|
|
return false; \
|
|
} \
|
|
\
|
|
TYPE retro68_sync_val_compare_and_swap_##SZ (TYPE *, TYPE, TYPE) \
|
|
__asm__ ("__sync_val_compare_and_swap_" #SZ); \
|
|
TYPE retro68_sync_val_compare_and_swap_##SZ (TYPE *ptr, TYPE oldval, \
|
|
TYPE newval) \
|
|
{ \
|
|
TYPE old = *ptr; \
|
|
if (old == oldval) \
|
|
*ptr = newval; \
|
|
return old; \
|
|
}
|
|
|
|
#define RETRO68_SYNC_LOCK(SZ, TYPE) \
|
|
TYPE retro68_sync_lock_test_and_set_##SZ (TYPE *, TYPE) \
|
|
__asm__ ("__sync_lock_test_and_set_" #SZ); \
|
|
TYPE retro68_sync_lock_test_and_set_##SZ (TYPE *ptr, TYPE value) \
|
|
{ \
|
|
TYPE old = *ptr; \
|
|
*ptr = value; \
|
|
return old; \
|
|
} \
|
|
\
|
|
void retro68_sync_lock_release_##SZ (TYPE *) \
|
|
__asm__ ("__sync_lock_release_" #SZ); \
|
|
void retro68_sync_lock_release_##SZ (TYPE *ptr) \
|
|
{ \
|
|
*ptr = 0; \
|
|
}
|
|
|
|
#define RETRO68_DEFINE_SYNC_SIZE(SZ, TYPE) \
|
|
RETRO68_SYNC_ALL_FETCH_OPS (SZ, TYPE) \
|
|
RETRO68_SYNC_COMPARE_EXCHANGE (SZ, TYPE) \
|
|
RETRO68_SYNC_LOCK (SZ, TYPE)
|
|
|
|
RETRO68_DEFINE_SYNC_SIZE (1, uint8_t)
|
|
RETRO68_DEFINE_SYNC_SIZE (2, uint16_t)
|
|
RETRO68_DEFINE_SYNC_SIZE (4, uint32_t)
|
|
RETRO68_DEFINE_SYNC_SIZE (8, uint64_t)
|