Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trampoline Revamp #36

Merged
merged 28 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const pythonInc = getPythonIncludePath(b.allocator) catch @panic("Missing python");
const pythonLib = getPythonLibraryPath(b.allocator) catch @panic("Missing python");

const test_step = b.step("test", "Run library tests");

const main_tests = b.addTest(.{
Expand All @@ -13,14 +16,29 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
main_tests.linkLibC();
main_tests.addIncludePath(.{ .path = getPythonIncludePath(b.allocator) catch @panic("Missing python") });
main_tests.addLibraryPath(.{ .path = getPythonLibraryPath(b.allocator) catch @panic("Missing python") });
main_tests.addIncludePath(.{ .path = pythonInc });
main_tests.addLibraryPath(.{ .path = pythonLib });
main_tests.linkSystemLibrary("python3.11");
main_tests.addAnonymousModule("pyconf", .{ .source_file = .{ .path = "./pyconf.dummy.zig" } });

const run_main_tests = b.addRunArtifact(main_tests);
test_step.dependOn(&run_main_tests.step);

// Setup a library target to trick the Zig Language Server into providing completions for @import("pydust")
const example_lib = b.addSharedLibrary(.{
.name = "example",
.root_source_file = .{ .path = "example/hello.zig" },
.main_pkg_path = .{ .path = "example" },
.target = target,
.optimize = optimize,
});
example_lib.linkLibC();
main_tests.addIncludePath(.{ .path = pythonInc });
main_tests.addLibraryPath(.{ .path = pythonLib });
main_tests.linkSystemLibrary("python3.11");
main_tests.addAnonymousModule("pydust", .{ .source_file = .{ .path = "pydust/src/pydust.zig" } });
main_tests.addAnonymousModule("pyconf", .{ .source_file = .{ .path = "./pyconf.dummy.zig" } });

// Option for emitting test binary based on the given root source.
// This is used for debugging as in .vscode/tasks.json
const test_debug_root = b.option([]const u8, "test-debug-root", "The root path of a file emitted as a binary for use with the debugger");
Expand Down
31 changes: 22 additions & 9 deletions example/buffers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,46 @@ pub const ConstantBuffer = py.class("ConstantBuffer", struct {
const Self = @This();

values: []i64,
pylength: isize, // isize to be compatible with Python API
shape: []const isize, // isize to be compatible with Python API
format: [:0]const u8 = "l", // i64

pub fn __init__(self: *Self, args: *const extern struct { elem: py.PyLong, size: py.PyLong }) !void {
self.values = try py.allocator.alloc(i64, try args.size.as(u64));
@memset(self.values, try args.elem.as(i64));
self.pylength = @intCast(self.values.len);
pub fn __new__(args: struct { elem: i64, length: u32 }) !Self {
const values = try py.allocator.alloc(i64, args.length);
@memset(values, args.elem);

const shape = try py.allocator.alloc(isize, 1);
shape[0] = @intCast(args.length);

return Self{
.values = values,
.shape = shape,
};
}

pub fn __del__(self: *Self) void {
py.allocator.free(self.values);
py.allocator.free(self.shape);
}

pub fn __buffer__(self: *const Self, view: *py.PyBuffer, flags: c_int) !void {
// For more details on request types, see https://docs.python.org/3/c-api/buffer.html#buffer-request-types
if (flags & py.PyBuffer.Flags.WRITABLE != 0) {
return py.BufferError.raise("request for writable buffer is rejected");
}
const pyObj = try py.self(self);
view.initFromSlice(i64, self.values, @ptrCast(&self.pylength), pyObj);
view.initFromSlice(i64, self.values, self.shape, self);
}

pub fn __release_buffer__(self: *const Self, view: *py.PyBuffer) void {
py.allocator.free(self.values);
_ = self;
// FIXME(ngates): ref count the buffer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what reference you mean here. The view holds a reference to an obj, and https://docs.python.org/3/c-api/buffer.html#c.PyBuffer_Release decrefs that reference AND releases the view which calls this function. This function is optional so I think that means no decrefs need to happen here, and re incref - this was helpful https://jakevdp.github.io/blog/2014/05/05/introduction-to-the-python-buffer-protocol/#Exploring-the-Buffer-Protocol-Code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhhh perfect. So we don't need release_buffer at all in this case then, since it's covered by the python object's refcnt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, but might be useful to have it empty given this is an example

// py.allocator.free(self.values);
// It might be necessary to clear the view here in case the __bufferr__ method allocates view properties.
_ = view;
}
});

// A function that accepts an object implementing the buffer protocol.
pub fn sum(args: *const extern struct { buf: py.PyObject }) !i64 {
pub fn sum(args: struct { buf: py.PyObject }) !i64 {
var view: py.PyBuffer = undefined;
// ND is required by asSlice.
try args.buf.getBuffer(&view, py.PyBuffer.Flags.ND);
Expand Down
31 changes: 18 additions & 13 deletions example/classes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ pub const Animal = py.class("Animal", struct {

kind: u64,

pub fn __init__(self: *Self, args: *const extern struct { kind: py.PyLong }) !void {
self.kind = try args.kind.as(u64);
pub fn __new__(args: struct { kind: u64 }) !Self {
return .{ .kind = args.kind };
}

pub fn get_kind(self: *Self) !py.PyLong {
return py.PyLong.from(u64, self.kind);
pub fn get_kind(self: *Self) !u64 {
return self.kind;
}

pub fn get_kind_name(self: *Self) !py.PyString {
Expand All @@ -30,15 +30,16 @@ pub const Dog = py.subclass("Dog", &.{Animal}, struct {
pub const __doc__ = "Adorable animal docstring";
const Self = @This();

// A subclass of a Pydust class is required to hold its parent's state.
animal: Animal,
name: py.PyString,

pub fn __init__(self: *Self, args: *const extern struct { name: py.PyString }) !void {
var kind = try py.PyLong.from(u64, 1);
defer kind.decref();
try Animal.__init__(&self.animal, &.{ .kind = kind });
pub fn __new__(args: struct { name: py.PyString }) !Self {
args.name.incref();
self.name = args.name;
return .{
.animal = try Animal.__new__(.{ .kind = 1 }),
.name = args.name,
};
}

pub fn __del__(self: *Self) void {
Expand All @@ -54,14 +55,18 @@ pub const Dog = py.subclass("Dog", &.{Animal}, struct {
return self.name;
}

pub fn make_noise() !py.PyString {
return py.PyString.fromSlice("Bark!");
pub fn make_noise(args: struct { is_loud: bool = false }) !py.PyString {
if (args.is_loud) {
return py.PyString.fromSlice("Bark!");
} else {
return py.PyString.fromSlice("bark...");
}
}

pub fn get_kind_name(self: *Self) !py.PyString {
var super = try py.super(Dog, self);
var superKind = try super.get("get_kind_name");
var kindStr = py.PyString.of(try superKind.call0());
var kindStr = try py.PyString.of(try superKind.call0());
kindStr = try kindStr.appendSlice(" named ");
kindStr = try kindStr.append(self.name);
return kindStr;
Expand All @@ -71,7 +76,7 @@ pub const Dog = py.subclass("Dog", &.{Animal}, struct {
pub const Owner = py.class("Owner", struct {
pub const __doc__ = "Takes care of an animal";

pub fn name_puppy(args: *const extern struct { name: py.PyString }) !py.PyObject {
pub fn name_puppy(args: struct { name: py.PyString }) !*Dog {
return try py.init(Dog, .{ .name = args.name });
}
});
Expand Down
2 changes: 1 addition & 1 deletion example/exceptions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const std = @import("std");
const py = @import("pydust");

// --8<-- [start:valueerror]
pub fn raise_value_error(args: *const struct { message: py.PyString }) !void {
pub fn raise_value_error(args: struct { message: py.PyString }) !void {
return py.ValueError.raise(try args.message.asSlice());
}
// --8<-- [end:valueerror]
Expand Down
4 changes: 2 additions & 2 deletions example/functions.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const py = @import("pydust");

pub fn double(args: *const extern struct { x: py.PyLong }) !i64 {
return try args.x.as(i64) * 2;
pub fn double(args: struct { x: i64 }) i64 {
return args.x * 2;
}

comptime {
Expand Down
4 changes: 2 additions & 2 deletions example/memory.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const py = @import("pydust");

// --8<-- [start:append]
pub fn append(args: *const struct { left: py.PyString }) !py.PyString {
pub fn append(args: struct { left: py.PyString }) !py.PyString {
// Since we create right, we must also decref it.
const right = try py.PyString.fromSlice("right");
defer right.decref();
Expand All @@ -14,7 +14,7 @@ pub fn append(args: *const struct { left: py.PyString }) !py.PyString {
// --8<-- [end:append]

// --8<-- [start:concat]
pub fn concat(args: *const struct { left: py.PyString }) !py.PyString {
pub fn concat(args: struct { left: py.PyString }) !py.PyString {
return args.left.concatSlice("right");
}
// --8<-- [end:concat]
Expand Down
2 changes: 1 addition & 1 deletion example/modules.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn whoami(self: *const Self) !py.PyString {

pub fn hello(
self: *const Self,
args: *const struct { name: py.PyString }, // (5)!
args: struct { name: py.PyString }, // (5)!
) !py.PyString {
var str = try py.PyString.fromSlice("Hello, ");
str = try str.append(args.name);
Expand Down
36 changes: 35 additions & 1 deletion pydust/src/builtins.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,49 @@ const py = @import("./pydust.zig");
const ffi = @import("./ffi.zig");
const PyError = @import("./errors.zig").PyError;

/// Returns a new reference to Py_None.
pub inline fn None() py.PyObject {
// It's important that we incref the Py_None singleton
const none = py.PyObject{ .py = ffi.Py_None };
none.incref();
return none;
}

/// Checks whether a given object is None. Avoids incref'ing None to do the check.
pub inline fn is_none(object: anytype) bool {
const obj = try py.PyObject.from(object);
return ffi.Py_IsNone(obj.py) == 1;
}

/// Returns a new reference to Py_False.
pub inline fn False() py.PyBool {
return py.PyBool.false_();
}

/// Returns a new reference to Py_True.
pub inline fn True() py.PyBool {
return py.PyBool.true_();
}

/// Get the length of the given object. Equivalent to len(obj) in Python.
pub fn len(object: anytype) !usize {
const obj = try py.PyObject.from(object);
const length = ffi.PyObject_Length(obj.py);
if (length < 0) return PyError.Propagate;
return length;
return @intCast(length);
}

/// Import a module by fully-qualified name returning a PyObject.
pub fn import(module_name: [:0]const u8) !py.PyObject {
return (try py.PyModule.import(module_name)).obj;
}

/// The equivalent of Python's super() builtin. Returns a PyObject.
pub fn super(comptime Super: type, selfInstance: anytype) !py.PyObject {
const imported = try import(py.findContainingModule(Super));
const superPyType = try imported.get(py.getClassName(Super));
const pyObj = try py.object(selfInstance);

const superBuiltin = py.PyObject{ .py = @alignCast(@ptrCast(&ffi.PySuper_Type)) };
return superBuiltin.call(py.PyObject, .{ superPyType, pyObj }, .{});
}
Loading
Loading