lassade / c2z

C++ to Zig bindings and transpiler
102 stars 7 forks source link

Draft: More STL container types #7

Open kassane opened 1 year ago

kassane commented 1 year ago

zig version: 0.11.0-dev.3905+309aacfc8

std::string

Test

Update: Wrong!! No C++ receiver in DoSomething().

$> zig build test
run test: error: SampleObject is doing something
// in tests.zig
test "cpp_string" {
    const ffi = @import("c020_string_shared.zig");

    const cstr: [*:0]const u8 = "Hello World";
    const str = cpp.String().init(.{}, cstr);
    try std.testing.expectEqual(str.str, cstr);
    // const shared = cpp.SharedPtr(ffi.SampleObject);
    // try expect(shared.use_count() == @atomicLoad(usize, null, .{.Relaxed}));
    var object = ffi.SampleObject.init();
    // failed: expected u8@0, found u8@208ecc
    // try std.testing.expectEqual(object.msg.get(), "");
    object.DoSomething();
    std.debug.print("{s}\n", .{object.msg.str});
    try std.testing.expectEqual(object.msg.get(), "SampleObject is doing something\n");
}

source:

// auto generated by c2z
const std = @import("std");
const cpp = @import("cpp");

pub const SampleObject = extern struct {
    msg: cpp.String(),

    extern fn _ZN12SampleObjectC1Ev(self: *SampleObject) void;
    pub inline fn init() SampleObject {
        var self: SampleObject = undefined;
        _ZN12SampleObjectC1Ev(&self);
        return self;
    }

    extern fn _ZN12SampleObjectD1Ev(self: *SampleObject) void;
    pub inline fn deinit(self: *SampleObject) void {
        self._ZN12SampleObjectD1Ev();
    }

    pub fn DoSomething(self: *SampleObject) void {
        const tmp: [*:0]const u8 = "SampleObject is doing something\n";
        self.msg.str = tmp;
    }
};

pub const SampleLibrary = extern struct {
    pub fn CreateObject(self: *SampleLibrary) cpp.SharedPtr(SampleObject) {
        _ = self;
        return;
    }
};
#pragma once

#include <iostream>
#include <memory>
#include <string>

class SampleObject {
    std::string msg;
public:
    SampleObject() {
        std::cout << "SampleObject created\n";
    }

    ~SampleObject() {
        std::cout << "SampleObject destroyed\n";
    }

    void DoSomething() {
        msg = "SampleObject is doing something\n";
    }
};

class SampleLibrary {
public:
    static std::shared_ptr<SampleObject> CreateObject() {
        return std::make_shared<SampleObject>();
    }
};

Impl - WiP

// UniquePtr implementation
pub fn UniquePtr(comptime T: type) type {
    return extern struct {
        const Self = @This();

        ptr: ?*T = null,

        pub fn init(ptr: *T) Self {
            return .{ .ptr = ptr };
        }

        pub fn get(self: *const Self) *T {
            return self.ptr.?;
        }

        pub fn release(self: *Self) *T {
            const ptr = self.ptr.?;
            self.ptr = null;
            return ptr;
        }

        pub fn reset(self: *Self, ptr: *T) void {
            if (self.ptr) {
                std.c.free(self.ptr);
            }
            self.ptr = ptr;
        }

        pub fn deinit(self: *Self) void {
            if (self.ptr) {
                std.c.free(self.ptr);
            }
        }
    };
}

// SharedPtr implementation
pub fn SharedPtr(comptime T: type) type {
    const AtomicUsize = std.atomic.Atomic(usize);
    return extern struct {
        const Self = @This();

        ptr: ?*T = null,
        counter: *AtomicUsize = null,

        pub fn init(ptr: *T) Self {
            const counter = std.heap.malloc(std.sizeOf(AtomicUsize));
            AtomicUsize.init(1);
            return .{ .ptr = ptr, .counter = counter };
        }

        pub fn get(self: *const Self) *T {
            return self.ptr.?;
        }

        pub fn use_count(self: *const Self) usize {
            return AtomicUsize.load(self.counter, .{.Relaxed});
        }

        pub fn acquire(self: *Self) void {
            AtomicUsize.fetchAdd(self.counter, 1, .{.Acquire});
        }

        pub fn release(self: *Self) void {
            if (AtomicUsize.fetchSub(self.counter, 1, .{.Release}) == 1) {
                AtomicUsize.store(self.counter, 0, .{.Release});
                AtomicUsize.release(self.counter);
                std.c.free(self.counter);
                std.c.free(self.ptr);
            }
        }

        pub fn deinit(self: *Self) void {
            AtomicUsize.release(self.counter);
            std.c.free(self.counter);
            std.c.free(self.ptr);
        }
    };
}

