From e800c71692e794b1840281698d7aec1126d8d89d Mon Sep 17 00:00:00 2001 From: richerfu Date: Thu, 11 Dec 2025 10:22:30 +0800 Subject: [PATCH 1/3] fix: use global allocator as default allocator --- src/napi/util/allocator.zig | 2 ++ src/napi/value/array.zig | 9 +++++---- src/napi/value/function.zig | 5 +++-- src/napi/value/string.zig | 4 ++-- src/napi/wrapper/callback_info.zig | 16 ++++++++++++++-- src/napi/wrapper/thread_safe_function.zig | 20 +++++++++++++++----- src/napi/wrapper/worker.zig | 6 +++++- 7 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/napi/util/allocator.zig b/src/napi/util/allocator.zig index 703bd74..6e8db2f 100644 --- a/src/napi/util/allocator.zig +++ b/src/napi/util/allocator.zig @@ -22,10 +22,12 @@ pub const AllocatorManager = struct { pub const global_manager = AllocatorManager.init(); +/// Get the global allocator pub fn globalAllocator() std.mem.Allocator { return global_manager.get(); } +/// Set a custom global allocator pub fn setGlobalAllocator(new_allocator: std.mem.Allocator) void { global_manager.set(new_allocator); } diff --git a/src/napi/value/array.zig b/src/napi/value/array.zig index 73ac479..47406b9 100644 --- a/src/napi/value/array.zig +++ b/src/napi/value/array.zig @@ -5,6 +5,7 @@ const Napi = @import("../util/napi.zig").Napi; const helper = @import("../util/helper.zig"); const ArrayList = std.ArrayList; const NapiError = @import("../wrapper/error.zig"); +const GlobalAllocator = @import("../util/allocator.zig"); pub const Array = struct { env: napi.napi_env, @@ -40,7 +41,7 @@ pub const Array = struct { var len: u32 = undefined; _ = napi.napi_get_array_length(env, raw, &len); - const allocator = std.heap.page_allocator; + const allocator = GlobalAllocator.globalAllocator(); const buf = allocator.alloc(infos.pointer.child, len) catch @panic("OOM"); for (0..len) |i| { @@ -67,16 +68,16 @@ pub const Array = struct { // Get Array List's items type const child = comptime helper.getArrayListElementType(T); - const gpa = std.heap.page_allocator; + const allocator = GlobalAllocator.globalAllocator(); var result: T = ArrayList(child).empty; var len: u32 = undefined; _ = napi.napi_get_array_length(env, raw, &len); - result.ensureTotalCapacity(gpa, len) catch @panic("OOM"); + result.ensureTotalCapacity(allocator, len) catch @panic("OOM"); for (0..len) |i| { var element: napi.napi_value = undefined; _ = napi.napi_get_element(env, raw, @intCast(i), &element); - result.append(gpa, Napi.from_napi_value(env, element, child)) catch @panic("OOM"); + result.append(allocator, Napi.from_napi_value(env, element, child)) catch @panic("OOM"); } return result; } diff --git a/src/napi/value/function.zig b/src/napi/value/function.zig index ac2039b..49ca02a 100644 --- a/src/napi/value/function.zig +++ b/src/napi/value/function.zig @@ -5,6 +5,7 @@ const CallbackInfo = @import("../wrapper/callback_info.zig").CallbackInfo; const Napi = @import("../util/napi.zig").Napi; const NapiError = @import("../wrapper/error.zig"); const Undefined = @import("./undefined.zig").Undefined; +const GlobalAllocator = @import("../util/allocator.zig"); pub fn Function(comptime Args: type, comptime Return: type) type { const ArgsInfos = @typeInfo(Args); @@ -37,7 +38,7 @@ pub fn Function(comptime Args: type, comptime Return: type) type { const undefined_value = Undefined.New(Env.from_raw(inner_env)); var init_argc: usize = params.len; - const allocator = std.heap.page_allocator; + const allocator = GlobalAllocator.globalAllocator(); const args_raw = allocator.alloc(napi.napi_value, init_argc) catch @panic("OOM"); defer allocator.free(args_raw); @@ -111,7 +112,7 @@ pub fn Function(comptime Args: type, comptime Return: type) type { const args_len = if (isTuple) ArgsInfos.@"struct".fields.len else 1; - const allocator = std.heap.page_allocator; + const allocator = GlobalAllocator.globalAllocator(); const args_raw = allocator.alloc(napi.napi_value, args_len) catch @panic("OOM"); defer allocator.free(args_raw); diff --git a/src/napi/value/string.zig b/src/napi/value/string.zig index 45cbb93..d2ab265 100644 --- a/src/napi/value/string.zig +++ b/src/napi/value/string.zig @@ -3,6 +3,7 @@ const napi = @import("napi-sys").napi_sys; const Value = @import("../value.zig").Value; const Env = @import("../env.zig").Env; const helper = @import("../util/helper.zig"); +const GlobalAllocator = @import("../util/allocator.zig"); pub const String = struct { env: napi.napi_env, @@ -15,12 +16,12 @@ pub const String = struct { pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { const stringMode = comptime helper.stringLike(T); + const allocator = GlobalAllocator.globalAllocator(); switch (stringMode) { .Utf8 => { var len: usize = 0; _ = napi.napi_get_value_string_utf8(env, raw, null, 0, &len); - const allocator = std.heap.page_allocator; const buf = allocator.alloc(u8, len + 1) catch @panic("OOM"); _ = napi.napi_get_value_string_utf8(env, raw, buf.ptr, len + 1, null); @@ -30,7 +31,6 @@ pub const String = struct { var len: usize = 0; _ = napi.napi_get_value_string_utf16(env, raw, null, 0, &len); - const allocator = std.heap.page_allocator; const buf = allocator.alloc(u16, len + 1) catch @panic("OOM"); _ = napi.napi_get_value_string_utf16(env, raw, buf.ptr, len + 1, null); diff --git a/src/napi/wrapper/callback_info.zig b/src/napi/wrapper/callback_info.zig index 92c7467..4db4ffd 100644 --- a/src/napi/wrapper/callback_info.zig +++ b/src/napi/wrapper/callback_info.zig @@ -19,16 +19,21 @@ pub const CallbackInfo = struct { @panic("Failed to get callback info"); } - const args_raw = GlobalAllocator.globalAllocator().alloc(napi.napi_value, init_argc) catch @panic("OOM"); + const allocator = GlobalAllocator.globalAllocator(); + const args_raw = allocator.alloc(napi.napi_value, init_argc) catch @panic("OOM"); var this: napi.napi_value = undefined; const status2 = napi.napi_get_cb_info(env, raw, &init_argc, args_raw.ptr, &this, null); if (status2 != napi.napi_ok) { + allocator.free(args_raw); @panic("Failed to get callback info"); } - const result = GlobalAllocator.globalAllocator().alloc(value.NapiValue, init_argc) catch @panic("OOM"); + const result = allocator.alloc(value.NapiValue, init_argc) catch { + allocator.free(args_raw); + @panic("OOM"); + }; for (0..init_argc) |i| { result[i] = value.NapiValue.from_raw(env, args_raw[i]); @@ -44,6 +49,13 @@ pub const CallbackInfo = struct { }; } + /// Free the allocated memory for args and args_raw + pub fn deinit(self: *const CallbackInfo) void { + const allocator = GlobalAllocator.globalAllocator(); + allocator.free(self.args_raw); + allocator.free(self.args); + } + pub fn Env(self: CallbackInfo) NapiEnv { return NapiEnv.from_raw(self.env); } diff --git a/src/napi/wrapper/thread_safe_function.zig b/src/napi/wrapper/thread_safe_function.zig index f1a4568..e658ff4 100644 --- a/src/napi/wrapper/thread_safe_function.zig +++ b/src/napi/wrapper/thread_safe_function.zig @@ -6,6 +6,7 @@ const Null = @import("../value/null.zig").Null; const Env = @import("../env.zig").Env; const NapiError = @import("./error.zig"); const String = @import("../value/string.zig").String; +const GlobalAllocator = @import("../util/allocator.zig"); pub const ThreadSafeFunctionMode = enum { NonBlocking, @@ -55,12 +56,13 @@ pub fn ThreadSafeFunction(comptime Args: type, comptime Return: type, comptime T fn cb(inner_env: napi.napi_env, js_callback: napi.napi_value, context: ?*anyopaque, data: ?*anyopaque) callconv(.c) void { const self: *Self = @ptrCast(@alignCast(context)); const args: *CallData(Args) = @ptrCast(@alignCast(data)); + const allocator = self.allocator; const args_len = if (@typeInfo(Args) == .@"struct" and @typeInfo(Args).@"struct".is_tuple) @typeInfo(Args).@"struct".fields.len else 1; const call_variant = if (self.thread_safe_function_call_variant) 1 else 0; - const argv = self.allocator.alloc(napi.napi_value, args_len + call_variant) catch @panic("OOM"); - defer self.allocator.free(argv); + const argv = allocator.alloc(napi.napi_value, args_len + call_variant) catch @panic("OOM"); + defer allocator.free(argv); @memset(argv, null); const undefined_value = Undefined.New(Env.from_raw(inner_env)); @@ -71,6 +73,9 @@ pub fn ThreadSafeFunction(comptime Args: type, comptime Return: type, comptime T // if err, return immediately var ret: napi.napi_value = undefined; _ = napi.napi_call_function(inner_env, undefined_value.raw, js_callback, args_len + call_variant, argv.ptr, &ret); + // Free the call data + allocator.destroy(param); + allocator.destroy(args); return; } else { argv[0] = Null.New(Env.from_raw(inner_env)).raw; @@ -80,19 +85,24 @@ pub fn ThreadSafeFunction(comptime Args: type, comptime Return: type, comptime T if (args.args) |actual_args| { if (@typeInfo(Args) == .@"struct" and @typeInfo(Args).@"struct".is_tuple) { inline for (@typeInfo(Args).@"struct".fields, 0..) |field, i| { - argv[i + call_variant] = try Napi.to_napi_value(inner_env, @field(actual_args.*, field.name), null); + argv[i + call_variant] = Napi.to_napi_value(inner_env, @field(actual_args.*, field.name), null) catch null; } } else { - argv[call_variant] = try Napi.to_napi_value(inner_env, actual_args.*, null); + argv[call_variant] = Napi.to_napi_value(inner_env, actual_args.*, null) catch null; } + // Free the args data + allocator.destroy(actual_args); } var ret: napi.napi_value = undefined; _ = napi.napi_call_function(inner_env, undefined_value.raw, js_callback, args_len + call_variant, argv.ptr, &ret); + + // Free the call data + allocator.destroy(args); } }; - const allocator = std.heap.page_allocator; + const allocator = GlobalAllocator.globalAllocator(); var self = allocator.create(Self) catch @panic("OOM"); self.* = Self{ .env = env, .raw = raw, .allocator = allocator, .args = undefined, .return_type = undefined, .tsfn_raw = undefined }; diff --git a/src/napi/wrapper/worker.zig b/src/napi/wrapper/worker.zig index c6a9793..e541e10 100644 --- a/src/napi/wrapper/worker.zig +++ b/src/napi/wrapper/worker.zig @@ -7,6 +7,7 @@ const Value = @import("../value.zig").Value; const Promise = @import("../value/promise.zig").Promise; const Napi = @import("../util/napi.zig").Napi; const NapiError = @import("./error.zig"); +const GlobalAllocator = @import("../util/allocator.zig"); const WorkerStatus = enum { Pending, @@ -180,7 +181,7 @@ pub fn WorkerContext(comptime T: type) type { } }; - const allocator = std.heap.page_allocator; + const allocator = GlobalAllocator.globalAllocator(); var self = allocator.create(Self) catch @panic("OOM"); self.* = Self{ @@ -205,6 +206,9 @@ pub fn WorkerContext(comptime T: type) type { } pub fn deinit(self: *Self) void { + if (self.promise) |promise| { + self.allocator.destroy(promise); + } self.allocator.destroy(self); } From 63cbeb56666f61d277b8a9d8456211c0cd3e1eb9 Mon Sep 17 00:00:00 2001 From: richerfu Date: Thu, 11 Dec 2025 14:36:54 +0800 Subject: [PATCH 2/3] feat: add types for example --- examples/basic/index.d.ts | 295 ++++++++++++++++++++++++++++++++++++++ examples/init/index.d.ts | 115 +++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 examples/basic/index.d.ts create mode 100644 examples/init/index.d.ts diff --git a/examples/basic/index.d.ts b/examples/basic/index.d.ts new file mode 100644 index 0000000..0776029 --- /dev/null +++ b/examples/basic/index.d.ts @@ -0,0 +1,295 @@ +/** + * Basic example module for zig-napi + * OpenHarmony/HarmonyNext native module written in Zig + */ + +// ============== Number Functions ============== + +/** + * Adds two 32-bit signed integers + * @param left - The first integer + * @param right - The second integer + * @returns The sum of left and right + */ +export function test_i32(left: number, right: number): number; + +/** + * Adds two 32-bit floating-point numbers + * @param left - The first float + * @param right - The second float + * @returns The sum of left and right + */ +export function test_f32(left: number, right: number): number; + +/** + * Adds two 32-bit unsigned integers + * @param left - The first unsigned integer + * @param right - The second unsigned integer + * @returns The sum of left and right + */ +export function test_u32(left: number, right: number): number; + +// ============== String Functions ============== + +/** + * Returns a greeting message + * @param name - The name to greet + * @returns A greeting string "Hello, {name}!" + */ +export function hello(name: string): string; + +/** + * A constant text string + */ +export const text: string; + +// ============== Error Functions ============== + +/** + * Throws a test error + * @throws {Error} Always throws an error with reason "test" + */ +export function throw_error(): void; + +// ============== Worker Functions ============== + +/** + * Calculates fibonacci number asynchronously (fire and forget) + * @param n - The fibonacci index + */ +export function fib(n: number): void; + +/** + * Calculates fibonacci number asynchronously with Promise + * @param n - The fibonacci index + * @returns A Promise that resolves when calculation is complete + */ +export function fib_async(n: number): Promise; + +// ============== Array Functions ============== + +/** + * Takes an array and returns it + * @param array - An array of numbers + * @returns The same array + */ +export function get_and_return_array(array: number[]): number[]; + +/** + * Takes a tuple array and returns it + * @param array - A tuple of [number, boolean, string] + * @returns The same tuple + */ +export function get_named_array(array: [number, boolean, string]): [number, boolean, string]; + +/** + * Takes an ArrayList and returns it + * @param array - An array of numbers + * @returns The same array + */ +export function get_arraylist(array: number[]): number[]; + +// ============== Object Types ============== + +/** + * Full field object with all required fields + */ +export interface FullField { + name: string; + age: number; + is_student: boolean; +} + +/** + * Object with optional fields + */ +export interface OptionalField { + name: string; + age?: number; + is_student?: boolean; +} + +/** + * Object with nullable field + */ +export interface NullableField { + name: string | null; +} + +// ============== Object Functions ============== + +/** + * Takes a full field object and returns it + * @param config - Object with name, age, and is_student + * @returns The same object + */ +export function get_object(config: FullField): FullField; + +/** + * Takes an object with optional fields + * @param config - Object with name (required), age and is_student (optional) + * @returns Object with default values applied (age: 18, is_student: true) + */ +export function get_object_optional(config: OptionalField): OptionalField; + +/** + * Takes an optional object and returns it + * @param config - Object with optional fields + * @returns The same object + */ +export function get_optional_object_and_return_optional(config: OptionalField): OptionalField; + +/** + * Takes an object with nullable name field + * @param config - Object with nullable name + * @returns The same object + */ +export function get_nullable_object(config: NullableField): NullableField; + +/** + * Returns a nullable object with null name + * @returns Object with name set to null + */ +export function return_nullable(): NullableField; + +// ============== Function Types ============== + +/** + * Callback function type that takes two numbers and returns a number + */ +export type CallbackFunction = (arg0: number, arg1: number) => number; + +// ============== Function Functions ============== + +/** + * Calls the provided callback function with (1, 2) + * @param cb - A callback function that takes two numbers and returns a number + * @returns The result of calling cb(1, 2) + */ +export function call_function(cb: CallbackFunction): number; + +/** + * Adds two numbers + * @param left - The first number + * @param right - The second number + * @returns The sum of left and right + */ +export function basic_function(left: number, right: number): number; + +/** + * Creates a new function that wraps basic_function + * @returns A function that adds two numbers + */ +export function create_function(): CallbackFunction; + +// ============== Thread Safe Function ============== + +/** + * Calls the thread safe function from multiple threads + * @param tsfn - A thread-safe callback function + */ +export function call_thread_safe_function(tsfn: CallbackFunction): void; + +// ============== Class Types ============== + +/** + * Basic test class with name and age properties + */ +export class TestClass { + constructor(name: string, age: number); + name: string; + age: number; +} + +/** + * Test class with custom init function + * Constructor takes (age, name) instead of field order + */ +export class TestWithInitClass { + constructor(age: number, name: string); + name: string; + age: number; + static readonly hello: string; +} + +/** + * Test class without constructor (abstract-like) + */ +export class TestWithoutInitClass { + private constructor(); + name: string; + age: number; + static readonly hello: string; +} + +/** + * Test class with factory method + */ +export class TestFactoryClass { + constructor(age: number, name: string); + name: string; + age: number; + /** + * Formats the object as a string + * @returns Formatted string representation + */ + format(): string; +} + +// ============== Log Functions ============== + +/** + * Tests hilog functionality (OpenHarmony logging) + */ +export function test_hilog(): void; + +// ============== Module Export ============== + +declare const hello: { + // Number + test_i32: typeof test_i32; + test_f32: typeof test_f32; + test_u32: typeof test_u32; + + // String + hello: typeof hello; + text: typeof text; + + // Error + throw_error: typeof throw_error; + + // Worker + fib: typeof fib; + fib_async: typeof fib_async; + + // Array + get_and_return_array: typeof get_and_return_array; + get_named_array: typeof get_named_array; + get_arraylist: typeof get_arraylist; + + // Object + get_object: typeof get_object; + get_object_optional: typeof get_object_optional; + get_optional_object_and_return_optional: typeof get_optional_object_and_return_optional; + get_nullable_object: typeof get_nullable_object; + return_nullable: typeof return_nullable; + + // Function + call_function: typeof call_function; + basic_function: typeof basic_function; + create_function: typeof create_function; + + // Thread Safe Function + call_thread_safe_function: typeof call_thread_safe_function; + + // Class + TestClass: typeof TestClass; + TestWithInitClass: typeof TestWithInitClass; + TestWithoutInitClass: typeof TestWithoutInitClass; + TestFactoryClass: typeof TestFactoryClass; + + // Log + test_hilog: typeof test_hilog; +}; + +export default hello; diff --git a/examples/init/index.d.ts b/examples/init/index.d.ts new file mode 100644 index 0000000..096296c --- /dev/null +++ b/examples/init/index.d.ts @@ -0,0 +1,115 @@ +/** + * Init example module for zig-napi + * OpenHarmony/HarmonyNext native module written in Zig + * Uses NODE_API_MODULE_WITH_INIT pattern + */ + +// ============== Number Functions (pub exports) ============== + +/** + * Adds two 32-bit signed integers + * @param left - The first integer + * @param right - The second integer + * @returns The sum of left and right + */ +export function test_i32(left: number, right: number): number; + +/** + * Adds two 32-bit floating-point numbers + * @param left - The first float + * @param right - The second float + * @returns The sum of left and right + */ +export function test_f32(left: number, right: number): number; + +/** + * Adds two 32-bit unsigned integers + * @param left - The first unsigned integer + * @param right - The second unsigned integer + * @returns The sum of left and right + */ +export function test_u32(left: number, right: number): number; + +// ============== Init Exports ============== + +/** + * Adds two 64-bit floating-point numbers + * @param left - The first number + * @param right - The second number + * @returns The sum of left and right + */ +export function add(left: number, right: number): number; + +/** + * Returns a greeting message + * @param name - The name to greet + * @returns A greeting string "Hello, {name}!" + */ +export function hello(name: string): string; + +/** + * A constant text string "Hello" + */ +export const text: string; + +/** + * Calculates fibonacci number asynchronously (fire and forget) + * @param n - The fibonacci index + */ +export function fib(n: number): void; + +/** + * Calculates fibonacci number asynchronously with Promise + * @param n - The fibonacci index + * @returns A Promise that resolves when calculation is complete + */ +export function fib_async(n: number): Promise; + +/** + * Takes an array of numbers and returns it + * @param array - An array of numbers + * @returns The same array + */ +export function get_and_return_array(array: number[]): number[]; + +/** + * Takes a tuple array and returns it + * @param array - A tuple of [number, boolean, string] + * @returns The same tuple + */ +export function get_named_array(array: [number, boolean, string]): [number, boolean, string]; + +/** + * Takes an ArrayList and returns it + * @param array - An array of numbers + * @returns The same array + */ +export function get_arraylist(array: number[]): number[]; + +/** + * Throws a test error + * @throws {Error} Always throws an error with reason "test" + */ +export function throw_error(): void; + +// ============== Module Export ============== + +declare const hello: { + // Number (pub exports) + test_i32: typeof test_i32; + test_f32: typeof test_f32; + test_u32: typeof test_u32; + + // Init exports + add: typeof add; + hello: typeof hello; + text: typeof text; + fib: typeof fib; + fib_async: typeof fib_async; + get_and_return_array: typeof get_and_return_array; + get_named_array: typeof get_named_array; + get_arraylist: typeof get_arraylist; + throw_error: typeof throw_error; +}; + +export default hello; From 4e55ad7dab4795ef9084facb3d5735844f56bd64 Mon Sep 17 00:00:00 2001 From: richerfu Date: Thu, 11 Dec 2025 20:24:31 +0800 Subject: [PATCH 3/3] chore: remove set globalAllocator --- src/napi.zig | 4 ---- src/napi/util/allocator.zig | 5 ----- 2 files changed, 9 deletions(-) diff --git a/src/napi.zig b/src/napi.zig index d450815..8c7fe6a 100644 --- a/src/napi.zig +++ b/src/napi.zig @@ -36,9 +36,5 @@ pub const ThreadSafeFunction = thread_safe_function.ThreadSafeFunction; pub const Class = class.Class; pub const ClassWithoutInit = class.ClassWithoutInit; -/// Default global allocator is the page allocator -/// You can change it by calling `GlobalAllocator.setGlobalAllocator` -pub const GlobalAllocator = DefaultGlobalAllocator; - pub const NODE_API_MODULE = module.NODE_API_MODULE; pub const NODE_API_MODULE_WITH_INIT = module.NODE_API_MODULE_WITH_INIT; diff --git a/src/napi/util/allocator.zig b/src/napi/util/allocator.zig index 6e8db2f..397ae50 100644 --- a/src/napi/util/allocator.zig +++ b/src/napi/util/allocator.zig @@ -26,8 +26,3 @@ pub const global_manager = AllocatorManager.init(); pub fn globalAllocator() std.mem.Allocator { return global_manager.get(); } - -/// Set a custom global allocator -pub fn setGlobalAllocator(new_allocator: std.mem.Allocator) void { - global_manager.set(new_allocator); -}