Bug 795505 - Feed typed arrays to js-ctypes. r=jorendorff
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Sat, 06 Oct 2012 21:53:23 -0400
changeset 109540 7be4e7680b42ebb8dafbb8a84b252862d78a3175
parent 109539 834fb95232af9f4e02e365f86d320c662091c0fe
child 109541 03e4c6401c35ba6d598f7ab62c9ba5a505967564
push id23632
push userphilringnalda@gmail.com
push dateSun, 07 Oct 2012 19:14:37 +0000
treeherdermozilla-central@83d38854c21e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs795505
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 795505 - Feed typed arrays to js-ctypes. r=jorendorff
js/src/ctypes/CTypes.cpp
toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
toolkit/components/ctypes/tests/xpcshellTestHarnessAdaptor.js
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -1989,16 +1989,57 @@ ConvertToJS(JSContext* cx,
   }
   case TYPE_function:
     JS_NOT_REACHED("cannot return a FunctionType");
   }
 
   return true;
 }
 
+// Determine if the contents of a typed array can be converted without
+// ambiguity to a C type. Elements of a Int8Array are converted to
+// ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc.
+bool CanConvertTypedArrayItemTo(JSObject *baseType, JSObject *valObj, JSContext *cx) {
+  TypeCode baseTypeCode = CType::GetTypeCode(baseType);
+  if (baseTypeCode == TYPE_void_t) {
+    return true;
+  }
+  TypeCode elementTypeCode;
+  switch (JS_GetTypedArrayType(valObj, cx)) {
+  case TypedArray::TYPE_INT8:
+    elementTypeCode = TYPE_int8_t;
+    break;
+  case TypedArray::TYPE_UINT8:
+  case TypedArray::TYPE_UINT8_CLAMPED:
+    elementTypeCode = TYPE_uint8_t;
+    break;
+  case TypedArray::TYPE_INT16:
+    elementTypeCode = TYPE_int16_t;
+    break;
+  case TypedArray::TYPE_UINT16:
+    elementTypeCode = TYPE_uint16_t;
+    break;
+  case TypedArray::TYPE_INT32:
+    elementTypeCode = TYPE_int32_t;
+    break;
+  case TypedArray::TYPE_UINT32:
+    elementTypeCode = TYPE_uint32_t;
+    break;
+  case TypedArray::TYPE_FLOAT32:
+    elementTypeCode = TYPE_float32_t;
+    break;
+  case TypedArray::TYPE_FLOAT64:
+    elementTypeCode = TYPE_float64_t;
+    break;
+  default:
+    return false;
+  }
+  return elementTypeCode == baseTypeCode;
+}
+
 // Implicitly convert jsval 'val' to a C binary representation of CType
 // 'targetType', storing the result in 'buffer'. Adequate space must be
 // provided in 'buffer' by the caller. This function generally does minimal
 // coercion between types. There are two cases in which this function is used:
 // 1) The target buffer is internal to a CData object; we simply write data
 //    into it.
 // 2) We are converting an argument for an ffi call, in which case 'isArgument'
 //    will be true. This allows us to handle a special case: if necessary,
@@ -2188,16 +2229,26 @@ ImplicitConvert(JSContext* cx,
       }
       break;
     } else if (!JSVAL_IS_PRIMITIVE(val) && JS_IsArrayBufferObject(valObj, cx)) {
       // Convert ArrayBuffer to pointer without any copy.
       // Just as with C arrays, we make no effort to
       // keep the ArrayBuffer alive.
       *static_cast<void**>(buffer) = JS_GetArrayBufferData(valObj, cx);
       break;
+    } if (!JSVAL_IS_PRIMITIVE(val) && JS_IsTypedArrayObject(valObj, cx)) {
+      if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
+        return TypeError(cx, "typed array with the appropriate type", val);
+      }
+
+      // Convert TypedArray to pointer without any copy.
+      // Just as with C arrays, we make no effort to
+      // keep the TypedArray alive.
+      *static_cast<void**>(buffer) = JS_GetArrayBufferViewData(valObj, cx);
+      break;
     }
     return TypeError(cx, "pointer", val);
   }
   case TYPE_array: {
     JSObject* baseType = ArrayType::GetBaseType(targetType);
     size_t targetLength = ArrayType::GetLength(targetType);
 
     if (JSVAL_IS_STRING(val)) {
@@ -2277,29 +2328,43 @@ ImplicitConvert(JSContext* cx,
           return false;
       }
 
       memcpy(buffer, intermediate.get(), arraySize);
 
     } else if (!JSVAL_IS_PRIMITIVE(val) &&
                JS_IsArrayBufferObject(valObj, cx)) {
       // Check that array is consistent with type, then
-      // copy the array. As with C arrays, data is *not*
-      // copied back to the ArrayBuffer at the end of a
-      // function call, so do not expect this to work
-      // as an inout argument.
+      // copy the array.
       uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj, cx);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
-        JS_ReportError(cx, "ArrayType length does not match source array length");
+        JS_ReportError(cx, "ArrayType length does not match source ArrayBuffer length");
         return false;
       }
       memcpy(buffer, JS_GetArrayBufferData(valObj, cx), sourceLength);
       break;