// String with allocator
pub fn StringAlloc(
    comptime Alloc: type,
) type {
    return extern struct {
        const Self = @This();

        str: [*:0]const u8,
        allocator: Alloc,

        pub fn init(allocator: Alloc, value: [*:0]const u8) Self {
            return .{ .str = value, .allocator = allocator };
        }

        pub fn get(self: *const Self) [*:0]const u8 {
            return self.str;
        }

        pub fn deinit(self: *Self) void {
            if (std.mem.len(self.str) != 0) {
                std.c.free(@as(?*anyopaque, @constCast(self.str)));
            }
        }
    };
}

// String with default allocator
pub fn String() type {
    return StringAlloc(Allocator(u8));
}
kassane commented 1 year ago

The types of atomic sorting need to be completely revised. I was only able to test the string.

kassane commented 1 year ago

By the way, during the tests I saw (std.debug.print) that in the for-loop of the cpp_vector test, empty values ​​with zero elements are compared.

lassade commented 1 year ago

I don't think the String impl is right, did it pass the tests ?!?! If you look at how cpp.Vector you see that C++ stores 3 pointers one for the start other for the end of the used data and other pointing to the end of the memory slice;

Try to remove the implementations of SampleObject from the header file, so the transpiled code acctually calls c++ compiled code, the transpiler it's far from been able to handle all c++ semantics, specially when dealing with std containers.

kassane commented 1 year ago

I don't think the String impl is right, did it pass the tests ?!?!

Yeah!

However, as I mentioned, the update would be from Zig to Zig. I can't receive C++ strings just like cpp_vector doesn't receive values.


As for smart pointers, I'll need to do implementation tests. Although it compiles without error.

kassane commented 1 year ago

the transpiler it's far from been able to handle all c++ semantics, specially when dealing with std containers.

I agree! I'll try use the .c_str() in std::string and .data() in std::vector and std::array when carrying out tests on the containers.

lassade commented 1 year ago

I did some work implement std::string, it's working but only for gnu abi, is in the newest branch.

kassane commented 1 year ago

I did some work implement std::string, it's working but only for gnu abi, is in the newest branch.

I've seen and tested it and also it's not available for msvc yet.

A question: Did you try viewing std::string from llvm-libcxx?

lassade commented 1 year ago

llvm-libcxx ? why the particular interest on msvc? is there any lib or project that requires it?

kassane commented 1 year ago

llvm-libcxx ? why the particular interest on msvc? is there any lib or project that requires it?

Two aspects here.

First, we already know that msvc is microsoft's standard abi. To date zig toolchain (clang-cl) does not support libc++ in msvc target, making nostdlib++.

Second, about libc++ and libstdc++ know that they are not that compatible as expected. Having zig toolchain as primary objective it's usual to think llvm-libcxx only except you want to host-build (killing cross-compilation). https://github.com/ziglang/zig/blob/master/lib/libcxx/src/string.cpp

e.g. (zig c++ w/ libstdc++): https://gist.github.com/kassane/7e9a2da137e13eb6e1dbab726693bdb7?permalink_comment_id=4475986#gistcomment-4475986

Reference: https://github.com/ziglang/zig/wiki/Troubleshooting-Build-Issues#dual-abi-linking


Most game devlopers look for msvc compatibility, being the case of the zig-gamedev project and unreal (TODO: testing...)!

lassade commented 1 year ago

I don't understand what you mean with the first part

kassane commented 1 year ago

I don't understand what you mean with the first part

What exactly?

MSVC target doesn't use zig libcxx and requires linking windows crt and sdk libraries directly.

