examples/atm-gui.zig:161:64: error: union 'atm-gui.AtmSt.sessionMsg(.exit)' has no member named 'ChangePin'if(resource.changePin.toButton())return .ChangePin;
~^~~~~~~~~
我们移除 ChangePin 消息,因此也将它从消息产生的地方移除,继续重新编译。
新的反馈如下:
1
2
3
examples/atm-gui.zig:296:10: error: no field named 'ChangePin' in enum '@typeInfo(atm-gui.AtmSt.sessionMsg(.exit)).@"union".tag_type.?' .ChangePin => |wit| { ~^~~~~~~~~
conststd=@import("std");pubfnmain()!void{varval:i32=0;consts1Wit=Witness(Exmaple,.exit,.s1){};_=s1Handler(s1Wit,&val);}pubfnWitness(T:type,end:T,start:T)type{returnstruct{pubfngetMsg(self:@This())@TypeOf(start.STM(end).getMsg){_=self;if(end==start)@compileError("Can't getMsg!");returnstart.STM(end).getMsg;}pubfnterminal(_:@This())void{if(end!=start)@compileError("Can't terminal!");return{};}};}constExmaple=enum{exit,s1,s2,// State to Message union
pubfnSTM(start:Exmaple,end:Exmaple)type{returnswitch(start){.exit=>exitMsg(end),.s1=>s1Msg(end),.s2=>s2Msg(end),};}};pubfnexitMsg(_:Exmaple)void{return{};}pubfns1Msg(end:Exmaple)type{returnunion(enum){Exit:Witness(Exmaple,end,.exit),S1ToS2:Witness(Exmaple,end,.s2),pubfngetMsg(ref:*consti32)@This(){if(ref.*>20)return.Exit;return.S1ToS2;}};}pubfns2Msg(end:Exmaple)type{returnunion(enum){S2ToS1:Witness(Exmaple,end,.s1),pubfngetMsg()@This(){return.S2ToS1;}};}fns1Handler(val:Witness(Exmaple,.exit,.s1),ref:*i32)void{std.debug.print("val: {d}\n",.{ref.*});switch(val.getMsg()(ref)){.Exit=>|wit|wit.terminal(),.S1ToS2=>|wit|{ref.*+=1;s2Handler(wit,ref);},}}fns2Handler(val:Witness(Exmaple,.exit,.s2),ref:*i32)void{switch(val.getMsg()()){.S2ToS1=>|wit|{ref.*+=2;s1Handler(wit,ref);},}}
constExmaple=enum{exit,s1,s2,// State to Message union
pubfnSTM(start:Exmaple,end:Exmaple)type{returnswitch(start){.exit=>exitMsg(end),.s1=>s1Msg(end),.s2=>s2Msg(end),};}};
constFallbackAllocator=struct{primary:Allocator,fallback:Allocator,fba:*std.heap.FixedBufferAllocator,pubfnallocator(self:*FallbackAllocator)Allocator{return.{.ptr=self,.vtable=&.{.alloc=alloc,.resize=resize,.free=free},};}fnalloc(ctx:*anyopaque,len:usize,ptr_align:u8,ra:usize)?[*]u8{constself:*FallbackAllocator=@ptrCast(@alignCast(ctx));returnself.primary.rawAlloc(len,ptr_align,ra)orelseself.fallback.rawAlloc(len,ptr_align,ra);}fnresize(ctx:*anyopaque,buf:[]u8,buf_align:u8,new_len:usize,ra:usize)bool{constself:*FallbackAllocator=@ptrCast(@alignCast(ctx));if(self.fba.ownsPtr(buf.ptr)){if(self.primary.rawResize(buf,buf_align,new_len,ra)){returntrue;}}returnself.fallback.rawResize(buf,buf_align,new_len,ra);}fnfree(_:*anyopaque,_:[]u8,_:u8,_:usize)void{// we noop this since, in our specific case, we know
// the fallback is an arena, which won't free individual items
}};
conststd=@import("std");pubfnmain()!void{vargpa=std.heap.GeneralPurposeAllocator(.{}){};constallocator=gpa.allocator();// change the type, just to make it easier to write this snippet
// the same would happen with our above StringHashMap(User)
varlookup=std.AutoHashMap(usize,usize).init(allocator);deferlookup.deinit();trylookup.put(100,100);constfirst=lookup.getPtr(100).?;for(0..50)|i|{trylookup.put(i,i);}first.*=200;}
pubfnLinkedListUnmanaged(comptimeT:type)type{returnstruct{head:?*Node=null,constSelf=@This();pubfndeinit(self:Self,allocator:Allocator)void{varnode=self.head;while(node)|n|{node=n.next;allocator.destroy(n);}}pubfnappend(self:*Self,allocator:Allocator,value:T)!void{constnode=tryallocator.create(Node);// .. same as above
}// Node is the same as above
pubconstNode=struct{...}};}
conststd=@import("std");pubfnmain()!void{vargpa=std.heap.GeneralPurposeAllocator{}.init();constallocator=gpa.allocator();varh=std.AutoHashMap(User,i32).init(allocator);tryh.put(User{id=3,state=.active},9001);deferh.deinit();constUser=struct{id:i32,state:State,constState=enum{active,pending};};}constUser=struct{id:i32,state:State,login_ids:[]i32,// You intended to use an array here instead of a slice.
...};
修改后的代码中,我修正了 User 结构体内部的 login_ids 从切片([]T)改为了数组 ([N]T)。在 Zig 中,使用数组可以避免与切片相关的不确定性和模糊性问题。
error: std.auto_hash.autoHash does not allow slices here ([]const u8) because the intent is unclear. Consider using std.StringHashMap for hashing the contents of []const u8. Alternatively, consider using std.auto_hash.hash or providing your own hash function instead.
conststd=@import("std");pubfnmain()!void{vargpa=std.heap.GeneralPurposeAllocator(.{}){};constallocator=gpa.allocator();varh=std.HashMap(User,i32,User.HashContext,std.hash_map.default_max_load_percentage).init(allocator);deferh.deinit();tryh.put(.{.id=1,.name="Teg"},100);tryh.put(.{.id=2,.name="Duncan"},200);std.debug.print("{d}\n",.{h.get(.{.id=1,.name="Teg"}).?});std.debug.print("{d}\n",.{h.get(.{.id=2,.name="Duncan"}).?});}constUser=struct{id:u32,name:[]constu8,pubconstHashContext=struct{pubfnhash(_:HashContext,u:User)u64{// TODO
}pubfneql(_:HashContext,a:User,b:User)bool{// TODO
}};};
/// sub_path is relative to the package root.
pubfnincludePath(self:Filter,sub_path:[]constu8)bool{if(self.include_paths.count()==0)returntrue;if(self.include_paths.contains(""))returntrue;if(self.include_paths.contains("."))returntrue;if(self.include_paths.contains(sub_path))returntrue;// Check if any included paths are parent directories of sub_path.
vardirname=sub_path;while(std.fs.path.dirname(dirname))|next_dirname|{if(self.include_paths.contains(next_dirname))returntrue;dirname=next_dirname;}returnfalse;}
consthashed_file=tryarena.create(HashedFile);hashed_file.*=.{.fs_path=fs_path,.normalized_path=trynormalizePathAlloc(arena,entry_pkg_path),.kind=kind,.hash=undefined,// to be populated by the worker
.failure=undefined,// to be populated by the worker
};wait_group.start();trythread_pool.spawn(workerHashFile,.{root_dir,hashed_file,&wait_group,});tryall_files.append(hashed_file);
constlink_name=trydir.readLink(hashed_file.fs_path,&buf);if(fs.path.sep!=canonical_sep){// Package hashes are intended to be consistent across
// platforms which means we must normalize path separators
// inside symlinks.
normalizePath(link_name);}hasher.update(link_name);
varhasher=Manifest.Hash.init(.{});varany_failures=false;for(all_files.items)|hashed_file|{hashed_file.failurecatch|err|{any_failures=true;tryeb.addRootErrorMessage(.{.msg=tryeb.printString("unable to hash '{s}': {s}",.{hashed_file.fs_path,@errorName(err),}),});};hasher.update(&hashed_file.hash);}
不过话说回来,很多底层系统的开发需求往往和这种类型系统的构建相悖,比如如果你的类型就是一个int的封装,那么即使发生拷贝你也无所谓性能开销。但是如果是一个struct,那么通常情况下,你会比较 care 拷贝,而可能考虑“移动”之类的手段。这个时候各种 C++的提供的幻觉,就成了程序员开发的绊脚石,经常你需要分析一段 C++表达式里到底有没有发生拷贝,他是左值还是右值,其实你在写 C 语言的时候也很少去考虑了这些,你在 Zig 里同样也不需要。
类型系统
C 语言最大弊病就是没有提供标准库,C++的标准库你要是能看懂,得具备相当的 C++的语法知识,但是 Zig 的标准库几乎不需要文档就能看懂。这其实是因为,在 C++里,类型不是一等成员(first class member),因此实现一些模版元编程算法特别不直观。但是在 Zig 里,type就是一等成员,比如你可以写:
1
constx:type=u32;
即,把一个type当成一个变量使用。但是 C++里如何来实现这一行代码呢?其实是如下:
1
using x =uint32_t;
那么我们如果要对某个类型做个计算,比如组合一个新类型,Zig 里其实非常直观
1
fnSome(comptimeInputType:type)type
即输入一个类型,输出一个新类型,那么 C++里对应的东西是啥呢?
1
2
3
4
template<typename InputType>structSome {
using OutputType = ...
}
比如实现一个函数,输入一个 bool 值,根据 bool 值,如果为真,那么输出 type A,如果为假那么输出 type B。
1
2
3
4
5
6
7
8
9
10
11
//基本模式
template<bool, typename A, typename B>structFn {
using OutputType = A;
};
//特例化的模式
template<typename A, typename B>structFn<false, A, B> {
using OutputType = B;
};
x86-64-linux// uses gnu libc
x86-64-linux-gnu// uses glibc
x86-64-musl// uses musl libc
x86-64-windows// uses MingW headers
x86-64-windows-msvc// uses MSVC headers but they need to be present in your system
wasm32-freestanding// you will have to use build-obj since wasm modules are not full exes
//demo 3.2
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.root_source_file=.{.path="main.zig"},.target=target,.optimize=optimize,});exe.linkLibC();exe.linkSystemLibrary("curl");b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
// 示例片段
conststd=@import("std");pubfnbuild(b:*std.build.Builder)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constgame=b.addExecutable(.{.name="game",.root_source_file=.{.path="src/game.zig"},.target=target,.optimize=optimize,});b.installArtifact(game);constpack_tool=b.addExecutable(.{.name="pack",.root_source_file=.{.path="tools/pack.zig"},.target=target,.optimize=optimize,});//译者改动:const precompilation = pack_tool.run(); // returns *RunStep
constprecompilation=b.addRunArtifact(pack_tool);precompilation.addArtifactArg(game);precompilation.addArg("assets.zip");constpack_step=b.step("pack","Packs the game and assets together");pack_step.dependOn(&precompilation.step);}
// 示例片段
conststd=@import("std");pubfnbuild(b:*std.build.Builder)void{constmode=b.standardOptimizeOption(.{});// const mode = b.standardReleaseOptions();
consttarget=b.standardTargetOptions(.{});// Generates the lex-based parser
constparser_gen=b.addSystemCommand(&[_][]constu8{"flex","--outfile=review-parser.c","review-parser.l",});// Our application
constexe=b.addExecutable(.{.name="upload-review",.root_source_file=.{.path="src/main.zig"},.target=target,.optimize=mode,});{exe.step.dependOn(&parser_gen.step);exe.addCSourceFile(.{.file=std.build.LazyPath.relative("review-parser.c"),.flags=&.{}});// add zig-args to parse arguments
constap=b.createModule(.{.source_file=.{.path="vendor/zig-args/args.zig"},.dependencies=&.{},});exe.addModule("args-parser",ap);// add libcurl for uploading
exe.addIncludePath(std.build.LazyPath.relative("vendor/libcurl/include"));exe.addObjectFile(std.build.LazyPath.relative("vendor/libcurl/lib/libcurl.a"));exe.linkLibC();b.installArtifact(exe);// exe.install();
}// Our test suite
consttest_step=b.step("test","Runs the test suite");consttest_suite=b.addTest(.{.root_source_file=.{.path="src/tests.zig"},});test_suite.step.dependOn(&parser_gen.step);exe.addCSourceFile(.{.file=std.build.LazyPath.relative("review-parser.c"),.flags=&.{}});// add libcurl for uploading
exe.addIncludePath(std.build.LazyPath.relative("vendor/libcurl/include"));exe.addObjectFile(std.build.LazyPath.relative("vendor/libcurl/lib/libcurl.a"));test_suite.linkLibC();test_step.dependOn(&test_suite.step);{constdeploy_step=b.step("deploy","Creates an application bundle");// compile the app bundler
constdeploy_tool=b.addExecutable(.{.name="deploy",.root_source_file=.{.path="tools/deploy.zig"},.target=target,.optimize=mode,});{deploy_tool.linkLibC();deploy_tool.linkSystemLibrary("libzip");}constbundle_app=b.addRunArtifact(deploy_tool);bundle_app.addArg("app-bundle.zip");bundle_app.addArtifactArg(exe);bundle_app.addArg("resources/index.htm");bundle_app.addArg("resources/style.css");deploy_step.dependOn(&bundle_app.step);}}
// demo2.2
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="downloader",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("download.c"),.flags=&.{}});exe.linkSystemLibrary("curl");b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
//demo 2.3
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("bass-player.c"),.flags=&.{}});exe.linkLibC();// 还是一步步看源代码,找新的函数,addIncludeDir,addLibDir ->new function
exe.addIncludePath(std.build.LazyPath.relative("bass/linux"));exe.addLibraryPath(std.build.LazyPath.relative("bass/linux/x64"));exe.linkSystemLibrary("bass");b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
addIncludePath 和 addLibraryPath 都可以被多次调用,以向编译器添加多个路径。这些函数不仅会影响 C 代码,还会影响 Zig 代码,因此 @cImport 可以访问包含路径中的所有头文件。
每个文件的包含路径
因此,如果我们需要为每个 C 文件设置不同的包含路径,我们就需要用不同的方法来解决这个问题:
由于我们仍然可以通过 addCSourceFile 传递任何 C 编译器标志,因此我们也可以在这里手动设置包含目录。
//demo2.4
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("multi-main.c"),.flags=&.{}});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("multi.c"),.flags=&.{"-I","inc1"}});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("multi.c"),.flags=&.{"-I","inc2"}});b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
//demo2.5
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("main.c"),.flags=&.{}});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("buffer.cc"),.flags=&.{}});exe.linkLibCpp();b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
如你所见,我们还需要调用 linkLibCpp,它将链接 Zig 附带的 c++ 标准库。
这就是构建 C++ 文件所需的全部知识,没有什么更神奇的了。
指定语言版本
试想一下,如果你创建了一个庞大的项目,其中的 C 或 C++ 文件有新有旧,而且可能是用不同的语言标准编写的。为此,我们可以使用编译器标志来传递 -std=c90 或 -std=c++98:
//demo2.6
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("main.c"),.flags=&.{"-std=c90"}});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("buffer.cc"),.flags=&.{"-std=c++17"}});exe.linkLibCpp();b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
条件编译
与 Zig 相比,C 和 C++ 的条件编译方式非常繁琐。由于缺乏惰性求值的功能,有时必须根据目标环境来包含/排除文件。你还必须提供宏定义来启用/禁用某些项目功能。
//demo2.7
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constuse_platform_io=b.option(bool,"platform-io","Uses the native api instead of the C wrapper")orelsetrue;constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("print-main.c"),.flags=&.{}});if(use_platform_io){exe.defineCMacro("USE_PLATFORM_IO",null);if(exe.target.isWindows()){exe.addCSourceFile(.{.file=std.build.LazyPath.relative("print-windows.c"),.flags=&.{}});}else{exe.addCSourceFile(.{.file=std.build.LazyPath.relative("print-unix.c"),.flags=&.{}});}}exe.linkLibC();b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
//demo2.8
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});constflags=.{"-Wall","-Wextra","-Werror=return-type",};constcflags=flags++.{"-std=c99"};constcppflags=cflags++.{"-std=c++17","-stdlib=libc++","-fno-exceptions",};exe.addCSourceFile(.{.file=std.build.LazyPath.relative("main.c"),.flags=&cflags,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("buffer.cc"),.flags=&cppflags,});exe.linkLibC();exe.linkLibCpp();b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
//demo2.9
conststd=@import("std");pubfnbuild(b:*std.build.Builder)!void{varsources=std.ArrayList([]constu8).init(b.allocator);// Search for all C/C++ files in `src` and add them
{vardir=trystd.fs.cwd().openIterableDir(".",.{.access_sub_paths=true});varwalker=trydir.walk(b.allocator);deferwalker.deinit();constallowed_exts=[_][]constu8{".c",".cpp",".cxx",".c++",".cc"};while(trywalker.next())|entry|{constext=std.fs.path.extension(entry.basename);constinclude_file=for(allowed_exts)|e|{if(std.mem.eql(u8,ext,e))breaktrue;}elsefalse;if(include_file){// we have to clone the path as walker.next() or walker.deinit() will override/kill it
trysources.append(b.dupe(entry.path));}}}consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFiles(sources.items,&.{});exe.linkLibC();exe.linkLibCpp();b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
//demo2.10
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("main.m"),.flags=&.{},});exe.linkFramework("Foundation");b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
//demo2.11
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="example",.root_source_file=.{.path="main.zig"},.target=target,.optimize=optimize,});exe.addCSourceFile(.{.file=std.build.LazyPath.relative("buffer.c"),.flags=&.{},});exe.linkLibC();b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
这就是需要做的一切!是这样吗?
实际上,有一种情况现在还没有得到很好的支持:
您应用程序的入口点现在必须在 Zig 代码中,因为根文件必须导出一个 pub fn main(…) ……。
因此,如果你想将 C 项目中的代码移植到 Zig 中,你必须将 argc 和 argv 转发到你的 C 代码中,并将 C 代码中的 main 重命名为其他函数(例如 oldMain),然后在 Zig 中调用它。如果需要 argc 和 argv,可以通过 std.process.argsAlloc 获取。或者更好: 在 Zig 中重写你的入口点,然后从你的项目中移除一些 C 语言!
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="test",.root_source_file=.{.path="src/main.zig"},.target=target,.optimize=optimize,});b.installArtifact(exe);construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);constunit_tests=b.addTest(.{.root_source_file=.{.path="src/main.zig"},.target=target,.optimize=optimize,});construn_unit_tests=b.addRunArtifact(unit_tests);consttest_step=b.step("test","Run unit tests");test_step.dependOn(&run_unit_tests.step);}
$ zig build --help
使用方法: zig build [steps][options]
Steps:
install (default) Copy build artifacts to prefix path
uninstall Remove build artifacts from prefix path
step-name This is what is shown in helpGeneral Options:
...
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for
-Dcpu=[string] Target CPU features to add or subtract
-Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag)
Supported Values:
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
为了开发用户体验和一般便利性,从构建脚本中直接运行程序是非常实用的。这通常是通过运行步骤实现的,可以通过 zig build run 调用。
为此,我们需要一个 RunStep,它将执行我们能在系统上运行的任何可执行文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
conststd=@import("std");pubfnbuild(b:*std.build.Builder)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="fresh",.root_source_file=.{.path="src/main.zig"},.target=target,.optimize=optimize,});construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
RunStep 有几个函数可以为执行进程的 argv 添加值:
addArg 将向 argv 添加一个字符串参数。
addArgs 将同时添加多个字符串参数
addArtifactArg 将向 argv 添加 LibExeObjStep 的结果文件
addFileSourceArg 会将其他步骤生成的任何文件添加到 argv。
请注意,第一个参数必须是我们要运行的可执行文件的路径。在本例中,我们要运行 exe 的编译输出。
现在,当我们调用 zig build run 时,我们将看到与自己运行已安装的 exe 相同的输出:
conststd=@import("std");pubfnbuild(b:*std.build.Builder)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});constexe=b.addExecutable(.{.name="fresh",.root_source_file=.{.path="src/main.zig"},.target=target,.optimize=optimize,});construn_cmd=b.addRunArtifact(exe);run_cmd.step.dependOn(b.getInstallStep());if(b.args)|args|{run_cmd.addArgs(args);}construn_step=b.step("run","Run the app");run_step.dependOn(&run_cmd.step);}
conststd=@import("std");constmem=std.mem;constmidi=@This();pubconstdecode=@import("midi/decode.zig");pubconstencode=@import("midi/encode.zig");pubconstfile=@import("midi/file.zig");pubconstFile=file.File;test"midi"{_=@import("midi/test.zig");_=decode;_=file;}pubconstMessage=struct{status:u7,values:[2]u7,pubfnkind(message:Message)Kind{const_kind=@as(u3,@truncate(message.status>>4));const_channel=@as(u4,@truncate(message.status));returnswitch(_kind){0x0=>Kind.NoteOff,0x1=>Kind.NoteOn,0x2=>Kind.PolyphonicKeyPressure,0x3=>Kind.ControlChange,0x4=>Kind.ProgramChange,0x5=>Kind.ChannelPressure,0x6=>Kind.PitchBendChange,0x7=>switch(_channel){0x0=>Kind.ExclusiveStart,0x1=>Kind.MidiTimeCodeQuarterFrame,0x2=>Kind.SongPositionPointer,0x3=>Kind.SongSelect,0x6=>Kind.TuneRequest,0x7=>Kind.ExclusiveEnd,0x8=>Kind.TimingClock,0xA=>Kind.Start,0xB=>Kind.Continue,0xC=>Kind.Stop,0xE=>Kind.ActiveSensing,0xF=>Kind.Reset,0x4,0x5,0x9,0xD=>Kind.Undefined,},};}pubfnchannel(message:Message)?u4{const_kind=message.kind();const_channel=@as(u4,@truncate(message.status));switch(_kind){// Channel events
.NoteOff,.NoteOn,.PolyphonicKeyPressure,.ControlChange,.ProgramChange,.ChannelPressure,.PitchBendChange,=>return_channel,// System events
.ExclusiveStart,.MidiTimeCodeQuarterFrame,.SongPositionPointer,.SongSelect,.TuneRequest,.ExclusiveEnd,.TimingClock,.Start,.Continue,.Stop,.ActiveSensing,.Reset,=>returnnull,.Undefined=>returnnull,}}pubfnvalue(message:Message)u14{// TODO: Is this the right order according to the midi spec?
return@as(u14,message.values[0])<<7|message.values[1];}pubfnsetValue(message:*Message,v:u14)void{message.values=.{@as(u7,@truncate(v>>7)),@as(u7,@truncate(v)),};}pubconstKind=enum{// Channel events
NoteOff,NoteOn,PolyphonicKeyPressure,ControlChange,ProgramChange,ChannelPressure,PitchBendChange,// System events
ExclusiveStart,MidiTimeCodeQuarterFrame,SongPositionPointer,SongSelect,TuneRequest,ExclusiveEnd,TimingClock,Start,Continue,Stop,ActiveSensing,Reset,Undefined,};};
constmidi=@import("../midi.zig");conststd=@import("std");constdebug=std.debug;constio=std.io;constmath=std.math;constmem=std.mem;constdecode=@This();fnstatusByte(b:u8)?u7{if(@as(u1,@truncate(b>>7))!=0)return@as(u7,@truncate(b));returnnull;}fnreadDataByte(reader:anytype)!u7{returnmath.cast(u7,tryreader.readByte())catchreturnerror.InvalidDataByte;}pubfnmessage(reader:anytype,last_message:?midi.Message)!midi.Message{varfirst_byte:?u8=tryreader.readByte();conststatus_byte=if(statusByte(first_byte.?))|status_byte|blk:{first_byte=null;break:blkstatus_byte;}elseif(last_message)|m|blk:{if(m.channel()==null)returnerror.InvalidMessage;break:blkm.status;}elsereturnerror.InvalidMessage;constkind=@as(u3,@truncate(status_byte>>4));constchannel=@as(u4,@truncate(status_byte));switch(kind){0x0,0x1,0x2,0x3,0x6=>returnmidi.Message{.status=status_byte,.values=[2]u7{math.cast(u7,first_byteorelsetryreader.readByte())catchreturnerror.InvalidDataByte,tryreadDataByte(reader),},},0x4,0x5=>returnmidi.Message{.status=status_byte,.values=[2]u7{math.cast(u7,first_byteorelsetryreader.readByte())catchreturnerror.InvalidDataByte,0,},},0x7=>{debug.assert(first_byte==null);switch(channel){0x0,0x6,0x07,0x8,0xA,0xB,0xC,0xE,0xF=>returnmidi.Message{.status=status_byte,.values=[2]u7{0,0},},0x1,0x3=>returnmidi.Message{.status=status_byte,.values=[2]u7{tryreadDataByte(reader),0,},},0x2=>returnmidi.Message{.status=status_byte,.values=[2]u7{tryreadDataByte(reader),tryreadDataByte(reader),},},// Undefined
0x4,0x5,0x9,0xD=>returnmidi.Message{.status=status_byte,.values=[2]u7{0,0},},}},}}pubfnchunk(reader:anytype)!midi.file.Chunk{varbuf:[8]u8=undefined;tryreader.readNoEof(&buf);returndecode.chunkFromBytes(buf);}pubfnchunkFromBytes(bytes:[8]u8)midi.file.Chunk{returnmidi.file.Chunk{.kind=bytes[0..4].*,.len=mem.readIntBig(u32,bytes[4..8]),};}pubfnfileHeader(reader:anytype)!midi.file.Header{varbuf:[14]u8=undefined;tryreader.readNoEof(&buf);returndecode.fileHeaderFromBytes(buf);}pubfnfileHeaderFromBytes(bytes:[14]u8)!midi.file.Header{const_chunk=decode.chunkFromBytes(bytes[0..8].*);if(!mem.eql(u8,&_chunk.kind,midi.file.Chunk.file_header))returnerror.InvalidFileHeader;if(_chunk.len<midi.file.Header.size)returnerror.InvalidFileHeader;returnmidi.file.Header{.chunk=_chunk,.format=mem.readIntBig(u16,bytes[8..10]),.tracks=mem.readIntBig(u16,bytes[10..12]),.division=mem.readIntBig(u16,bytes[12..14]),};}pubfnint(reader:anytype)!u28{varres:u28=0;while(true){constb=tryreader.readByte();constis_last=@as(u1,@truncate(b>>7))==0;constvalue=@as(u7,@truncate(b));res=trymath.mul(u28,res,math.maxInt(u7)+1);res=trymath.add(u28,res,value);if(is_last)returnres;}}pubfnmetaEvent(reader:anytype)!midi.file.MetaEvent{returnmidi.file.MetaEvent{.kind_byte=tryreader.readByte(),.len=trydecode.int(reader),};}pubfntrackEvent(reader:anytype,last_event:?midi.file.TrackEvent)!midi.file.TrackEvent{varpeek_reader=io.peekStream(1,reader);varin_reader=peek_reader.reader();constdelta_time=trydecode.int(&in_reader);constfirst_byte=tryin_reader.readByte();if(first_byte==0xFF){returnmidi.file.TrackEvent{.delta_time=delta_time,.kind=midi.file.TrackEvent.Kind{.MetaEvent=trydecode.metaEvent(&in_reader)},};}constlast_midi_event=if(last_event)|e|switch(e.kind){.MidiEvent=>|m|m,.MetaEvent=>null,}elsenull;peek_reader.putBackByte(first_byte)catchunreachable;returnmidi.file.TrackEvent{.delta_time=delta_time,.kind=midi.file.TrackEvent.Kind{.MidiEvent=trydecode.message(&in_reader,last_midi_event)},};}/// Decodes a midi file from a reader. Caller owns the returned value
/// (see: `midi.File.deinit`).
pubfnfile(reader:anytype,allocator:*mem.Allocator)!midi.File{varchunks=std.ArrayList(midi.File.FileChunk).init(allocator);errdefer{(midi.File{.format=0,.division=0,.chunks=chunks.toOwnedSlice(),}).deinit(allocator);}constheader=trydecode.fileHeader(reader);constheader_data=tryallocator.alloc(u8,header.chunk.len-midi.file.Header.size);errdeferallocator.free(header_data);tryreader.readNoEof(header_data);while(true){constc=decode.chunk(reader)catch|err|switch(err){error.EndOfStream=>break,else=>|e|returne,};constchunk_bytes=tryallocator.alloc(u8,c.len);errdeferallocator.free(chunk_bytes);tryreader.readNoEof(chunk_bytes);trychunks.append(.{.kind=c.kind,.bytes=chunk_bytes,});}returnmidi.File{.format=header.format,.division=header.division,.header_data=header_data,.chunks=chunks.toOwnedSlice(),};}
constmidi=@import("../midi.zig");conststd=@import("std");constdebug=std.debug;constio=std.io;constmath=std.math;constmem=std.mem;constencode=@This();pubfnmessage(writer:anytype,last_message:?midi.Message,msg:midi.Message)!void{if(msg.channel()==nullorlast_message==nullormsg.status!=last_message.?.status){trywriter.writeByte((1<<7)|@as(u8,msg.status));}switch(msg.kind()){.ExclusiveStart,.TuneRequest,.ExclusiveEnd,.TimingClock,.Start,.Continue,.Stop,.ActiveSensing,.Reset,.Undefined,=>{},.ProgramChange,.ChannelPressure,.MidiTimeCodeQuarterFrame,.SongSelect,=>{trywriter.writeByte(msg.values[0]);},.NoteOff,.NoteOn,.PolyphonicKeyPressure,.ControlChange,.PitchBendChange,.SongPositionPointer,=>{trywriter.writeByte(msg.values[0]);trywriter.writeByte(msg.values[1]);},}}pubfnchunkToBytes(_chunk:midi.file.Chunk)[8]u8{varres:[8]u8=undefined;mem.copy(u8,res[0..4],&_chunk.kind);mem.writeIntBig(u32,res[4..8],_chunk.len);returnres;}pubfnfileHeaderToBytes(header:midi.file.Header)[14]u8{varres:[14]u8=undefined;mem.copy(u8,res[0..8],&chunkToBytes(header.chunk));mem.writeIntBig(u16,res[8..10],header.format);mem.writeIntBig(u16,res[10..12],header.tracks);mem.writeIntBig(u16,res[12..14],header.division);returnres;}pubfnint(writer:anytype,i:u28)!void{vartmp=i;varis_first=true;varbuf:[4]u8=undefined;varfbs=io.fixedBufferStream(&buf).writer();// TODO: Can we find a way to not encode this in reverse order and then flipping the bytes?
while(tmp!=0oris_first):(is_first=false){fbs.writeByte(@as(u7,@truncate(tmp))|(@as(u8,1<<7)*@intFromBool(!is_first)))catchunreachable;tmp>>=7;}mem.reverse(u8,fbs.context.getWritten());trywriter.writeAll(fbs.context.getWritten());}pubfnmetaEvent(writer:anytype,event:midi.file.MetaEvent)!void{trywriter.writeByte(event.kind_byte);tryint(writer,event.len);}pubfntrackEvent(writer:anytype,last_event:?midi.file.TrackEvent,event:midi.file.TrackEvent)!void{constlast_midi_event=if(last_event)|e|switch(e.kind){.MidiEvent=>|m|m,.MetaEvent=>null,}elsenull;tryint(writer,event.delta_time);switch(event.kind){.MetaEvent=>|meta|{trywriter.writeByte(0xFF);trymetaEvent(writer,meta);},.MidiEvent=>|msg|trymessage(writer,last_midi_event,msg),}}pubfnfile(writer:anytype,f:midi.File)!void{trywriter.writeAll(&encode.fileHeaderToBytes(.{.chunk=.{.kind=midi.file.Chunk.file_header.*,.len=@as(u32,@intCast(midi.file.Header.size+f.header_data.len)),},.format=f.format,.tracks=@as(u16,@intCast(f.chunks.len)),.division=f.division,}));trywriter.writeAll(f.header_data);for(f.chunks)|c|{trywriter.writeAll(&encode.chunkToBytes(.{.kind=c.kind,.len=@as(u32,@intCast(c.bytes.len)),}));trywriter.writeAll(c.bytes);}}
pubfnint(writer:anytype,i:u28)!void{vartmp=i;varis_first=true;varbuf:[4]u8=undefined;varfbs=io.fixedBufferStream(&buf).writer();// TODO: Can we find a way to not encode this in reverse order and then flipping the bytes?
while(tmp!=0oris_first):(is_first=false){fbs.writeByte(@as(u7,@truncate(tmp))|(@as(u8,1<<7)*@intFromBool(!is_first)))catchunreachable;tmp>>=7;}mem.reverse(u8,fbs.context.getWritten());trywriter.writeAll(fbs.context.getWritten());}
constbuiltin=@import("builtin");conststd=@import("std");constBuilder=std.build.Builder;constMode=builtin.Mode;pubfnbuild(b:*Builder)void{consttest_all_step=b.step("test","Run all tests in all modes.");inlinefor(@typeInfo(std.builtin.Mode).Enum.fields)|field|{consttest_mode=@field(std.builtin.Mode,field.name);constmode_str=@tagName(test_mode);consttests=b.addTest("midi.zig");tests.setBuildMode(test_mode);tests.setNamePrefix(mode_str++" ");consttest_step=b.step("test-"++mode_str,"Run all tests in "++mode_str++".");test_step.dependOn(&tests.step);test_all_step.dependOn(test_step);}constexample_step=b.step("examples","Build examples");inlinefor([_][]constu8{"midi_file_to_text_stream",})|example_name|{constexample=b.addExecutable(example_name,"example/"++example_name++".zig");example.addPackagePath("midi","midi.zig");example.install();example_step.dependOn(&example.step);}constall_step=b.step("all","Build everything and runs all tests");all_step.dependOn(test_all_step);all_step.dependOn(example_step);b.default_step.dependOn(all_step);}
这个 build 比较复杂,我们逐行来解析:
1
consttest_all_step=b.step("test","Run all tests in all modes.");
constall_step=b.step("all","Build everything and runs all tests");all_step.dependOn(test_all_step);all_step.dependOn(example_step);b.default_step.dependOn(all_step);
GC stands for garbage collection, which is primarily a memory management strategy for the heap region. Memory allocations in the heap are done in exponentially increasing sizes, with a special sub-heap dedicated solely to very large objects. One advantage of this approach might be its efficiency in handling memory requests of various sizes.
Represented as a formula:
$$
Heap = (M, S, (H_3, H_4, …, H_{12}))
$$
Where:
M: is a special region dedicated to storing large objects.
S: represents the free space.
H: is the sub-heap used for storing smaller objects. $H_i$ represents the sub-heap of size $2^i$, where each addition is twice the size of the previous.
graph TD
A[Heap]
B[M]
C[S]
D[H:Sub Heap]
A --> B
A --> C
A --> D
D --> H3
D --> H4
D --> H5
D --> Hi
We’ve designed a memory resource pool. This pool consists of numerous allocation segments of fixed size. It means that, regardless of how much space a sub-heap requests, it will request it in units of these fixed-size “segments”. For instance, if the allocation segments in the pool are of size 1MB, then a sub-heap might request space in sizes of 1MB, 2MB, 3MB, etc., rather than requesting non-integer multiples like 1.5MB or 2.5MB.
The dynamic allocation and reclaiming of space by the sub-heaps from a resource pool made up of fixed-size segments provide greater flexibility and may enhance the efficiency of memory utilization.
Types of GC
There are many common types of GC.
In terms of bitmap recorded data, there exists “Generational Garbage Collection”. In Generational Garbage Collection, “generations” or “ages” do not refer to the bitmap. They actually denote portions of the memory used to store objects. Based on the lifespan of objects, GC categorizes them into different generations. The fundamental idea behind this strategy is that newly created objects will become garbage sooner, whereas older objects might live longer.
Generally, in Generational GC, there are two primary generations:
Young Generation: Newly created objects are initially placed here. The Young Generation space is usually smaller and is garbage collected frequently.
Old or Tenured Generation: After objects have lived in the Young Generation for a sufficient amount of time and have survived several garbage collections, they are moved to the Old Generation. The Old Generation space is typically larger than the Young Generation, and garbage collection occurs less frequently since objects in the Old Generation are expected to have a longer lifespan.
Bitmaps serve as a tool here, used to track and manage which objects in each generation are active (i.e., still in use) and which are garbage. When the algorithm extends to Generational GC, multiple bitmaps can be maintained for different generations within the same heap space. In this way, active and inactive objects of each generation can be tracked individually.
“Maintain one bitmap for the Young Generation and another for the Old Generation.” This allows us to consider each generation separately during garbage collection, optimizing the efficiency and performance of the collection process.
In terms of moving existing data, there are “Moving GC” and “Non-Moving GC”. Moving GC relocates living objects to new memory addresses and compresses memory, while Non-Moving GC doesn’t move living objects, allowing other languages to seamlessly call data in memory.
Within this Moving GC category, there are “Generational Copying Collectors” and “Cheney’s Copying Collector”.
Generational Copying Collector: It is a common method of garbage collection, especially in functional programming languages. It assumes that newly created objects will soon become unreachable (i.e., “die”), whereas older objects are more likely to persist. Thus, memory is divided into two or more “generations”. New objects are created in the “Young Generation”, and when they live long enough, they are moved to the “Old Generation”.
Cheney’s Copying Collector: This is a garbage collector used for semi-space. It works by dividing the available memory in half and only allocating objects in one half. When this half is exhausted, the collector performs garbage collection by copying active objects to the other half. The original half is then completely emptied, becoming the new available space. Cheney’s collector is particularly suited for handling data with short lifespans because it quickly copies only the active data while ignoring the dead data. This makes it highly efficient for its “minor collections” (collections that only reclaim Young Generation data) when dealing with programs that handle a large amount of short-lifetime data, such as functional programs. The advantage of this method is that it can efficiently handle memory fragmentation since memory becomes continuously occupied by copying active objects to new locations.
Characteristics:
Any precise copy gc requires the runtime system to locate and update all pointers of every heap-allocated data.
In traditional garbage collection strategies (Moving GC), compaction is a commonly used technique, moving active objects into a contiguous region of memory, thereby freeing up unused memory. In other words, it consolidates memory fragmentation.
In Non-Moving GC, there’s “Mark-Sweep”.
The absence of compaction and object movement is very important, as the value of a pointer (i.e., the memory address of an object) remains fixed and there’s no time spent updating moved addresses. This makes Non-Moving GC highly suitable for languages that need to interact with other languages, as they can access objects in memory without the need for extra work. Moreover, the feature of not moving objects is beneficial for supporting multiple native threads. In a multi-threaded environment, if the location of an object in memory keeps shifting, coordination and synchronization between threads become more complicated. Therefore, avoiding object movement simplifies multithreaded programming.
The benefits are as follows:
Lock Simplification: In a multi-threaded environment, if an object needs to be moved (e.g., during the compaction phase of garbage collection), we need to ensure other threads cannot access this object while it’s moving. This might require complex locking strategies and synchronization mechanisms. However, if objects never move, this synchronization need is reduced, making locking strategies simpler.
Pointer Stability: In multi-threaded programs, threads might share pointers or references to objects. If an object moves in memory, all threads sharing that object would need to update their pointers or references. This not only adds synchronization complexity but might also introduce errors, like dangling pointers. If objects don’t move, these pointers remain consistently valid.
Predictability and Performance: Not having to move objects means memory access patterns are more stable and predictable. In multi-threaded programs, predictability is a valuable trait as it can reduce contention between threads, improving overall program performance.
Reduced Pause Times: Object movement in garbage collection can lead to noticeable pauses in an application because all threads must be paused to move objects safely. In a multi-threaded environment, this pause might be more pronounced as more threads might actively use objects. Not moving objects reduces such pauses.
Interoperability with Other Languages or Systems: If your multi-threaded application interoperates with other languages (like C or C++) or systems, having objects in stable locations becomes even more crucial since external code might rely on the fact that objects aren’t moving.
However, Non-Moving GC has its disadvantages:
Memory Fragmentation: Since objects don’t move, spaces in memory might become non-contiguous. This could lead to memory fragmentation, decreasing memory usage efficiency.
Memory Allocation: Due to fragmentation, memory allocation can become more complicated. For instance, if there isn’t enough contiguous space to meet an allocation request, the allocator might need to do more work to find available space. This might decrease allocation performance.
Memory Usage: Due to fragmentation, memory usage can become less efficient. For instance, if there’s a large object without enough contiguous space to store it, it might get split into multiple fragments which could be assigned to different contiguous spaces, potentially decreasing memory utilization.
Memory Overhead: Due to fragmentation, memory overhead can become less efficient. For instance, if there’s a large object without enough contiguous space to store it, it might get split into multiple fragments which could be assigned to different contiguous spaces, potentially decreasing memory utilization.
……
To address these problems requires many complicated steps, which won’t be elaborated on here. We’ll focus on Bog’s GC for the explanation.
Meta Bitmap
“Meta Bitmap” or “meta-level bitmaps”. This is a higher-level bitmap that summarizes the contents of the original bitmap. This hierarchical structure is similar to the inode mapping in file systems or the use of multi-level page tables in computer memory management.
For instance, consider a simple bitmap: 1100 1100. A meta-level bitmap might represent how many free blocks are in every 4 bits. In this scenario, the meta-level bitmap could be 1021 (indicating there’s 1 free block in the first 4 bits, 2 free blocks in the second 4 bits, and so on).
The system doesn’t just blindly start searching from the beginning of the bitmap for a free bit; it remembers the last-found position. This way, the next search can begin from this position, speeding up the search process further. This means that the time needed to find the next free bit remains approximately the same, regardless of memory size, which is a very efficient performance characteristic.
What about the worst-case scenario?
Let’s design for a 32-bit architecture. A 32-bit architecture means that the computer’s instruction set and data path are designed to handle data units 32 bits wide. Therefore, when operating on 32-bit data units (like an integer or part of a bitmap), such an architecture can typically process all 32 bits at once. This results in logarithmic operations based on 32, because for larger data sections (like a bitmap), operations might need to proceed in blocks/chunks of 32 bits. The search time is logarithmically related to the size of segmentSize.
For example, if a bitmap is 320 bits long, then on a 32-bit architecture, the worst-case scenario might require checking 10 blocks of 32 bits to find a free bit. This can be represented by log32(320), which results in 10.
Bitmap
Since Bog’s GC is essentially still based on “Mark-Sweep”, using bitmaps to record data is indispensable. In Bog, we adopted the method of “bitmap records data” for GC. And to improve efficiency, we introduced the concept of meta-bitmaps, where every 4 elements correspond to a meta-bitmap, recording the occupancy status of multiple spaces, and increasing the depth based on the object age in the heap.
Implementation
In reality, Bog’s design is a bit more complex. Here are sample in practical code:
max_size: Represents the maximum number of bytes a Page can store. We have defined a constant to represent the size of 1 MiB and ensure at compile time that the size of the Page type is exactly 1 MiB. Otherwise, a compile-time error will be triggered.
val_count: Represents the number of Value objects a Page can store.
pad_size: Represents the size of the unused space remaining in the Page after storing the maximum number of Value objects.
An enumeration type named State is defined, which has four possible values: empty, white, gray, and black.
In the context of Garbage Collection (GC), these states are typically related to the status of objects during the GC process. For instance, in generational garbage collection, an object might be marked as “white” (unvisited/pending), “gray” (visited but its references not yet processed), or “black” (processed).
List: Stores pointers of the Page type.
meta: Represents the state of each Value object within a Page. Here, we use an enum type to represent the state, and since there are only 4 states, they can be represented using 2 bits. Thus, we can use a u32 to represent the state of all Value objects within a Page. Each State potentially corresponds to the status of a Value object in the values field.
A __padding field, used to pad extra memory space. Its size is determined by the previously mentioned pad_size and is an array of bytes (u8). This is commonly used to ensure memory alignment of data structures.
free: Represents the number, index, or other information related to free or available spaces concerning memory management.
marked: Represents the number, index, or other information about marked spaces, used during the garbage collection process to determine whether to continue checking values on this page.
values: Represents the Value objects in a Page. It’s an array of Value objects, the size of which is determined by val_count.