You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
5.6 KiB
251 lines
5.6 KiB
4 years ago
|
/*
|
||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||
|
*
|
||
|
* This source code is licensed under the MIT license found in the
|
||
|
* LICENSE file in the root directory of this source tree.
|
||
|
*/
|
||
|
|
||
|
// Testing ProducerConsumerQueue from
|
||
|
// https://github.com/facebook/folly/blob/master/folly/ProducerConsumerQueue.h
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
#include "ref_queue.h"
|
||
|
#include "scheduler.h"
|
||
|
|
||
|
#define NUM_THREADS 2
|
||
|
#define CONTEXT_SWITCHES_BOUND 2
|
||
|
#define SCHEDULE_POINTS 8
|
||
|
#include "test_mains.h"
|
||
|
|
||
|
// size must be >= 2.
|
||
|
#define QUEUE_DEPTH 2
|
||
|
|
||
|
#define READ_BUG false
|
||
|
#define SIZE_BUG true
|
||
|
|
||
|
typedef enum { CONSUMER, PRODUCER } frame_type;
|
||
|
|
||
|
typedef struct {
|
||
|
int size;
|
||
|
int readIndex;
|
||
|
int writeIndex;
|
||
|
data_type* records;
|
||
|
} queue;
|
||
|
|
||
|
typedef struct {
|
||
|
queue* q;
|
||
|
ref_queue* ref;
|
||
|
data_type v;
|
||
|
int currentWrite;
|
||
|
int nextRecord;
|
||
|
} producer_frame;
|
||
|
|
||
|
typedef struct {
|
||
|
queue* q;
|
||
|
ref_queue* ref;
|
||
|
int currentRead;
|
||
|
int currentWrite; // For the bug
|
||
|
int nextRecord;
|
||
|
data_type result;
|
||
|
} consumer_frame;
|
||
|
|
||
|
void init_queue(queue* q, size_t size) {
|
||
|
q->size = size;
|
||
|
q->readIndex = 0;
|
||
|
q->writeIndex = 0;
|
||
|
q->records = malloc(sizeof(data_type) * size);
|
||
|
}
|
||
|
|
||
|
consumer_frame* make_consumer_frame(queue* q, ref_queue* ref) {
|
||
|
consumer_frame* frame = malloc(sizeof(consumer_frame));
|
||
|
frame->q = q;
|
||
|
frame->ref = ref;
|
||
|
return frame;
|
||
|
}
|
||
|
|
||
|
producer_frame* make_producer_frame(queue* q, ref_queue* ref) {
|
||
|
producer_frame* frame = malloc(sizeof(producer_frame));
|
||
|
frame->q = q;
|
||
|
frame->ref = ref;
|
||
|
return frame;
|
||
|
}
|
||
|
|
||
|
data_type val = 0;
|
||
|
|
||
|
// write
|
||
|
int step_producer(producer_frame* frame, int pc) {
|
||
|
queue* q = frame->q;
|
||
|
switch (pc) {
|
||
|
case 0:
|
||
|
frame->v = val;
|
||
|
val++;
|
||
|
frame->currentWrite = q->writeIndex;
|
||
|
frame->nextRecord = frame->currentWrite + 1;
|
||
|
if (frame->nextRecord == q->size) {
|
||
|
frame->nextRecord = 0;
|
||
|
}
|
||
|
pc = 1;
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
if (frame->nextRecord != q->readIndex) {
|
||
|
pc = 2;
|
||
|
} else {
|
||
|
bool queue_result = false;
|
||
|
bool ref_result = ref_enqueue(frame->ref, frame->v);
|
||
|
if (ref_result != queue_result) {
|
||
|
printf("ref_result: %d, queue_result: %d\n", ref_result, queue_result);
|
||
|
}
|
||
|
assert(ref_result == queue_result);
|
||
|
pc = 0;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
q->records[frame->currentWrite] = frame->v;
|
||
|
pc = 3;
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
q->writeIndex = frame->nextRecord;
|
||
|
bool queue_result = true;
|
||
|
// printf("test enqueue: %d\n", frame->v);
|
||
|
bool ref_result = ref_enqueue(frame->ref, frame->v);
|
||
|
assert(ref_result == queue_result);
|
||
|
pc = 0;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false);
|
||
|
}
|
||
|
return pc;
|
||
|
}
|
||
|
|
||
|
// read
|
||
|
int step_consumer(consumer_frame* frame, int pc) {
|
||
|
queue* q = frame->q;
|
||
|
switch (pc) {
|
||
|
case 0:
|
||
|
frame->currentRead = q->readIndex;
|
||
|
pc = 1;
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
if (frame->currentRead == q->writeIndex) {
|
||
|
// queue is empty
|
||
|
data_type ref_result = ref_dequeue(frame->ref);
|
||
|
assert(ref_result == EMPTY);
|
||
|
pc = 0;
|
||
|
break;
|
||
|
}
|
||
|
frame->nextRecord = frame->currentRead + 1;
|
||
|
if (frame->nextRecord == q->size) {
|
||
|
frame->nextRecord = 0;
|
||
|
}
|
||
|
pc = 2;
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
frame->result = q->records[frame->currentRead];
|
||
|
pc = 3;
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
q->readIndex = frame->nextRecord;
|
||
|
data_type ref_result = ref_dequeue(frame->ref);
|
||
|
assert(ref_result == frame->result);
|
||
|
pc = 0;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false);
|
||
|
}
|
||
|
return pc;
|
||
|
}
|
||
|
|
||
|
int step_consumer_bug(consumer_frame* frame, int pc) {
|
||
|
queue* q = frame->q;
|
||
|
switch (pc) {
|
||
|
case 0:
|
||
|
frame->currentWrite = q->writeIndex;
|
||
|
pc = 1;
|
||
|
break;
|
||
|
|
||
|
case 1:
|
||
|
frame->currentRead = q->readIndex;
|
||
|
if (frame->currentRead == frame->currentWrite) {
|
||
|
// queue is empty
|
||
|
data_type ref_result = ref_dequeue(frame->ref);
|
||
|
assert(ref_result == EMPTY);
|
||
|
pc = 0;
|
||
|
break;
|
||
|
}
|
||
|
frame->nextRecord = frame->currentRead + 1;
|
||
|
if (frame->nextRecord == q->size) {
|
||
|
frame->nextRecord = 0;
|
||
|
}
|
||
|
pc = 2;
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
frame->result = q->records[frame->currentRead];
|
||
|
pc = 3;
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
q->readIndex = frame->nextRecord;
|
||
|
data_type ref_result = ref_dequeue(frame->ref);
|
||
|
assert(ref_result == frame->result);
|
||
|
pc = 0;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(false);
|
||
|
}
|
||
|
return pc;
|
||
|
}
|
||
|
|
||
|
// Runs the operation associated with the given frame until reaching the next
|
||
|
// block. That is, the next statement accessing shared memory.
|
||
|
int step(base_frame* frame) {
|
||
|
assert(frame != NULL);
|
||
|
if (frame->derived_frame_type == CONSUMER) {
|
||
|
consumer_frame* derived_frame = (consumer_frame*)frame->derived_frame;
|
||
|
assert(derived_frame != NULL);
|
||
|
if (READ_BUG) {
|
||
|
return step_consumer_bug(derived_frame, frame->pc);
|
||
|
} else {
|
||
|
return step_consumer(derived_frame, frame->pc);
|
||
|
}
|
||
|
} else if (frame->derived_frame_type == PRODUCER) {
|
||
|
producer_frame* derived_frame = (producer_frame*)frame->derived_frame;
|
||
|
assert(derived_frame != NULL);
|
||
|
return step_producer(derived_frame, frame->pc);
|
||
|
} else {
|
||
|
assert(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int test_with_schedule(uint8_t* schedule, size_t len) {
|
||
|
queue test_queue;
|
||
|
|
||
|
init_queue(&test_queue, QUEUE_DEPTH);
|
||
|
// From the folly documentation:
|
||
|
// "Also, note that the number of usable slots in the queue at any
|
||
|
// given time is actually (size-1)..."
|
||
|
ref_queue* ref;
|
||
|
if (SIZE_BUG) {
|
||
|
ref = new_ref_queue(QUEUE_DEPTH);
|
||
|
} else {
|
||
|
ref = new_ref_queue(QUEUE_DEPTH - 1);
|
||
|
}
|
||
|
base_frame frames[2];
|
||
|
init_frame(&frames[0], make_consumer_frame(&test_queue, ref), CONSUMER);
|
||
|
init_frame(&frames[1], make_producer_frame(&test_queue, ref), PRODUCER);
|
||
|
execute_schedule(schedule, len, frames, NUM_THREADS, CONTEXT_SWITCHES_BOUND);
|
||
|
return 0;
|
||
|
}
|