Bug 972581 part 2 -- Add 1-dim mapPar r=shu

This commit is contained in:
Nicholas D. Matsakis 2014-02-13 16:38:21 -05:00
parent 4bca0b155e
commit ec9d0a481a
10 changed files with 424 additions and 119 deletions

View File

@ -377,6 +377,7 @@ selfhosting_srcs := \
$(srcdir)/builtin/Iterator.js \
$(srcdir)/builtin/Map.js \
$(srcdir)/builtin/Number.js \
$(srcdir)/builtin/Parallel.js \
$(srcdir)/builtin/String.js \
$(srcdir)/builtin/Set.js \
$(srcdir)/builtin/TypedObject.js \

View File

@ -570,116 +570,6 @@ function ArrayKeys() {
* http://wiki.ecmascript.org/doku.php?id=strawman:data_parallelism
*/
/* The mode asserts options object. */
#define TRY_PARALLEL(MODE) \
((!MODE || MODE.mode !== "seq"))
#define ASSERT_SEQUENTIAL_IS_OK(MODE) \
do { if (MODE) AssertSequentialIsOK(MODE) } while(false)
/* Safe versions of ARRAY.push(ELEMENT) */
#define ARRAY_PUSH(ARRAY, ELEMENT) \
callFunction(std_Array_push, ARRAY, ELEMENT);
#define ARRAY_SLICE(ARRAY, ELEMENT) \
callFunction(std_Array_slice, ARRAY, ELEMENT);
/**
* The ParallelSpew intrinsic is only defined in debug mode, so define a dummy
* if debug is not on.
*/
#ifndef DEBUG
#define ParallelSpew(args)
#endif
#define MAX_SLICE_SHIFT 6
#define MAX_SLICE_SIZE 64
#define MAX_SLICES_PER_WORKER 8
/**
* Determine the number and size of slices.
*/
function ComputeSlicesInfo(length) {
var count = length >>> MAX_SLICE_SHIFT;
var numWorkers = ForkJoinNumWorkers();
if (count < numWorkers)
count = numWorkers;
else if (count >= numWorkers * MAX_SLICES_PER_WORKER)
count = numWorkers * MAX_SLICES_PER_WORKER;
// Round the slice size to be a power of 2.
var shift = std_Math_max(std_Math_log2(length / count) | 0, 1);
// Recompute count with the rounded size.
count = length >>> shift;
if (count << shift !== length)
count += 1;
return { shift: shift, statuses: new Uint8Array(count), lastSequentialId: 0 };
}
/**
* Macros to help compute the start and end indices of slices based on id. Use
* with the object returned by ComputeSliceInfo.
*/
#define SLICE_START(info, id) \
(id << info.shift)
#define SLICE_END(info, start, length) \
std_Math_min(start + (1 << info.shift), length)
#define SLICE_COUNT(info) \
info.statuses.length
/**
* ForkJoinGetSlice acts as identity when we are not in a parallel section, so
* pass in the next sequential value when we are in sequential mode. The
* reason for this odd API is because intrinsics *need* to be called during
* ForkJoin's warmup to fill the TI info.
*/
#define GET_SLICE(info, id) \
((id = ForkJoinGetSlice(InParallelSection() ? -1 : NextSequentialSliceId(info, -1))) >= 0)
#define SLICE_STATUS_DONE 1
/**
* Macro to mark a slice as completed in the info object.
*/
#define MARK_SLICE_DONE(info, id) \
UnsafePutElements(info.statuses, id, SLICE_STATUS_DONE)
/**
* Reset the status array of the slices info object.
*/
function SlicesInfoClearStatuses(info) {
var statuses = info.statuses;
var length = statuses.length;
for (var i = 0; i < length; i++)
UnsafePutElements(statuses, i, 0);
info.lastSequentialId = 0;
}
/**
* Compute the slice such that all slices before it (but not including it) are
* completed.
*/
function NextSequentialSliceId(info, doneMarker) {
var statuses = info.statuses;
var length = statuses.length;
for (var i = info.lastSequentialId; i < length; i++) {
if (statuses[i] === SLICE_STATUS_DONE)
continue;
info.lastSequentialId = i;
return i;
}
return doneMarker == undefined ? length : doneMarker;
}
/**
* Determinism-preserving bounds function.
*/
function ShrinkLeftmost(info) {
return function () {
return [NextSequentialSliceId(info), SLICE_COUNT(info)]
};
}
/**
* Creates a new array by applying |func(e, i, self)| for each element |e|
* with index |i|.

View File

@ -0,0 +1,67 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Shared utility functions for parallel operations in `Array.js`
// and `TypedObject.js`.
/**
* Determine the number and size of slices.
*/
function ComputeSlicesInfo(length) {
var count = length >>> MAX_SLICE_SHIFT;
var numWorkers = ForkJoinNumWorkers();
if (count < numWorkers)
count = numWorkers;
else if (count >= numWorkers * MAX_SLICES_PER_WORKER)
count = numWorkers * MAX_SLICES_PER_WORKER;
// Round the slice size to be a power of 2.
var shift = std_Math_max(std_Math_log2(length / count) | 0, 1);
// Recompute count with the rounded size.
count = length >>> shift;
if (count << shift !== length)
count += 1;
return { shift: shift, statuses: new Uint8Array(count), lastSequentialId: 0 };
}
/**
* Reset the status array of the slices info object.
*/
function SlicesInfoClearStatuses(info) {
var statuses = info.statuses;
var length = statuses.length;
for (var i = 0; i < length; i++)
UnsafePutElements(statuses, i, 0);
info.lastSequentialId = 0;
}
/**
* Compute the slice such that all slices before it (but not including it) are
* completed.
*/
function NextSequentialSliceId(info, doneMarker) {
var statuses = info.statuses;
var length = statuses.length;
for (var i = info.lastSequentialId; i < length; i++) {
if (statuses[i] === SLICE_STATUS_DONE)
continue;
info.lastSequentialId = i;
return i;
}
return doneMarker == undefined ? length : doneMarker;
}
/**
* Determinism-preserving bounds function.
*/
function ShrinkLeftmost(info) {
return function () {
return [NextSequentialSliceId(info), SLICE_COUNT(info)]
};
}

View File

@ -1426,6 +1426,8 @@ TypedObject::attach(ArrayBufferObject &buffer, int32_t offset)
void
TypedObject::attach(TypedObject &typedObj, int32_t offset)
{
JS_ASSERT(typedObj.typedMem() != NULL);
attach(typedObj.owner(), typedObj.offset() + offset);
}
@ -1453,8 +1455,9 @@ TypedObjLengthFromType(TypeDescr &descr)
/*static*/ TypedObject *
TypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type,
HandleTypedObject typedObj, size_t offset)
HandleTypedObject typedObj, size_t offset)
{
JS_ASSERT(typedObj->typedMem() != NULL);
JS_ASSERT(offset <= typedObj->size());
JS_ASSERT(offset + type->size() <= typedObj->size());
@ -2665,6 +2668,28 @@ JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::AttachTypedObjectJitInfo,
AttachTypedObjectJitInfo,
js::AttachTypedObject);
bool
js::SetTypedObjectOffset(ThreadSafeContext *, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(argc == 2);
JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
JS_ASSERT(args[1].isInt32());
TypedObject &typedObj = args[0].toObject().as<TypedObject>();
int32_t offset = args[1].toInt32();
JS_ASSERT(typedObj.typedMem() != nullptr); // must be attached already
typedObj.setPrivate(typedObj.owner().dataPointer() + offset);
typedObj.setReservedSlot(JS_TYPEDOBJ_SLOT_BYTEOFFSET, Int32Value(offset));
return true;
}
JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::SetTypedObjectOffsetJitInfo,
SetTypedObjectJitInfo,
js::SetTypedObjectOffset);
bool
js::ObjectIsTypeDescr(ThreadSafeContext *, unsigned argc, Value *vp)
{

View File

@ -696,6 +696,15 @@ bool NewDerivedTypedObject(JSContext *cx, unsigned argc, Value *vp);
bool AttachTypedObject(ThreadSafeContext *cx, unsigned argc, Value *vp);
extern const JSJitInfo AttachTypedObjectJitInfo;
/*
* Usage: SetTypedObjectOffset(typedObj, offset)
*
* Changes the offset for `typedObj` within its buffer to `offset`.
* `typedObj` must already be attached.
*/
bool SetTypedObjectOffset(ThreadSafeContext *cx, unsigned argc, Value *vp);
extern const JSJitInfo SetTypedObjectOffsetJitInfo;
/*
* Usage: ObjectIsTypeDescr(obj)
*

View File

@ -447,6 +447,11 @@ TypedObjectPointer.prototype.getX4 = function() {
//
// The methods in this section modify the data pointed at by `this`.
// Convenience function
function SetTypedObjectValue(descr, typedObj, offset, fromValue) {
new TypedObjectPointer(descr, typedObj, offset).set(fromValue);
}
// Assigns `fromValue` to the memory pointed at by `this`, adapting it
// to `typeRepr` as needed. This is the most general entry point and
// works for any type.
@ -934,10 +939,23 @@ function TypedArrayMap(a, b) {
return MapTypedSeqImpl(this, a, thisType, b);
else if (typeof a === "function")
return MapTypedSeqImpl(this, 1, thisType, a);
else if (typeof a === "number")
return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
}
// Warning: user exposed!
function TypedArrayMapPar(a, b) {
if (!IsObject(this) || !ObjectIsTypedObject(this))
return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
else
var thisType = TYPEDOBJ_TYPE_DESCR(this);
if (!TypeDescrIsArrayType(thisType))
return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
// Arguments: [depth], func
if (typeof a === "number" && typeof b === "function")
return MapTypedParImpl(this, a, thisType, b);
else if (typeof a === "function")
return MapTypedParImpl(this, 1, thisType, a);
return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
}
// Warning: user exposed!
@ -1001,11 +1019,6 @@ function TypedObjectArrayTypeFromPar(a,b,c) {
return callFunction(TypedObjectArrayTypeFrom, this, a, b, c);
}
// Warning: user exposed!
function TypedArrayMapPar(a, b) {
return callFunction(TypedArrayMap, this, a, b);
}
// Warning: user exposed!
function TypedArrayReducePar(a, b) {
return callFunction(TypedArrayReduce, this, a, b);
@ -1036,6 +1049,12 @@ function GET_BIT(data, index) {
return (data[word] & mask) != 0;
}
function TypeDescrIsUnsizedArrayType(t) {
assert(IsObject(t) && ObjectIsTypeDescr(t),
"TypeDescrIsArrayType called on non-type-object");
return DESCR_KIND(t) === JS_TYPEREPR_UNSIZED_ARRAY_KIND;
}
function TypeDescrIsArrayType(t) {
assert(IsObject(t) && ObjectIsTypeDescr(t), "TypeDescrIsArrayType called on non-type-object");
@ -1073,7 +1092,8 @@ function TypeDescrIsSizedArrayType(t) {
}
function TypeDescrIsSimpleType(t) {
assert(IsObject(t) && ObjectIsTypeDescr(t), "TypeDescrIsSimpleType called on non-type-object");
assert(IsObject(t) && ObjectIsTypeDescr(t),
"TypeDescrIsSimpleType called on non-type-object");
var kind = DESCR_KIND(t);
switch (kind) {
@ -1326,6 +1346,183 @@ function MapTypedSeqImpl(inArray, depth, outputType, func) {
}
// Implements |map| and |from| methods for typed |inArray|.
function MapTypedParImpl(inArray, depth, outputType, func) {
assert(IsObject(outputType) && ObjectIsTypeDescr(outputType),
"Map/From called on non-type-object outputType");
assert(IsObject(inArray) && ObjectIsTypedObject(inArray),
"Map/From called on non-object or untyped input array.");
assert(TypeDescrIsArrayType(outputType),
"Map/From called on non array-type outputType");
var inArrayType = TypeOfTypedObject(inArray);
if (ShouldForceSequential() ||
depth <= 0 ||
TO_INT32(depth) !== depth ||
!TypeDescrIsArrayType(inArrayType) ||
!TypeDescrIsUnsizedArrayType(outputType))
{
// defer error cases to seq implementation:
return MapTypedSeqImpl(inArray, depth, outputType, func);
}
switch (depth) {
case 1:
return MapTypedParImplDepth1(inArray, inArrayType, outputType, func);
default:
return MapTypedSeqImpl(inArray, depth, outputType, func);
}
}
function RedirectPointer(typedObj, offset, outputIsScalar) {
if (!outputIsScalar || !InParallelSection()) {
// ^ Subtle note: always check InParallelSection() last, because
// otherwise the other if conditions will not execute during
// sequential mode and we will not gather enough type
// information.
// Here `typedObj` represents the input or output pointer we will
// pass to the user function. Ideally, we will just update the
// offset of `typedObj` in place so that it moves along the
// input/output buffer without incurring any allocation costs. But
// we can only do this if these changes are invisible to the user.
//
// Under normal uses, such changes *should* be invisible -- the
// in/out pointers are only intended to be used during the
// callback and then discarded, but of course in the general case
// nothing prevents them from escaping.
//
// However, if we are in parallel mode, we know that the pointers
// will not escape into global state. They could still escape by
// being returned into the resulting array, but even that avenue
// is impossible if the result array cannot contain objects.
//
// Therefore, we reuse a pointer if we are both in parallel mode
// and we have a transparent output type. It'd be nice to loosen
// this condition later by using fancy ion optimizations that
// assume the value won't escape and copy it if it does. But those
// don't exist yet. Moreover, checking if the type is transparent
// is an overapproximation: users can manually declare opaque
// types that nonetheless only contain scalar data.
typedObj = NewDerivedTypedObject(TYPEDOBJ_TYPE_DESCR(typedObj),
typedObj, 0);
}
SetTypedObjectOffset(typedObj, offset);
return typedObj;
}
SetScriptHints(RedirectPointer, { inline: true });
function MapTypedParImplDepth1(inArray, inArrayType, outArrayType, func) {
assert(IsObject(inArrayType) && ObjectIsTypeDescr(inArrayType) &&
TypeDescrIsArrayType(inArrayType),
"DoMapTypedParDepth1: invalid inArrayType");
assert(IsObject(outArrayType) && ObjectIsTypeDescr(outArrayType) &&
TypeDescrIsArrayType(outArrayType),
"DoMapTypedParDepth1: invalid outArrayType");
assert(IsObject(inArray) && ObjectIsTypedObject(inArray),
"DoMapTypedParDepth1: invalid inArray");
// Determine the grain types of the input and output.
const inGrainType = inArrayType.elementType;
const outGrainType = outArrayType.elementType;
const inGrainTypeSize = DESCR_SIZE(inGrainType);
const outGrainTypeSize = DESCR_SIZE(outGrainType);
const inGrainTypeIsComplex = !TypeDescrIsSimpleType(inGrainType);
const outGrainTypeIsComplex = !TypeDescrIsSimpleType(outGrainType);
const length = inArray.length;
const mode = undefined;
const outArray = new outArrayType(length);
const outGrainTypeIsTransparent = ObjectIsTransparentTypedObject(outArray);
// Construct the slices and initial pointers for each worker:
const slicesInfo = ComputeSlicesInfo(length);
const numWorkers = ForkJoinNumWorkers();
assert(numWorkers > 0, "Should have at least the main thread");
const pointers = [];
for (var i = 0; i < numWorkers; i++) {
const inPointer = new TypedObjectPointer(inGrainType, inArray, 0);
const inTypedObject = inPointer.getDerivedIf(inGrainTypeIsComplex);
const outPointer = new TypedObjectPointer(outGrainType, outArray, 0);
const outTypedObject = outPointer.getOpaqueIf(outGrainTypeIsComplex);
ARRAY_PUSH(pointers, ({ inTypedObject: inTypedObject,
outTypedObject: outTypedObject }));
}
// Below we will be adjusting offsets within the input to point at
// successive entries; we'll need to know the offset of inArray
// relative to its owner (which is often but not always 0).
const inBaseOffset = TYPEDOBJ_BYTEOFFSET(inArray);
ForkJoin(mapThread, ShrinkLeftmost(slicesInfo), ForkJoinMode(mode));
return outArray;
function mapThread(workerId, warmup) {
assert(TO_INT32(workerId) === workerId,
"workerId not int: " + workerId);
assert(workerId >= 0 && workerId < pointers.length,
"workerId too large: " + workerId + " >= " + pointers.length);
assert(!!pointers[workerId],
"no pointer data for workerId: " + workerId);
var sliceId;
const { inTypedObject, outTypedObject } = pointers[workerId];
while (GET_SLICE(slicesInfo, sliceId)) {
const indexStart = SLICE_START(slicesInfo, sliceId);
const indexEnd = SLICE_END(slicesInfo, indexStart, length);
var inOffset = inBaseOffset + std_Math_imul(inGrainTypeSize, indexStart);
var outOffset = std_Math_imul(outGrainTypeSize, indexStart);
// Set the target region so that user is only permitted to write
// within the range set aside for this slice. This prevents user
// from writing to typed objects that escaped from prior slices
// during sequential iteration. Note that, for any particular
// iteration of the loop below, it's only valid to write to the
// memory range corresponding to the index `i` -- however, since
// the different iterations cannot communicate typed object
// pointers to one another during parallel exec, we need only
// fear escaped typed objects from *other* slices, so we can
// just set the target region once.
const endOffset = std_Math_imul(outGrainTypeSize, indexEnd);
SetForkJoinTargetRegion(outArray, outOffset, endOffset);
for (var i = indexStart; i < indexEnd; i++) {
var inVal = (inGrainTypeIsComplex
? RedirectPointer(inTypedObject, inOffset,
outGrainTypeIsTransparent)
: inArray[i]);
var outVal = (outGrainTypeIsComplex
? RedirectPointer(outTypedObject, outOffset,
outGrainTypeIsTransparent)
: undefined);
const r = func(inVal, i, inArray, outVal);
if (r !== undefined) {
if (outGrainTypeIsComplex)
SetTypedObjectValue(outGrainType, outArray, outOffset, r);
else
outArray[i] = r;
}
inOffset += inGrainTypeSize;
outOffset += outGrainTypeSize;
}
MARK_SLICE_DONE(slicesInfo, sliceId);
if (warmup)
return;
}
}
return undefined;
}
SetScriptHints(MapTypedParImplDepth1, { cloneAtCallsite: true });
function ReduceTypedSeqImpl(array, outputType, func, initial) {
assert(IsObject(array) && ObjectIsTypedObject(array), "Reduce called on non-object or untyped input array.");
assert(IsObject(outputType) && ObjectIsTypeDescr(outputType), "Reduce called on non-type-object outputType");

View File

@ -90,6 +90,64 @@ var std_Set_iterator = Set.prototype[std_iterator];
var std_Map_iterator_next = Object.getPrototypeOf(Map()[std_iterator]()).next;
var std_Set_iterator_next = Object.getPrototypeOf(Set()[std_iterator]()).next;
/* Safe versions of ARRAY.push(ELEMENT) */
#define ARRAY_PUSH(ARRAY, ELEMENT) \
callFunction(std_Array_push, ARRAY, ELEMENT);
#define ARRAY_SLICE(ARRAY, ELEMENT) \
callFunction(std_Array_slice, ARRAY, ELEMENT);
/********** Parallel JavaScript macros and so on **********/
#ifdef ENABLE_PARALLEL_JS
/* The mode asserts options object. */
#define TRY_PARALLEL(MODE) \
((!MODE || MODE.mode !== "seq"))
#define ASSERT_SEQUENTIAL_IS_OK(MODE) \
do { if (MODE) AssertSequentialIsOK(MODE) } while(false)
/**
* The ParallelSpew intrinsic is only defined in debug mode, so define a dummy
* if debug is not on.
*/
#ifndef DEBUG
#define ParallelSpew(args)
#endif
#define MAX_SLICE_SHIFT 6
#define MAX_SLICE_SIZE 64
#define MAX_SLICES_PER_WORKER 8
/**
* Macros to help compute the start and end indices of slices based on id. Use
* with the object returned by ComputeSliceInfo.
*/
#define SLICE_START(info, id) \
(id << info.shift)
#define SLICE_END(info, start, length) \
std_Math_min(start + (1 << info.shift), length)
#define SLICE_COUNT(info) \
info.statuses.length
/**
* ForkJoinGetSlice acts as identity when we are not in a parallel section, so
* pass in the next sequential value when we are in sequential mode. The
* reason for this odd API is because intrinsics *need* to be called during
* ForkJoin's warmup to fill the TI info.
*/
#define GET_SLICE(info, id) \
((id = ForkJoinGetSlice(InParallelSection() ? -1 : NextSequentialSliceId(info, -1))) >= 0)
#define SLICE_STATUS_DONE 1
/**
* Macro to mark a slice as completed in the info object.
*/
#define MARK_SLICE_DONE(info, id) \
UnsafePutElements(info.statuses, id, SLICE_STATUS_DONE)
#endif // ENABLE_PARALLEL_JS
/********** List specification type **********/

View File

@ -0,0 +1,32 @@
// Test basic mapPar parallel execution using an out
// pointer to generate a struct return.
if (!this.hasOwnProperty("TypedObject"))
quit();
load(libdir + "parallelarray-helpers.js")
var { ArrayType, StructType, uint32 } = TypedObject;
function test() {
var L = minItemsTestingThreshold;
var Point = new StructType({x: uint32, y: uint32});
var Points = Point.array();
var points = new Points(L);
for (var i = 0; i < L; i++)
points[i].x = i;
assertParallelExecSucceeds(
function() points.mapPar(function(p, i, c, out) { out.y = p.x; }),
function(points2) {
for (var i = 0; i < L; i++) {
assertEq(points[i].x, i);
assertEq(points[i].y, 0);
assertEq(points2[i].x, 0);
assertEq(points2[i].y, i);
}
});
}
test();

View File

@ -0,0 +1,23 @@
// Test basic mapPar parallel execution.
if (!this.hasOwnProperty("TypedObject"))
quit();
load(libdir + "parallelarray-helpers.js")
var { ArrayType, StructType, uint32 } = TypedObject;
function test() {
var L = minItemsTestingThreshold;
var Uints = uint32.array(L);
var uints1 = new Uints();
assertParallelExecSucceeds(
function() uints1.mapPar(function(e) e + 1),
function(uints2) {
for (var i = 0; i < L; i++)
assertEq(uints1[i] + 1, uints2[i]);
});
}
test();

View File

@ -692,6 +692,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FNINFO("AttachTypedObject",
JSNativeThreadSafeWrapper<js::AttachTypedObject>,
&js::AttachTypedObjectJitInfo, 5, 0),
JS_FNINFO("SetTypedObjectOffset",
JSNativeThreadSafeWrapper<js::SetTypedObjectOffset>,
&js::SetTypedObjectOffsetJitInfo, 2, 0),
JS_FNINFO("ObjectIsTypeDescr",
JSNativeThreadSafeWrapper<js::ObjectIsTypeDescr>,
&js::ObjectIsTypeDescrJitInfo, 5, 0),