+    }  else if (!JSVAL_IS_PRIMITIVE(val) &&
+               JS_IsTypedArrayObject(valObj, cx)) {
+      // Check that array is consistent with type, then
+      // copy the array.
+      if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
+        return TypeError(cx, "typed array with the appropriate type", val);
+      }
+
+      uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj, cx);
+      size_t elementSize = CType::GetSize(baseType);
+      size_t arraySize = elementSize * targetLength;
+      if (arraySize != size_t(sourceLength)) {
+        JS_ReportError(cx, "typed array length does not match source TypedArray length");
+        return false;
+      }
+      memcpy(buffer, JS_GetArrayBufferViewData(valObj, cx), sourceLength);
+      break;
     } else {
       // Don't implicitly convert to string. Users can implicitly convert
       // with `String(x)` or `""+x`.
       return TypeError(cx, "array", val);
     }
     break;
   }
   case TYPE_struct: {
--- a/toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
+++ b/toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
@@ -1747,27 +1747,91 @@ function run_PointerType_tests() {
   let z_t = ctypes.int32_t.array().ptr;
   do_check_eq(ptrValue(z_t()), 0);
   do_check_throws(function() { z_t().contents }, Error);
   z_t = ctypes.int32_t.array(0).ptr;
   do_check_eq(ptrValue(z_t()), 0);
   let z = ctypes.int32_t.array(0)().address();
   do_check_eq(z.contents.length, 0);
 
-  // Check that you can use an ArrayBuffer as a pointer
-  let c_arraybuffer = new ArrayBuffer(24);
-  let c_int32 = new Int32Array(c_arraybuffer);
-  for (let i = 0; i < 6; ++i) {
-    c_int32[i] = i;
-  }
-  let c_ptr = ctypes.int32_t.ptr(c_arraybuffer);
-  do_check_eq(c_ptr.contents, 0);
-  let c_array = ctypes.cast(c_ptr, ctypes.ArrayType(ctypes.int32_t, 6).ptr).contents;
-  for (let i = 0; i < 6; ++i) {
-    do_check_eq(c_array[i], c_int32[i]);
+  // Check that you can use an ArrayBuffer or a typed array as a pointer
+  let c_arraybuffer = new ArrayBuffer(256);
+  let typed_array_samples =
+       [
+         [new Int8Array(c_arraybuffer), ctypes.int8_t],
+         [new Uint8Array(c_arraybuffer), ctypes.uint8_t],
+         [new Int16Array(c_arraybuffer), ctypes.int16_t],
+         [new Uint16Array(c_arraybuffer), ctypes.uint16_t],
+         [new Int32Array(c_arraybuffer), ctypes.int32_t],
+         [new Uint32Array(c_arraybuffer), ctypes.uint32_t],
+         [new Float32Array(c_arraybuffer), ctypes.float32_t],
+         [new Float64Array(c_arraybuffer), ctypes.float64_t]
+        ];
+  for (let i = 0; i < typed_array_samples.length; ++i) {
+    for (let j = 0; j < typed_array_samples.length; ++j) {
+      let view = typed_array_samples[i][0];
+      let item_type = typed_array_samples[j][1];
+      let number_of_items = c_arraybuffer.byteLength / item_type.size;
+      let array_type = item_type.array(number_of_items);
+
+      if (i != j) {
+        do_print("Checking that typed array " + (view.constructor.name) +
+                 " canNOT be converted to " + item_type + " pointer/array");
+        do_check_throws(function() { item_type.ptr(view); }, TypeError);
+        do_check_throws(function() { array_type(view); }, TypeError);
+
+      } else {
+        do_print("Checking that typed array " + (view.constructor.name) +
+                 " can be converted to " + item_type + " pointer/array");
+        // Fill buffer using view
+        for (let k = 0; k < number_of_items; ++k) {
+          view[k] = k;
+        }
+
+        // Convert ArrayBuffer to pointer then array and check contents
+        let c_ptr = item_type.ptr(c_arraybuffer);
+        let c_array = ctypes.cast(c_ptr, array_type.ptr).contents;
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        // Convert view to pointer then array and check contents
+        c_ptr = item_type.ptr(view);
+        c_array = ctypes.cast(c_ptr, array_type.ptr).contents;
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        // Convert ArrayBuffer to array of the right size and check contents
+        c_array = array_type(c_arraybuffer);
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        // Convert typed array to array of the right size and check contents
+        c_array = array_type(view);
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        let array_type_too_large = item_type.array(number_of_items + 1);
+        let array_type_too_small = item_type.array(number_of_items - 1);
+
+        do_check_throws(function() { array_type_too_large(c_arraybuffer); }, Error);
+        do_check_throws(function() { array_type_too_small(c_arraybuffer); }, Error);
+        do_check_throws(function() { array_type_too_large(view); }, Error);
+        do_check_throws(function() { array_type_too_small(view); }, Error)
+
+        // Convert array to void*
+        ctypes.voidptr_t(c_arraybuffer);
+
+        // Convert view to void*
+        ctypes.voidptr_t(view);
+      }
+    }
   }
 }
 
 function run_FunctionType_tests() {
   run_type_ctor_class_tests(ctypes.FunctionType,
     ctypes.FunctionType(ctypes.default_abi, ctypes.void_t),
     ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [ ctypes.int32_t ]),
     [ "abi", "returnType", "argTypes", "isVariadic" ],
@@ -2005,35 +2069,16 @@ function run_ArrayType_tests() {
 
   c.value = c;
   do_check_eq(c[3], 4);
   do_check_throws(function() { c.value; }, Error);
   do_check_throws(function() { c.value = [1, 2, 3, 4, 5]; }, Error);
   do_check_throws(function() { c.value = [1, 2, 3, 4, 5, 6, 7]; }, Error);
   do_check_throws(function() { c.value = [1, 2, 7.4, 4, 5, 6]; }, TypeError);
   do_check_throws(function() { c.value = []; }, Error);
-
-  // Check that you can use an ArrayBuffer with the right size for an array
-  let c_arraybuffer = new ArrayBuffer(24);
-  let c_int32 = new Int32Array(c_arraybuffer);
-  for (let i = 0; i < 6; ++i) {
-    c_int32[i] = i + 101;
-  }
-  c.value = c_arraybuffer;
-  do_check_eq(c[3], 104);
-  do_check_eq(c.toSource(), "ctypes.int32_t.array(6)([101, 102, 103, 104, 105, 106])");
-  do_check_eq(c.toSource(), c.toString());
-  eval("let c2 = " + c.toSource());
-  do_check_eq(c2.constructor.name, "int32_t[6]");
-  do_check_eq(c2.length, 6);
-  do_check_eq(c2[3], c[3]);
-
-  // Check that you cannot use an ArrayBuffer with the wrong size for an array
-  c_arraybuffer = new ArrayBuffer(20);
-  do_check_throws(function () { c.value = c_arraybuffer; }, Error);
 }
 
 function run_type_toString_tests() {
   var c = ctypes;
 
   // Figure out whether we can create functions with ctypes.stdcall_abi and ctypes.winapi_abi.
   var haveStdCallABI;
   try {
--- a/toolkit/components/ctypes/tests/xpcshellTestHarnessAdaptor.js
+++ b/toolkit/components/ctypes/tests/xpcshellTestHarnessAdaptor.js
@@ -33,16 +33,20 @@ function do_check_eq(left, right, stack)
 function do_check_true(condition, stack) {
   do_check_eq(condition, true, stack);
 }
 
 function do_check_false(condition, stack) {
   do_check_eq(condition, false, stack);
 }
 
+function do_print(text) {
+  dump("INFO: " + text + "\n");
+}
+
 function FileFaker(path) {
   this._path = path;
 }
 FileFaker.prototype = {
   get path() {
     return this._path;
   },
   get parent() {