--- edit

Missing Second,


- About libc++ and libstdc++ know
+ Second, about libc++ and libstdc++ know
lassade commented 1 year ago

MSVC string works completly different from the clang implementation :( it requires a second implementation

For some reasong I can't build with:

zig build test -Doptimize=ReleaseFast -freference-trace -Dtarget=native-windows-msvc
kassane commented 1 year ago

For some reasong I can't build with:

zig build test -Doptimize=ReleaseFast -freference-trace -Dtarget=native-windows-msvc

Could you show the error that occurred in the msvc build?

My case (Linux user), I use xwin to download libraries + includes sdk and crt (VS 2019/ 16 version). https://github.com/kassane/c2z/blob/msvc/build.zig#L100

The last CI commit returns the @compileError to msvc.

lassade commented 1 year ago

Ok I manage to build, but the tests exit with error code 5, have no clue how to fix it.

lassade commented 1 year ago

I finnaly figure out, it was a small mistake on my end, I had to make MSVC work in debug builds and it does sort of does ...

By the way xWin isn't needed at all

kassane commented 1 year ago

I finnaly figure out, it was a small mistake on my end, I had to make MSVC work in debug builds and it does sort of does

Great!

By the way xWin isn't needed at all

Yes! needed for non-windows or another arch-msvc only. Because zig toolchain statically configures msvc target (it does not switch to x86 or aarch64).

kassane commented 1 year ago

@lassade, have you tested astd::vector<std::vector<T>>?

lassade commented 1 year ago

no, but theres no reason why it can't work

kassane commented 1 year ago

no, but theres no reason why it can't work

Transpiling test

- bool enumerate(std::vector<uint8_t>& out_buf, size_t count);
+ bool enumerate(std::vector<std::vector<uint8_t>>& out_buf, size_t count);

info: using host `x86_64-linux-gnu` as target
info: zig cc -x c++ -lc++ -Xclang -ast-dump=json -fsyntax-only -target x86_64-linux-gnu 
info: binding `test_cases/include/c013_cpp_vector.h`
thread 51809 panic: integer overflow
/home/kassane/Documentos/c2z/src/Transpiler.zig:2436:34: 0x2845be in transpileType (c2z)
    const ch = ttname[ttname.len - 1];
                                 ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:2553:48: 0x287a4a in transpileArgs (c2z)
            const name = try self.transpileType(arg);
                                               ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:2548:35: 0x2878d4 in transpileArgs (c2z)
            try self.transpileArgs(args, buffer, index);
                                  ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:2501:31: 0x285cee in transpileType (c2z)
        try self.transpileArgs(ttname[less_than..], &args, &index);
                              ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:2464:43: 0x284d78 in transpileType (c2z)
        var inner = try self.transpileType(raw_name);
                                          ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:1060:52: 0x29252a in visitCXXMethodDecl (c2z)
                var z_type = try self.transpileType(c_type);
                                                   ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:1548:35: 0x259a9a in visit (c2z)
    return self.visitCXXMethodDecl(value, null);
                                  ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:425:27: 0x282158 in visitTranslationUnitDecl (c2z)
            try self.visit(item);
                          ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:312:42: 0x259773 in visit (c2z)
        try self.visitTranslationUnitDecl(value);
                                         ^
/home/kassane/Documentos/c2z/src/Transpiler.zig:191:19: 0x24816c in run (c2z)
    try self.visit(value);
                  ^
/home/kassane/Documentos/c2z/src/main.zig:130:27: 0x244d84 in main (c2z)
        try transpiler.run(&tree.value);
                          ^
/home/kassane/zig/0.11.0-dev.3937+78eb3c561/files/lib/std/start.zig:608:37: 0x23eea4 in posixCallMainAndExit (c2z)
            const result = root.main() catch |err| {
                                    ^
/home/kassane/zig/0.11.0-dev.3937+78eb3c561/files/lib/std/start.zig:367:5: 0x23e991 in _start (c2z)
    @call(.never_inline, posixCallMainAndExit, .{});
    ^
[1]    51809 IOT instruction (core dumped)  ./zig-out/bin/c2z -no-glue test_cases/include/c013_cpp_vector.h
``