Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1cc1fc6
Add time namespace with clock and timestamp functions
zendrx Jul 20, 2026
d1a60a3
Implement time functions in buzz_time.zig
zendrx Jul 20, 2026
ea8797b
Add time module tests for various functionalities
zendrx Jul 20, 2026
27fc6b9
Add support for buzz_time.zig in static libraries
zendrx Jul 20, 2026
a898223
Update time.buzz
zendrx Jul 20, 2026
47ca54d
Ensure formatted string is not empty in tests
zendrx Jul 21, 2026
6a26b36
Refactor time.buzz test cases for improved clarity
zendrx Jul 21, 2026
c604303
Fix typo in export declaration for now function
zendrx Jul 21, 2026
634c46a
Refactor time functions to use updated API calls
zendrx Jul 21, 2026
d7dcdec
Simplify push of milliseconds to Buzz API
zendrx Jul 21, 2026
483b4e2
Fix function declaration for timeNow
zendrx Jul 21, 2026
9852dc1
Change var to final in time.buzz tests
zendrx Jul 21, 2026
36e9f05
Merge branch 'buzz-language:main' into time-feature
zendrx Jul 24, 2026
e8bddcb
Refactor assertions for better readability
zendrx Jul 24, 2026
b2604fe
Change CI workflow branch to 'time-feature'
zendrx Jul 24, 2026
dcdf11b
Fix indentation for workflow_dispatch in ci.yaml
zendrx Jul 24, 2026
64a0434
Fix syntax error in buzz_time.zig
zendrx Jul 24, 2026
02c3690
Add 'time' header to static_headers.zig
zendrx Jul 24, 2026
9e26bf1
Fix header reference for time in static libraries
zendrx Jul 24, 2026
9dadc95
Fix formatting of nanos calculation in buzz_time.zig
zendrx Jul 24, 2026
39ed452
Fix formatting of timestamp in buzz_time.zig
zendrx Jul 24, 2026
e1081e9
fix: Root default values on the stack to prevent bad GC sweep
giann Jul 24, 2026
9496c1b
Fix argument order in Clock.now calls
zendrx Jul 24, 2026
e828709
Remove unnecessary complex format/parse
zendrx Jul 24, 2026
5421f86
Refactor time.buzz to adjust function exports
zendrx Jul 24, 2026
b1207a2
Merge branch 'buzz-language:main' into time-feature
zendrx Jul 24, 2026
4648015
Refactor time tests to add monotonic and CPU checks
zendrx Jul 24, 2026
7eaef1c
Refactor time tests to add monotonic and CPU checks
zendrx Jul 25, 2026
658580d
Merge branch 'time-feature' of https://github.com/zendrx/buzz into ti…
zendrx Jul 27, 2026
1562f66
Update static_headers.zig
zendrx Jul 27, 2026
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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
on:
push:
branches: [main]
branches: [time-feature]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
tests:
Expand Down
6 changes: 0 additions & 6 deletions src/GC.zig
Original file line number Diff line number Diff line change
Expand Up @@ -463,12 +463,6 @@ fn freeObj(self: *GC, obj: *o.Obj) (std.mem.Allocator.Error || std.fmt.BufPrintE
},
.UpValue => {
const obj_upvalue = o.ObjUpValue.cast(obj).?;
if (obj_upvalue.closed) |value| {
if (value.isObj()) {
try freeObj(self, value.obj());
}
}

free(self, o.ObjUpValue, obj_upvalue);
},
.Closure => {
Expand Down
10 changes: 8 additions & 2 deletions src/buzz_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,9 @@ export fn bz_newQualifiedObjectInstance(self: *VM, qualified_name: [*]const u8,
@panic("Could not create error");
};

// Keep the new instance rooted while cloning defaults may allocate.
self.push(instance.toValue());

// Set instance fields with default values
for (object.defaults, 0..) |default, idx| {
if (default) |udefault| {
Expand All @@ -786,7 +789,7 @@ export fn bz_newQualifiedObjectInstance(self: *VM, qualified_name: [*]const u8,
}
}

return instance.toValue();
return self.pop();
}

fn instanciateError(
Expand Down Expand Up @@ -890,6 +893,9 @@ export fn bz_newObjectInstance(vm: *VM, object_value: v.Value, typedef_value: v.
) catch @panic("Out of memory"),
) catch @panic("Could not instanciate object");

// Keep the new instance rooted while cloning defaults may allocate.
vm.push(instance.toValue());

// If not anonymous, set default fields
if (object) |uobject| {
for (uobject.defaults, 0..) |default, idx| {
Expand All @@ -903,7 +909,7 @@ export fn bz_newObjectInstance(vm: *VM, object_value: v.Value, typedef_value: v.
}
}

return instance.toValue();
return vm.pop();
}

export fn bz_getObjectField(object_value: v.Value, field_idx: usize) callconv(.c) v.Value {
Expand Down
32 changes: 32 additions & 0 deletions src/lib/buzz_time.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const api = @import("buzz_api.zig");
const std = @import("std");

pub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int {
const ts = std.Io.Timestamp.now(ctx.getIo(), .real);
const ms = ts.toMilliseconds();
ctx.vm.bz_push(.fromDouble(@floatFromInt(ms)));
return 1;
}

pub export fn timeMonotonic(ctx: *api.NativeCtx) callconv(.c) c_int {
const ts = std.Io.Timestamp.now(ctx.getIo(), .awake);
const ms = ts.toMilliseconds();
ctx.vm.bz_push(.fromDouble(@floatFromInt(ms)));
return 1;
}

pub export fn timeCpu(ctx: *api.NativeCtx) callconv(.c) c_int {
const ts = std.Io.Timestamp.now(ctx.getIo(), .cpu_process);
const ms = ts.toMilliseconds();
ctx.vm.bz_push(.fromDouble(@floatFromInt(ms)));
return 1;
}

pub const library = api.BuzzApi(
"time",
&.{
&.{ "now", timeNow },
&.{ "monotonic", timeMonotonic },
&.{ "cpu", timeCpu },
},
){};
2 changes: 2 additions & 0 deletions src/lib/static_headers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const math = Header{ .name = "math", .path = "math.buzz" };
pub const os = Header{ .name = "os", .path = "os.buzz" };
pub const serialize = Header{ .name = "serialize", .path = "serialize.buzz" };
pub const std = Header{ .name = "std", .path = "std.buzz" };
pub const time = Header{ .name = "time", .path = "time.buzz" };
pub const testing = Header{ .name = "test", .path = "testing.buzz" };

/// Buzz headers bundled with the compiler/runtime and installed for tooling.
Expand All @@ -41,4 +42,5 @@ pub const all = [_]Header{
serialize,
std,
testing,
time,
};
2 changes: 2 additions & 0 deletions src/lib/static_libraries.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub const all = [_]Library{
.{ .header = static_headers.os, .zig_path = "buzz_os.zig", .wasm_native = false },
.{ .header = static_headers.serialize, .zig_path = "buzz_serialize.zig", .wasm_native = true },
.{ .header = static_headers.std, .zig_path = "buzz_std.zig", .wasm_native = true },
.{ .header = static_headers.time, .zig_path = "buzz_time.zig", .wasm_native = false },
.{ .header = static_headers.testing, .zig_path = null, .wasm_native = false },
};

Expand Down Expand Up @@ -98,6 +99,7 @@ fn nativeMethods(comptime library: Library) std.StaticStringMap(buzz_api.NativeF
if (std.mem.eql(u8, zig_path, "buzz_os.zig")) return checkedNativeMethods(library, @import("buzz_os.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_serialize.zig")) return checkedNativeMethods(library, @import("buzz_serialize.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_std.zig")) return checkedNativeMethods(library, @import("buzz_std.zig").library);
if (std.mem.eql(u8, zig_path, "buzz_time.zig")) return checkedNativeMethods(library, @import("buzz_time.zig").library);

@compileError("unknown native library path: " ++ zig_path);
}
Expand Down
12 changes: 12 additions & 0 deletions src/lib/time.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace time;

import "buzz:errors";

/// Real-time (wall-clock) in milliseconds since Unix epoch
export extern fun now() > double;

/// Monotonic clock in milliseconds (never goes backwards for intervals)
export extern fun monotonic() > double;

/// Cpu time used by the current process in milliseconds
export extern fun cpu() > double !> errors\UnexpectedError;
62 changes: 44 additions & 18 deletions src/obj.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1571,16 +1571,20 @@ pub const ObjObjectInstance = struct {
type_def: *ObjTypeDef,
gc: *GC,
) !Self {
const fields = try gc.allocateMany(
Value,
type_def.resolved_type.?.ObjectInstance.of
.resolved_type.?.Object
.propertiesCount(),
);

@memset(fields, Value.Null);

return .{
.vm = vm,
.object = object,
.type_def = type_def,
.fields = try gc.allocateMany(
Value,
type_def.resolved_type.?.ObjectInstance.of
.resolved_type.?.Object
.propertiesCount(),
),
.fields = fields,
};
}

Expand Down Expand Up @@ -1759,15 +1763,23 @@ pub const ObjObject = struct {
property_count: ?usize = 0,

pub fn init(allocator: Allocator, type_def: *ObjTypeDef) !Self {
const fields = try allocator.alloc(
Value,
type_def.resolved_type.?.Object.fields.count(),
);
errdefer allocator.free(fields);

const defaults = try allocator.alloc(
?Value,
type_def.resolved_type.?.Object.propertiesCount(),
);

@memset(fields, Value.Void);
@memset(defaults, null);

const self = Self{
.fields = try allocator.alloc(
Value,
type_def.resolved_type.?.Object.fields.count(),
),
.defaults = try allocator.alloc(
?Value,
type_def.resolved_type.?.Object.propertiesCount(),
),
.fields = fields,
.defaults = defaults,
.type_def = type_def,
};

Expand Down Expand Up @@ -5925,24 +5937,38 @@ pub fn cloneObject(obj: *Obj, vm: *VM) !Value {

.List => {
const list = ObjList.cast(obj).?;
var items = try list.items.clone(vm.gc.allocator);
errdefer items.deinit(vm.gc.allocator);

const methods = try vm.gc.allocator.alloc(?*ObjNative, list.methods.len);
errdefer vm.gc.allocator.free(methods);

@memcpy(methods, list.methods);

return (try vm.gc.allocateObject(
ObjList{
.type_def = list.type_def,
.items = try list.items.clone(vm.gc.allocator),
.methods = list.methods,
.items = items,
.methods = methods,
},
)).toValue();
},

.Map => {
const map = ObjMap.cast(obj).?;
var cloned_map = try map.map.clone(vm.gc.allocator);
errdefer cloned_map.deinit(vm.gc.allocator);

const methods = try vm.gc.allocator.alloc(?*ObjNative, map.methods.len);
errdefer vm.gc.allocator.free(methods);

@memcpy(methods, map.methods);

return (try vm.gc.allocateObject(
ObjMap{
.type_def = map.type_def,
.map = try map.map.clone(vm.gc.allocator),
.methods = map.methods,
.map = cloned_map,
.methods = methods,
},
)).toValue();
},
Expand Down
5 changes: 3 additions & 2 deletions src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3084,6 +3084,9 @@ pub const VM = struct {
unreachable;
};

// Keep the new instance rooted while cloning defaults may allocate.
self.push(obj_instance.toValue());

// If not anonymous, set default fields
if (object) |uobject| {
// Set instance fields with default values
Expand All @@ -3104,8 +3107,6 @@ pub const VM = struct {
}
}

self.push(obj_instance.toValue());

const frame = self.currentFrame().?;
const next_full_instruction = self.readInstruction(frame);
@call(
Expand Down
44 changes: 44 additions & 0 deletions tests/behavior/c-buzz-api.buzz
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import "buzz:std";
import "../utils/buzz_c_api";

object ApiDefaultClone {
tokens: [int] = [
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 125, 126, 127, 128,
129, 130, 131, 132, 133, 134, 135, 136,
137, 138, 139, 140, 141, 142, 143, 144,
145, 146, 147, 148, 149, 150, 151, 152,
153, 154, 155, 156, 157, 158, 159, 160,
161, 162, 163, 164, 165, 166, 167, 168,
169, 170, 171, 172, 173, 174, 175, 176,
177, 178, 179, 180, 181, 182, 183, 184,
185, 186, 187, 188, 189, 190, 191, 192,
193, 194, 195, 196, 197, 198, 199, 200,
201, 202, 203, 204, 205, 206, 207, 208,
209, 210, 211, 212, 213, 214, 215, 216,
217, 218, 219, 220, 221, 222, 223, 224,
225, 226, 227, 228, 229, 230, 231, 232,
233, 234, 235, 236, 237, 238, 239, 240,
241, 242, 243, 244, 245, 246, 247, 248,
249, 250, 251, 252, 253, 254, 255, 256,
],
}

test "C buzz api library" {
std\assert(
buzz_c_api\add(40, rhs: 2) == 42,
Expand All @@ -22,4 +59,11 @@ test "C buzz api library" {
buzz_c_api\argCount(1, b: 2, c: 3) == 3,
message: "C library can read NativeCtx arg_count"
);
std\assert(
buzz_c_api\instantiateDefaultedObject(
ApiDefaultClone,
typedef_value: typeof ApiDefaultClone{}
) == 5120000,
message: "C library can instantiate objects with cloneable defaults under GC pressure"
);
}
49 changes: 49 additions & 0 deletions tests/behavior/objects.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,52 @@ test "Object with static fields" {

std\assert(second.id == Second.nextId, message: "could use static fields");
}

object CloneDefault {
tokens: [int] = [
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 125, 126, 127, 128,
129, 130, 131, 132, 133, 134, 135, 136,
137, 138, 139, 140, 141, 142, 143, 144,
145, 146, 147, 148, 149, 150, 151, 152,
153, 154, 155, 156, 157, 158, 159, 160,
161, 162, 163, 164, 165, 166, 167, 168,
169, 170, 171, 172, 173, 174, 175, 176,
177, 178, 179, 180, 181, 182, 183, 184,
185, 186, 187, 188, 189, 190, 191, 192,
193, 194, 195, 196, 197, 198, 199, 200,
201, 202, 203, 204, 205, 206, 207, 208,
209, 210, 211, 212, 213, 214, 215, 216,
217, 218, 219, 220, 221, 222, 223, 224,
225, 226, 227, 228, 229, 230, 231, 232,
233, 234, 235, 236, 237, 238, 239, 240,
241, 242, 243, 244, 245, 246, 247, 248,
249, 250, 251, 252, 253, 254, 255, 256,
],
}

test "Objects keep cloneable defaults alive during instantiation" {
var i = 0;
var total = 0;

while (i < 20000) {
total = total + CloneDefault{}.tokens[0];
i = i + 1;
}

std\assert(total == 20000, message: "object instances survive GC while cloning defaults");
}
31 changes: 31 additions & 0 deletions tests/behavior/time.buzz
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import "buzz:std";
import "buzz:time";

test "now returns a positive number" {
final now = time\now();
std\assert(
now > 0,
message: "now should be greater than 0"
);
}

test "monotonic increases" {
final start = time\monotonic();
os\sleep(10.0);
final end = time\monotonic();
std\assert(
end > start,
message: "monotonic should increase after sleep"
);
}

test "cpu returns a positive number" {
final cpu = time\cpu() catch {
// CPU clock might not be available on all platforms
return;
};
std\assert(
cpu >= 0,
message: "cpu time should be >= 0"
);
}
Loading