Skip to content

Commit

Permalink
init work for buffer protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
delta003 committed Sep 1, 2023
1 parent 572f048 commit 94714e9
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 0 deletions.
30 changes: 30 additions & 0 deletions example/buffers.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const std = @import("std");
const py = @import("pydust");

pub fn sum(args: *const struct { arr: py.PyObject }) !u64 {
var out: py.PyBuffer = try py.PyBuffer.get(args.arr);
defer out.decref();

const values = try out.asSliceView(u64);
var s: u64 = 0;
for (values) |v| s += v;
return s;
}

pub fn reverse(args: *const struct { arr: py.PyObject }) !py.PyObject {
var out: py.PyBuffer = try py.PyBuffer.get(args.arr);
// don't decref out because we return it as result

// we can just work with slice, but this tests getPtr
const length: usize = @intCast(out.shape[0]);
const iter: usize = @divFloor(length, 2);
for (0..iter) |i| {
var left = try out.getPtr(u64, &[_]isize{@intCast(i)});
var right = try out.getPtr(u64, &[_]isize{@intCast(length - i - 1)});
const tmp: u64 = left.*;
left.* = right.*;
right.* = tmp;
}

return out.obj;
}
3 changes: 3 additions & 0 deletions example/modules.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ pub fn count(self: *const Self) u32 {
comptime {
py.module(@This());
}

// TODO(marko): Move this to submodule
pub usingnamespace @import("buffers.zig");
1 change: 1 addition & 0 deletions pydust/src/types.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub usingnamespace @import("types/buffer.zig");
pub usingnamespace @import("types/error.zig");
pub usingnamespace @import("types/float.zig");
pub usingnamespace @import("types/long.zig");
Expand Down
75 changes: 75 additions & 0 deletions pydust/src/types/buffer.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const std = @import("std");
const py = @import("../pydust.zig");
const ffi = py.ffi;
const PyError = @import("../errors.zig").PyError;

/// Wrapper for Python Py_buffer.
/// See: https://docs.python.org/3/c-api/buffer.html
pub const PyBuffer = extern struct {
const Self = @This();

buf: ?[*]u8,
obj: py.PyObject,
// product(shape) * itemsize.
// For contiguous arrays, this is the length of the underlying memory block.
// For non-contiguous arrays, it is the length that the logical structure would
// have if it were copied to a contiguous representation.
len: isize,
itemsize: isize,
readonly: c_int,
ndim: c_int,
format: [*:0]u8,
shape: [*:0]isize,
strides: [*:0]isize,
suboffsets: [*:0]isize,
internal: ?*anyopaque,

pub fn get(obj: py.PyObject) !Self {
return getWithFlag(obj, ffi.PyBUF_FULL);
}

pub fn getro(obj: py.PyObject) !Self {
return getWithFlag(obj, ffi.PyBUF_FULL_RO);
}

pub fn getWithFlag(obj: py.PyObject, flag: c_int) !Self {
if (ffi.PyObject_CheckBuffer(obj.py) != 1) {
// TODO(marko): This should be an error once we figure out how to do it
@panic("not a buffer");
}
var out: Self = undefined;
if (ffi.PyObject_GetBuffer(obj.py, @ptrCast(&out), flag) != 0) {
// TODO(marko): This should be an error once we figure out how to do it
@panic("unable to get buffer");
}
return out;
}

pub fn asSliceView(self: *const Self, comptime value_type: type) ![]value_type {
if (ffi.PyBuffer_IsContiguous(@ptrCast(self), 'C') != 1) {
// TODO(marko): This should be an error once we figure out how to do it
@panic("only continuous buffers are supported for view - use getPtr instead");
}
return @alignCast(std.mem.bytesAsSlice(value_type, self.buf.?[0..@intCast(self.len)]));
}

pub fn fromOwnedSlice(comptime value_type: type, values: []value_type) !Self {
_ = values;
// TODO(marko): We need to create an object using PyType_FromSpec and register buffer release
@panic("not implemented");
}

pub fn getPtr(self: *const Self, comptime value_type: type, item: [*]const isize) !*value_type {
var ptr: *anyopaque = ffi.PyBuffer_GetPointer(@ptrCast(self), item) orelse return PyError.Propagate;
return @ptrCast(@alignCast(ptr));
}

pub fn incref(self: *Self) void {
self.obj.incref();
}

pub fn decref(self: *Self) void {
// decrefs the underlying object
ffi.PyBuffer_Release(@ptrCast(self));
}
};
1 change: 1 addition & 0 deletions pydust/src/types/error.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub const PyExc = struct {
PyErr.setString(self, message);
}

// TODO(marko): this doesn't work, can't do it in compile time
pub const BaseException: PyExc = .{ .obj = .{ .py = ffi.PyExc_BaseException } };
pub const TypeError: PyExc = .{ .obj = .{ .py = ffi.PyExc_TypeError } };
pub const ValueError: PyExc = .{ .obj = .{ .py = ffi.PyExc_ValueError } };
Expand Down
12 changes: 12 additions & 0 deletions test/test_buffers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from example import modules
from array import array # implements buffer protocol


def test_sum():
arr = array("L", [1, 2, 3, 4, 5]) # uint64
assert modules.sum(arr) == 15


def test_reverse():
arr = array("L", [1, 2, 3, 4, 5]) # uint64
assert arr == modules.reverse(arr)

0 comments on commit 94714e9

Please sign in to comment.