1 // Copyright 2006-2008 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided
11 // with the distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived
14 // from this software without specific prior written permission.
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 // This is a JavaScript implementation of the Richards
32 // http://www.cl.cam.ac.uk/~mr10/Bench.html
34 // The benchmark was originally implemented in BCPL by
39 * The Richards benchmark simulates the task dispatcher of an
42 function runRichards() {
44 var scheduler = new Scheduler();
45 scheduler.addIdleTask(ID_IDLE, 0, null, COUNT);
47 var queue = new Packet(null, ID_WORKER, KIND_WORK);
48 queue = new Packet(queue, ID_WORKER, KIND_WORK);
49 scheduler.addWorkerTask(ID_WORKER, 1000, queue);
51 queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE);
52 queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE);
53 queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE);
54 scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue);
56 queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE);
57 queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE);
58 queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE);
59 scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue);
61 scheduler.addDeviceTask(ID_DEVICE_A, 4000, null);
63 scheduler.addDeviceTask(ID_DEVICE_B, 5000, null);
67 if (scheduler.queueCount != EXPECTED_QUEUE_COUNT ||
68 scheduler.holdCount != EXPECTED_HOLD_COUNT) {
70 "Error during execution: queueCount = " + scheduler.queueCount +
71 ", holdCount = " + scheduler.holdCount + ".";
80 * These two constants specify how many times a packet is queued and
81 * how many times a task is put on hold in a correct run of richards.
82 * They don't have any meaning a such but are characteristic of a
83 * correct run so if the actual queue or hold count is different from
84 * the expected there must be a bug in the implementation.
86 var EXPECTED_QUEUE_COUNT = 2322;
87 var EXPECTED_HOLD_COUNT = 928;
91 * A scheduler can be used to schedule a set of tasks based on their relative
92 * priorities. Scheduling is done by maintaining a list of task control blocks
93 * which holds tasks and the data queue they are processing.
96 function Scheduler() {
100 this.blocks = new Array(NUMBER_OF_IDS);
102 this.currentTcb = null;
103 this.currentId = null;
110 var ID_HANDLER_A = 2;
111 var ID_HANDLER_B = 3;
114 var NUMBER_OF_IDS = 6;
120 * Add an idle task to this scheduler.
121 * @param {int} id the identity of the task
122 * @param {int} priority the task's priority
123 * @param {Packet} queue the queue of work to be processed by the task
124 * @param {int} count the number of times to schedule the task
126 Scheduler.prototype.addIdleTask = function (id, priority, queue, count) {
128 this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count));
133 * Add a work task to this scheduler.
134 * @param {int} id the identity of the task
135 * @param {int} priority the task's priority
136 * @param {Packet} queue the queue of work to be processed by the task
138 Scheduler.prototype.addWorkerTask = function (id, priority, queue) {
140 this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0));
145 * Add a handler task to this scheduler.
146 * @param {int} id the identity of the task
147 * @param {int} priority the task's priority
148 * @param {Packet} queue the queue of work to be processed by the task
150 Scheduler.prototype.addHandlerTask = function (id, priority, queue) {
152 this.addTask(id, priority, queue, new HandlerTask(this));
157 * Add a handler task to this scheduler.
158 * @param {int} id the identity of the task
159 * @param {int} priority the task's priority
160 * @param {Packet} queue the queue of work to be processed by the task
162 Scheduler.prototype.addDeviceTask = function (id, priority, queue) {
164 this.addTask(id, priority, queue, new DeviceTask(this))
169 * Add the specified task and mark it as running.
170 * @param {int} id the identity of the task
171 * @param {int} priority the task's priority
172 * @param {Packet} queue the queue of work to be processed by the task
173 * @param {Task} task the task to add
175 Scheduler.prototype.addRunningTask = function (id, priority, queue, task) {
177 this.addTask(id, priority, queue, task);
178 this.currentTcb.setRunning();
183 * Add the specified task to this scheduler.
184 * @param {int} id the identity of the task
185 * @param {int} priority the task's priority
186 * @param {Packet} queue the queue of work to be processed by the task
187 * @param {Task} task the task to add
189 Scheduler.prototype.addTask = function (id, priority, queue, task) {
191 this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task);
192 this.list = this.currentTcb;
193 this.blocks[id] = this.currentTcb;
198 * Execute the tasks managed by this scheduler.
200 Scheduler.prototype.schedule = function () {
201 this.currentTcb = this.list;
202 while (this.currentTcb != null) {
204 if (this.currentTcb.isHeldOrSuspended()) {
205 this.currentTcb = this.currentTcb.link;
207 this.currentId = this.currentTcb.id;
208 this.currentTcb = this.currentTcb.run();
215 * Release a task that is currently blocked and return the next block to run.
216 * @param {int} id the id of the task to suspend
218 Scheduler.prototype.release = function (id) {
220 var tcb = this.blocks[id];
221 if (tcb == null) return tcb;
225 if (tcb.priority > this.currentTcb.priority) {
228 return this.currentTcb;
234 * Block the currently executing task and return the next task control block
235 * to run. The blocked task will not be made runnable until it is explicitly
236 * released, even if new work is added to it.
238 Scheduler.prototype.holdCurrent = function () {
241 this.currentTcb.markAsHeld();
243 return this.currentTcb.link;
247 * Suspend the currently executing task and return the next task control block
248 * to run. If new work is added to the suspended task it will be made runnable.
250 Scheduler.prototype.suspendCurrent = function () {
252 this.currentTcb.markAsSuspended();
255 return this.currentTcb;
260 * Add the specified packet to the end of the worklist used by the task
261 * associated with the packet and make the task runnable if it is currently
263 * @param {Packet} packet the packet to add
265 Scheduler.prototype.queue = function (packet) {
267 var t = this.blocks[packet.id];
268 if (t == null) return t;
272 packet.id = this.currentId;
273 return t.checkPriorityAdd(this.currentTcb, packet);
277 * A task control block manages a task and the queue of work packages associated
279 * @param {TaskControlBlock} link the preceding block in the linked block list
280 * @param {int} id the id of this block
281 * @param {int} priority the priority of this block
282 * @param {Packet} queue the queue of packages to be processed by the task
283 * @param {Task} task the task
286 function TaskControlBlock(link, id, priority, queue, task) {
290 this.priority = priority;
296 this.state = STATE_SUSPENDED;
298 this.state = STATE_SUSPENDED_RUNNABLE;
304 * The task is running and is currently scheduled.
306 var STATE_RUNNING = 0;
309 * The task has packets left to process.
311 var STATE_RUNNABLE = 1;
314 * The task is not currently running. The task is not blocked as such and may
315 * be started by the scheduler.
317 var STATE_SUSPENDED = 2;
320 * The task is blocked and cannot be run until it is explicitly released.
324 var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE;
325 var STATE_NOT_HELD = ~STATE_HELD;
327 TaskControlBlock.prototype.setRunning = function () {
329 this.state = STATE_RUNNING;
333 TaskControlBlock.prototype.markAsNotHeld = function () {
335 this.state = this.state & STATE_NOT_HELD;
339 TaskControlBlock.prototype.markAsHeld = function () {
341 this.state = this.state | STATE_HELD;
345 TaskControlBlock.prototype.isHeldOrSuspended = function () {
347 return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED);
349 return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED);
353 TaskControlBlock.prototype.markAsSuspended = function () {
355 this.state = this.state | STATE_SUSPENDED;
357 this.state = this.state | STATE_SUSPENDED;
361 TaskControlBlock.prototype.markAsRunnable = function () {
363 this.state = this.state | STATE_RUNNABLE;
365 this.state = this.state | STATE_RUNNABLE;
370 * Runs this task, if it is ready to be run, and returns the next task to run.
372 TaskControlBlock.prototype.run = function () {
375 if (this.state == STATE_SUSPENDED_RUNNABLE) {
377 this.queue = packet.link;
378 if (this.queue == null) {
379 this.state = STATE_RUNNING;
381 this.state = STATE_RUNNABLE;
387 return this.task.run(packet);
391 * Adds a packet to the worklist of this block's task, marks this as runnable if
392 * necessary, and returns the next runnable object to run (the one
393 * with the highest priority).
395 TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) {
397 if (this.queue == null) {
399 this.markAsRunnable();
400 if (this.priority > task.priority) return this;
402 this.queue = packet.addTo(this.queue);
411 TaskControlBlock.prototype.toString = function () {
413 return "tcb { " + this.task + "@" + this.state + " }";
415 return "tcb { " + this.task + "@" + this.state + " }";
420 * An idle task doesn't do any work itself but cycles control between the two
422 * @param {Scheduler} scheduler the scheduler that manages this task
423 * @param {int} v1 a seed value that controls how the device tasks are scheduled
424 * @param {int} count the number of times this task should be scheduled
427 function IdleTask(scheduler, v1, count) {
429 this.scheduler = scheduler;
437 IdleTask.prototype.run = function (packet) {
440 if (this.count == 0) return this.scheduler.holdCurrent();
444 if ((this.v1 & 1) == 0) {
445 this.v1 = this.v1 >> 1;
446 return this.scheduler.release(ID_DEVICE_A);
448 this.v1 = (this.v1 >> 1) ^ 0xD008;
449 return this.scheduler.release(ID_DEVICE_B);
454 IdleTask.prototype.toString = function () {
463 * A task that suspends itself after each time it has been run to simulate
464 * waiting for data from an external device.
465 * @param {Scheduler} scheduler the scheduler that manages this task
468 function DeviceTask(scheduler) {
470 this.scheduler = scheduler;
475 DeviceTask.prototype.run = function (packet) {
476 if (packet == null) {
478 if (this.v1 == null) return this.scheduler.suspendCurrent();
482 return this.scheduler.queue(v);
487 return this.scheduler.holdCurrent();
491 DeviceTask.prototype.toString = function () {
498 * A task that manipulates work packets.
499 * @param {Scheduler} scheduler the scheduler that manages this task
500 * @param {int} v1 a seed used to specify how work packets are manipulated
501 * @param {int} v2 another seed used to specify how work packets are manipulated
504 function WorkerTask(scheduler, v1, v2) {
506 this.scheduler = scheduler;
512 WorkerTask.prototype.run = function (packet) {
513 if (packet == null) {
514 return this.scheduler.suspendCurrent();
517 if (this.v1 == ID_HANDLER_A) {
518 this.v1 = ID_HANDLER_B;
520 this.v1 = ID_HANDLER_A;
525 for (var i = 0; i < DATA_SIZE; i++) {
528 if (this.v2 > 26) this.v2 = 1;
529 packet.a2[i] = this.v2;
532 return this.scheduler.queue(packet);
536 WorkerTask.prototype.toString = function () {
543 * A task that manipulates work packets and then suspends itself.
544 * @param {Scheduler} scheduler the scheduler that manages this task
547 function HandlerTask(scheduler) {
549 this.scheduler = scheduler;
555 HandlerTask.prototype.run = function (packet) {
557 if (packet != null) {
558 if (packet.kind == KIND_WORK) {
559 this.v1 = packet.addTo(this.v1);
561 this.v2 = packet.addTo(this.v2);
567 if (this.v1 != null) {
568 var count = this.v1.a1;
570 if (count < DATA_SIZE) {
571 if (this.v2 != null) {
573 this.v2 = this.v2.link;
574 v.a1 = this.v1.a2[count];
575 this.v1.a1 = count + 1;
576 return this.scheduler.queue(v);
580 this.v1 = this.v1.link;
581 return this.scheduler.queue(v);
585 return this.scheduler.suspendCurrent();
588 HandlerTask.prototype.toString = function () {
590 return "HandlerTask";
601 * A simple package of data that is manipulated by the tasks. The exact layout
602 * of the payload data carried by a packet is not importaint, and neither is the
603 * nature of the work performed on packets by the tasks.
605 * Besides carrying data, packets form linked lists and are hence used both as
606 * data and worklists.
607 * @param {Packet} link the tail of the linked list of packets
608 * @param {int} id an ID for this packet
609 * @param {int} kind the type of this packet
612 function Packet(link, id, kind) {
618 this.a2 = new Array(DATA_SIZE);
623 * Add this packet to the end of a worklist, and return the worklist.
624 * @param {Packet} queue the worklist to add this packet to
626 Packet.prototype.addTo = function (queue) {
628 if (queue == null) return this;
629 var peek, next = queue;
630 while ((peek = next.link) != null) {
639 Packet.prototype.toString = function () {
645 for (let i = 0; i < 350; ++i)