The main body of code is located here. In the code linted_error
is a platform specific type for error codes. As well, as an optimization the code can use Linux futexes.
linted/mem.h
is just a tiny wrapper aroundmalloc
.linted/sched.h
does a bunch of stuff but is only used in this code to wrap platform specific pause instructions for spinlocks.
Otherwise, there are no other unportabilities.
I think the main problem with this implementation is that it doesn't use memory fences correctly. Am I right that I only need to use memory fences with memory_order_relaxed
memory ordering. If so, then it should be possible to convert to usage of memory_order_relaxed
easily and possibly obtain a small speedup right?
I also think it may be possible to use memory_order_relaxed
without memory fences for the trigger code as triggers are supposed to be unreliable anyway.
Note that for my use case it doesn't matter what order things are inserted onto the stack.
The stack:
/*
* Copyright 2015 Steven Stewart-Gallus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#include "config.h"
#include "linted/stack.h"
#include "linted/error.h"
#include "linted/mem.h"
#include "linted/node.h"
#include "linted/sched.h"
#include "linted/trigger.h"
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
typedef _Atomic(struct linted_node *) atomic_node;
struct linted_stack {
atomic_node inbox;
struct linted_node *outbox;
struct linted_trigger inbox_filled;
};
static void refresh_node(struct linted_node *node)
{
node->next = 0;
}
linted_error linted_stack_create(struct linted_stack **stackp)
{
linted_error err = 0;
struct linted_stack *stack;
{
void *xx;
err = linted_mem_alloc(&xx, sizeof *stack);
if (err != 0)
return err;
stack = xx;
}
atomic_node ptr = ATOMIC_VAR_INIT((void *)0);
stack->inbox = ptr;
stack->outbox = 0;
linted_trigger_create(&stack->inbox_filled);
*stackp = stack;
return 0;
}
void linted_stack_destroy(struct linted_stack *stack)
{
linted_trigger_destroy(&stack->inbox_filled);
linted_mem_free(stack);
}
void linted_stack_send(struct linted_stack *stack,
struct linted_node *node)
{
for (;;) {
struct linted_node *next = atomic_load_explicit(
&stack->inbox, memory_order_acquire);
node->next = next;
atomic_thread_fence(memory_order_release);
if (atomic_compare_exchange_weak_explicit(
&stack->inbox, &next, node,
memory_order_acq_rel, memory_order_acquire)) {
break;
}
linted_sched_light_yield();
}
linted_trigger_set(&stack->inbox_filled);
}
void linted_stack_recv(struct linted_stack *stack,
struct linted_node **nodep)
{
struct linted_node *ret = stack->outbox;
if (ret != 0) {
stack->outbox = ret->next;
goto give_node;
}
for (;;) {
for (uint_fast8_t ii = 0U; ii < 20U; ++ii) {
ret = atomic_exchange_explicit(
&stack->inbox, 0, memory_order_acq_rel);
if (ret != 0)
goto put_on_outbox;
linted_sched_light_yield();
}
linted_trigger_wait(&stack->inbox_filled);
}
put_on_outbox:
atomic_thread_fence(memory_order_acquire);
for (struct linted_node *node = ret->next;;) {
if (0 == node)
break;
struct linted_node *next = node->next;
node->next = stack->outbox;
stack->outbox = node;
node = next;
}
give_node:
refresh_node(ret);
*nodep = ret;
}
linted_error linted_stack_try_recv(struct linted_stack *stack,
struct linted_node **nodep)
{
struct linted_node *ret = stack->outbox;
if (ret != 0) {
stack->outbox = ret->next;
goto give_node;
}
ret = atomic_exchange_explicit(&stack->inbox, 0,
memory_order_acq_rel);
if (0 == ret)
return EAGAIN;
atomic_thread_fence(memory_order_acquire);
for (struct linted_node *node = ret->next;;) {
if (0 == node)
break;
struct linted_node *next = node->next;
node->next = stack->outbox;
stack->outbox = node;
node = next;
}
give_node:
refresh_node(ret);
*nodep = ret;
return 0;
}
linted/node.h:
/*
* Copyright 2015 Steven Stewart-Gallus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#ifndef LINTED_NODE_H
#define LINTED_NODE_H
struct linted_node {
struct linted_node *next;
};
#endif /* LINTED_NODE_H */
linted/mem.h:
/*
* Copyright 2014, 2015 Steven Stewart-Gallus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#ifndef LINTED_MEM_H
#define LINTED_MEM_H
#include "linted/error.h"
#include <stddef.h>
/**
* @file
*
* Allocates memory.
*/
/* For consistent platform behaviour we return the null pointer on
* zero sized allocations.
*/
static inline linted_error
linted_mem_safe_multiply(size_t nmemb, size_t size, size_t *resultp)
{
if (size > 0U && ((size_t)-1) / size < nmemb)
return LINTED_ERROR_OUT_OF_MEMORY;
*resultp = nmemb *size;
return 0;
}
static inline linted_error linted_mem_alloc(void **memp, size_t size)
{
extern void *malloc(size_t size);
void *memory;
if (0U == size) {
memory = 0;
} else {
memory = malloc(size);
if (0 == memory)
return LINTED_ERROR_OUT_OF_MEMORY;
}
*memp = memory;
return 0;
}
static inline linted_error
linted_mem_alloc_array(void **memp, size_t nmemb, size_t size)
{
extern void *malloc(size_t size);
linted_error err;
void *memory;
if (0U == nmemb || 0U == size) {
memory = 0;
goto store_mem;
}
size_t total;
err = linted_mem_safe_multiply(nmemb, size, &total);
if (err != 0)
return err;
memory = malloc(total);
if (0 == memory)
return LINTED_ERROR_OUT_OF_MEMORY;
store_mem:
*memp = memory;
return 0;
}
static inline linted_error linted_mem_alloc_zeroed(void **memp,
size_t size)
{
extern void *calloc(size_t nmemb, size_t size);
void *memory;
if (0U == size) {
memory = 0;
} else {
memory = calloc(1U, size);
if (0 == memory)
return LINTED_ERROR_OUT_OF_MEMORY;
}
*memp = memory;
return 0;
}
static inline linted_error
linted_mem_alloc_array_zeroed(void **memp, size_t nmemb, size_t size)
{
extern void *calloc(size_t nmemb, size_t size);
void *memory;
if (0U == nmemb || 0U == size) {
memory = 0;
} else {
memory = calloc(nmemb, size);
if (0 == memory)
return LINTED_ERROR_OUT_OF_MEMORY;
}
*memp = memory;
return 0;
}
static inline linted_error linted_mem_realloc(void **memp, void *memory,
size_t new_size)
{
extern void *realloc(void *ptr, size_t size);
extern void free(void *ptr);
void *new_memory;
if (0U == new_size) {
free(memory);
new_memory = 0;
} else {
new_memory = realloc(memory, new_size);
if (0 == new_memory)
return LINTED_ERROR_OUT_OF_MEMORY;
}
*memp = new_memory;
return 0;
}
static inline linted_error linted_mem_realloc_array(void **memp,
void *memory,
size_t nmemb,
size_t size)
{
extern void *realloc(void *ptr, size_t size);
extern void free(void *ptr);
linted_error err = 0;
void *new_memory;
if (0U == nmemb || 0U == size) {
free(memory);
new_memory = 0;
goto store_mem;
}
size_t total;
err = linted_mem_safe_multiply(nmemb, size, &total);
if (err != 0)
return err;
new_memory = realloc(memory, total);
if (0 == new_memory)
return LINTED_ERROR_OUT_OF_MEMORY;
store_mem:
*memp = new_memory;
return 0;
}
static inline void linted_mem_free(void *memory)
{
extern void free(void *ptr);
/* This is primarily for making debugging easier and not for
* any sort of optimization */
if (memory != 0)
free(memory);
}
#endif /* LINTED_MEM_H */
linted/trigger.h:
/*
* Copyright 2015 Steven Stewart-Gallus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#ifndef LINTED_TRIGGER_H
#define LINTED_TRIGGER_H
#include <stdatomic.h>
struct linted_trigger {
atomic_int _triggered;
};
void linted_trigger_create(struct linted_trigger *trigger);
void linted_trigger_destroy(struct linted_trigger *trigger);
void linted_trigger_set(struct linted_trigger *trigger);
void linted_trigger_wait(struct linted_trigger *trigger);
#endif /* LINTED_TRIGGER_H */
The trigger code is below. Triggers are unreliable flag variables for waiting on.
/*
* Copyright 2015 Steven Stewart-Gallus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#define _GNU_SOURCE
#include "config.h"
#include "linted/trigger.h"
#include "linted/error.h"
#include "linted/sched.h"
#include <errno.h>
#include <stdatomic.h>
#include <time.h>
#include <unistd.h>
#if defined HAVE_POSIX_API
#include <syscall.h>
#include <linux/futex.h>
#endif
static linted_error wait_until_different(atomic_int const *uaddr,
int val);
static linted_error hint_wakeup(atomic_int const *uaddr);
void linted_trigger_create(struct linted_trigger *trigger)
{
atomic_int zero = ATOMIC_VAR_INIT(0);
trigger->_triggered = zero;
}
void linted_trigger_destroy(struct linted_trigger *trigger)
{
}
void linted_trigger_set(struct linted_trigger *trigger)
{
atomic_store_explicit(&trigger->_triggered, 1,
memory_order_release);
hint_wakeup(&trigger->_triggered);
}
void linted_trigger_wait(struct linted_trigger *trigger)
{
wait_until_different(&trigger->_triggered, 0);
atomic_store_explicit(&trigger->_triggered, 0,
memory_order_release);
}
#if defined HAVE_POSIX_API
static linted_error futex_wait(atomic_int const *uaddr, int val,
struct timespec const *timeout);
static linted_error futex_wake(unsigned *restrict wokeupp,
atomic_int const *uaddr, int val);
static linted_error wait_until_different(atomic_int const *uaddr,
int val)
{
return futex_wait(uaddr, val, NULL);
}
static linted_error hint_wakeup(atomic_int const *uaddr)
{
return futex_wake(NULL, uaddr, 1);
}
#else
static linted_error wait_until_different(atomic_int const *uaddr,
int val)
{
for (;;) {
if (atomic_load_explicit(uaddr, memory_order_acquire) !=
val)
return 0;
linted_sched_light_yield();
}
}
static linted_error hint_wakeup(atomic_int const *uaddr)
{
return 0;
}
#endif
#if defined HAVE_POSIX_API
static linted_error futex_wait(atomic_int const *uaddr, int val,
struct timespec const *timeout)
{
int xx =
syscall(__NR_futex, (intptr_t)uaddr, (intptr_t)FUTEX_WAIT,
(intptr_t)val, (intptr_t)timeout);
if (xx < 0) {
return errno;
}
return 0;
}
static linted_error futex_wake(unsigned *restrict wokeupp,
atomic_int const *uaddr, int val)
{
int xx = syscall(__NR_futex, (intptr_t)uaddr,
(intptr_t)FUTEX_WAKE, (intptr_t)val);
if (xx < 0) {
return errno;
}
if (wokeupp != NULL) {
*wokeupp = xx;
}
return 0;
}
#endif