Skip to content

Commit

Permalink
Merge pull request #34 from vixalien/wip/vixalien/pointer-boxing
Browse files Browse the repository at this point in the history
Box arguments taking into consideration deep pointers
  • Loading branch information
ahgilak authored Oct 2, 2024
2 parents 3d49604 + 7442694 commit 8ab0cdc
Show file tree
Hide file tree
Showing 13 changed files with 488 additions and 158 deletions.
1 change: 1 addition & 0 deletions src/bindings/girepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const { g } = openLib(libName("girepository-1.0", 1), {
get_interface: $pointer($pointer),
get_param_type: $pointer($pointer, $i32),
is_zero_terminated: $bool($pointer),
is_pointer: $bool($pointer),
},
enum_info: {
get_n_values: $i32($pointer),
Expand Down
1 change: 1 addition & 0 deletions src/bindings/gobject.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const { g } = openLib(libName("gobject-2.0", 0), {
default_interface_ref: $pointer($i64),
parent: $i64($i64),
is_a: $bool($i64, $i64),
query: $pointer($i64),
},
type_class: {
ref: $pointer($i64),
Expand Down
224 changes: 191 additions & 33 deletions src/types/argument.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,130 @@
import g from "../bindings/mod.js";
import { GITypeTag } from "../bindings/enums.js";
import { GIInfoType, GITypeTag } from "../bindings/enums.js";
import {
cast_buf_ptr,
cast_ptr_u64,
cast_str_buf,
cast_u64_ptr,
deref_buf,
deref_ptr,
deref_str,
} from "../base_utils/convert.ts";
import { ExtendedDataView } from "../utils/dataview.js";
import { objectByInfo } from "../utils/gobject.js";
import { boxArray, unboxArray } from "./argument/array.js";
import { boxInterface, unboxInterface } from "./argument/interface.js";
import {
boxInterface,
getInterfaceSize,
unboxInterface,
} from "./argument/interface.js";
import { unboxList } from "./argument/list.js";
import { ensure_number_range } from "../bindings/ranges.ts";

export function initArgument(type) {
/**
* @param {Deno.PointerObject} info
* @returns {number | null}
*/
function getArgumentSize(type) {
const tag = g.type_info.get_tag(type);

switch (tag) {
case GITypeTag.INTERFACE: {
// TODO: just generate a space large enough to hold the type
const info = g.type_info.get_interface(type);
const o = objectByInfo(info);
const v = new o();
const result = cast_ptr_u64(Reflect.getOwnMetadata("gi:ref", v));
g.base_info.unref(info);
return result;
return getInterfaceSize(info);
}
default:
// generate a new pointer
return cast_ptr_u64(cast_buf_ptr(new ArrayBuffer(8)));
return 8;
}
}

function initPointer(size) {
const pointer = cast_buf_ptr(new ArrayBuffer(size));
return pointer;
}

/**
* @param {ExtendedDataView} view the view to use for initializing an argument
* @param {number} offset
* @param {Deno.PointerObject} type
* @param {number} n_pointers the number of deep pointers to create
*/
function initArgument(view, offset, type, n_pointers) {
// get the size of the argument and create various pointers
const pointer_size = getArgumentSize(type);
let pointer;

if (pointer_size) pointer = initPointer(pointer_size);

// initialize deep pointers
for (let i = 0; i < n_pointers; i++) {
// create a new pointer that points to the initialized value
const new_pointer = initPointer(8);
const view = new ExtendedDataView(deref_buf(new_pointer, 8));
if (pointer) view.setBigUint64(cast_ptr_u64(pointer));
pointer = new_pointer;
}

if (pointer) {
view.setBigUint64(cast_ptr_u64(pointer), offset);
}
}

/** Create a new buffer for a list of items
* @param {...([type: Deno.PointerObject, n_pointers: number] | Deno.PointerObject)} types a list of types or a tuple with a type and number of pointers
* @returns
*/
export function initArguments(...types) {
const buffer = new ArrayBuffer(types.length * 8);
const view = new ExtendedDataView(buffer);

for (let i = 0; i < types.length; i++) {
const element = types[i];
let type, n_pointers = 0;

if (Array.isArray(element)) {
type = element[0];
n_pointers = element[1];
} else {
type = element;
}

initArgument(view, i * 8, type, n_pointers);
}

return buffer;
}

function getDeepViews(buffer, offset, n_pointers) {
const views = [new ExtendedDataView(buffer, offset)];

for (let i = 0; i < n_pointers; i++) {
const pointer = views[0].getBigUint64();
if (pointer === 0n) {
views.unshift(new ExtendedDataView(new ArrayBuffer(8)));
break;
}
views.unshift(new ExtendedDataView(deref_buf(cast_u64_ptr(pointer), 8)));
}

return views;
}

/** This function is given a pointer OR a value, and must hence extract it
* @param {Deno.PointerObject} type
* @param {ArrayBuffer} buffer
* @param {number} [offset]
* @param {number} [n_pointers] how many times the argument is wrapped in pointers
* @param {number} [length] the length for arrays
* @returns
*/
export function unboxArgument(type, buffer, offset) {
export function unboxArgument(
type,
buffer,
offset,
n_pointers = 0,
length = -1,
) {
const tag = g.type_info.get_tag(type);
const dataView = new ExtendedDataView(buffer, offset);
const [dataView, containerView] = getDeepViews(buffer, offset, n_pointers);

switch (tag) {
case GITypeTag.VOID:
Expand Down Expand Up @@ -94,7 +177,9 @@ export function unboxArgument(type, buffer, offset) {
/* non-basic types */

case GITypeTag.ARRAY: {
return unboxArray(type, deref_ptr(buffer), -1);
// containerView may be empty nPointers = 0
const buffer = containerView?.buffer || dataView.buffer;
return unboxArray(type, buffer, length);
}

case GITypeTag.GLIST:
Expand All @@ -104,7 +189,25 @@ export function unboxArgument(type, buffer, offset) {

case GITypeTag.INTERFACE: {
const info = g.type_info.get_interface(type);
const result = unboxInterface(info, buffer);
const info_type = g.base_info.get_type(info);
let result;

switch (info_type) {
case GIInfoType.OBJECT:
case GIInfoType.STRUCT:
case GIInfoType.INTERFACE: {
result = unboxInterface(info, buffer);
break;
}
case GIInfoType.ENUM:
case GIInfoType.FLAGS: {
result = dataView.getInt32();
break;
}
default:
result = null;
}

g.base_info.unref(info);
return result;
}
Expand All @@ -114,9 +217,13 @@ export function unboxArgument(type, buffer, offset) {
}
}

export function boxArgument(type, value) {
const buffer = new ArrayBuffer(8);
const dataView = new ExtendedDataView(buffer);
export function boxArgument(
type,
value,
buffer = new ArrayBuffer(8),
offset = 0,
) {
const dataView = new ExtendedDataView(buffer, offset);
const tag = g.type_info.get_tag(type);

switch (tag) {
Expand Down Expand Up @@ -209,32 +316,36 @@ export function boxArgument(type, value) {
);
break;

case GITypeTag.GTYPE:
ensure_number_range(GITypeTag.GTYPE, value);
dataView.setBigUint64(value);
case GITypeTag.GTYPE: {
let numericValue = value;
// quick check to get the GType of a class
if (typeof value === "function") {
numericValue = Reflect.getMetadata("gi:gtype", value);
}
if (
typeof numericValue !== "bigint" && typeof numericValue !== "number"
) {
throw new TypeError("Expected a GType or a class");
}
ensure_number_range(GITypeTag.GTYPE, numericValue);
dataView.setBigUint64(numericValue);
break;
}

/* non-basic types */

case GITypeTag.ARRAY: {
if (Array.isArray(value)) {
value = boxArray(type, value);
}
const buffer = normalizeArray(type, value);
if (!buffer) break;

if (value) {
dataView.setBigUint64(
cast_ptr_u64(cast_buf_ptr(value)),
);
}
dataView.setBigUint64(cast_ptr_u64(cast_buf_ptr(buffer)));

break;
}

case GITypeTag.INTERFACE: {
const info = g.type_info.get_interface(type);
dataView.setBigUint64(
BigInt(boxInterface(info, value)),
);
dataView.setBigUint64(boxInterface(info, value));
g.base_info.unref(info);
break;
}
Expand All @@ -248,3 +359,50 @@ function normalizeNumber(value, allowNaN = false) {
if (allowNaN && isNaN(value)) return NaN;
return value || 0;
}

const typedArrays = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
];

/**
* @param {*} value
* @returns {value is import("../base_utils/ffipp.js").TypedArray}
*/
export function isTypedArray(value) {
return typedArrays.some((typedArray) => value instanceof typedArray);
}

/**
* @param {Deno.PointerObject} type
* @param {any} value
* @returns {ArrayBuffer}
*/
export function normalizeArray(type, value) {
if (!value) return null;

if (typeof value === "string") {
return boxArray(type, value.split("").map((char) => char.charCodeAt(0)));
}

if (Array.isArray(value) || isTypedArray(value)) {
return boxArray(type, value);
}

if (value instanceof ArrayBuffer) {
return value;
}

throw new TypeError(
"Expected a string, array, ArrayBuffer or TypedArray",
);
}
Loading

0 comments on commit 8ab0cdc

Please sign in to comment.