mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
stage2 cbe: non pointer optionals
This commit is contained in:
parent
cfc19eace7
commit
0a7be71bc2
6 changed files with 163 additions and 46 deletions
|
|
@ -50,20 +50,20 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
|
|||
}
|
||||
|
||||
pub fn AutoHashMap(comptime K: type, comptime V: type) type {
|
||||
return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
|
||||
return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
|
||||
}
|
||||
|
||||
pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {
|
||||
return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
|
||||
return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
|
||||
}
|
||||
|
||||
/// Builtin hashmap for strings as keys.
|
||||
pub fn StringHashMap(comptime V: type) type {
|
||||
return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
|
||||
return HashMap([]const u8, V, hashString, eqlString, default_max_load_percentage);
|
||||
}
|
||||
|
||||
pub fn StringHashMapUnmanaged(comptime V: type) type {
|
||||
return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
|
||||
return HashMapUnmanaged([]const u8, V, hashString, eqlString, default_max_load_percentage);
|
||||
}
|
||||
|
||||
pub fn eqlString(a: []const u8, b: []const u8) bool {
|
||||
|
|
@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 {
|
|||
return std.hash.Wyhash.hash(0, s);
|
||||
}
|
||||
|
||||
pub const DefaultMaxLoadPercentage = 80;
|
||||
/// Deprecated use `default_max_load_percentage`
|
||||
pub const DefaultMaxLoadPercentage = default_max_load_percentage;
|
||||
|
||||
pub const default_max_load_percentage = 80;
|
||||
|
||||
/// General purpose hash table.
|
||||
/// No order is guaranteed and any modification invalidates live iterators.
|
||||
|
|
@ -89,13 +92,13 @@ pub fn HashMap(
|
|||
comptime V: type,
|
||||
comptime hashFn: fn (key: K) u64,
|
||||
comptime eqlFn: fn (a: K, b: K) bool,
|
||||
comptime MaxLoadPercentage: u64,
|
||||
comptime max_load_percentage: u64,
|
||||
) type {
|
||||
return struct {
|
||||
unmanaged: Unmanaged,
|
||||
allocator: *Allocator,
|
||||
|
||||
pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage);
|
||||
pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, max_load_percentage);
|
||||
pub const Entry = Unmanaged.Entry;
|
||||
pub const Hash = Unmanaged.Hash;
|
||||
pub const Iterator = Unmanaged.Iterator;
|
||||
|
|
@ -251,9 +254,9 @@ pub fn HashMapUnmanaged(
|
|||
comptime V: type,
|
||||
hashFn: fn (key: K) u64,
|
||||
eqlFn: fn (a: K, b: K) bool,
|
||||
comptime MaxLoadPercentage: u64,
|
||||
comptime max_load_percentage: u64,
|
||||
) type {
|
||||
comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100);
|
||||
comptime assert(max_load_percentage > 0 and max_load_percentage < 100);
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
|
@ -274,12 +277,12 @@ pub fn HashMapUnmanaged(
|
|||
// Having a countdown to grow reduces the number of instructions to
|
||||
// execute when determining if the hashmap has enough capacity already.
|
||||
/// Number of available slots before a grow is needed to satisfy the
|
||||
/// `MaxLoadPercentage`.
|
||||
/// `max_load_percentage`.
|
||||
available: Size = 0,
|
||||
|
||||
// This is purely empirical and not a /very smart magic constant™/.
|
||||
/// Capacity of the first grow when bootstrapping the hashmap.
|
||||
const MinimalCapacity = 8;
|
||||
const minimal_capacity = 8;
|
||||
|
||||
// This hashmap is specially designed for sizes that fit in a u32.
|
||||
const Size = u32;
|
||||
|
|
@ -382,7 +385,7 @@ pub fn HashMapUnmanaged(
|
|||
found_existing: bool,
|
||||
};
|
||||
|
||||
pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage);
|
||||
pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage);
|
||||
|
||||
pub fn promote(self: Self, allocator: *Allocator) Managed {
|
||||
return .{
|
||||
|
|
@ -392,7 +395,7 @@ pub fn HashMapUnmanaged(
|
|||
}
|
||||
|
||||
fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
|
||||
return size * 100 < MaxLoadPercentage * cap;
|
||||
return size * 100 < max_load_percentage * cap;
|
||||
}
|
||||
|
||||
pub fn init(allocator: *Allocator) Self {
|
||||
|
|
@ -425,7 +428,7 @@ pub fn HashMapUnmanaged(
|
|||
}
|
||||
|
||||
fn capacityForSize(size: Size) Size {
|
||||
var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1);
|
||||
var new_cap = @truncate(u32, (@as(u64, size) * 100) / max_load_percentage + 1);
|
||||
new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
|
||||
return new_cap;
|
||||
}
|
||||
|
|
@ -439,7 +442,7 @@ pub fn HashMapUnmanaged(
|
|||
if (self.metadata) |_| {
|
||||
self.initMetadatas();
|
||||
self.size = 0;
|
||||
self.available = @truncate(u32, (self.capacity() * MaxLoadPercentage) / 100);
|
||||
self.available = @truncate(u32, (self.capacity() * max_load_percentage) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -712,9 +715,9 @@ pub fn HashMapUnmanaged(
|
|||
}
|
||||
|
||||
// This counts the number of occupied slots, used + tombstones, which is
|
||||
// what has to stay under the MaxLoadPercentage of capacity.
|
||||
// what has to stay under the max_load_percentage of capacity.
|
||||
fn load(self: *const Self) Size {
|
||||
const max_load = (self.capacity() * MaxLoadPercentage) / 100;
|
||||
const max_load = (self.capacity() * max_load_percentage) / 100;
|
||||
assert(max_load >= self.available);
|
||||
return @truncate(Size, max_load - self.available);
|
||||
}
|
||||
|
|
@ -733,7 +736,7 @@ pub fn HashMapUnmanaged(
|
|||
const new_cap = capacityForSize(self.size);
|
||||
try other.allocate(allocator, new_cap);
|
||||
other.initMetadatas();
|
||||
other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
|
||||
other.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
|
||||
|
||||
var i: Size = 0;
|
||||
var metadata = self.metadata.?;
|
||||
|
|
@ -751,7 +754,7 @@ pub fn HashMapUnmanaged(
|
|||
}
|
||||
|
||||
fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void {
|
||||
const new_cap = std.math.max(new_capacity, MinimalCapacity);
|
||||
const new_cap = std.math.max(new_capacity, minimal_capacity);
|
||||
assert(new_cap > self.capacity());
|
||||
assert(std.math.isPowerOfTwo(new_cap));
|
||||
|
||||
|
|
@ -759,7 +762,7 @@ pub fn HashMapUnmanaged(
|
|||
defer map.deinit(allocator);
|
||||
try map.allocate(allocator, new_cap);
|
||||
map.initMetadatas();
|
||||
map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
|
||||
map.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
|
||||
|
||||
if (self.size != 0) {
|
||||
const old_capacity = self.capacity();
|
||||
|
|
@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" {
|
|||
|
||||
try map.put(0, 0);
|
||||
expectEqual(map.count(), 1);
|
||||
expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity);
|
||||
expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity);
|
||||
|
||||
try map.ensureCapacity(65);
|
||||
expectEqual(map.count(), 1);
|
||||
|
|
|
|||
|
|
@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
|||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
// we don't want to emit optionals and error unions to headers since they have no ABI
|
||||
.typedefs = undefined,
|
||||
};
|
||||
defer dg.fwd_decl.deinit();
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,34 @@ pub const CValue = union(enum) {
|
|||
};
|
||||
|
||||
pub const CValueMap = std.AutoHashMap(*Inst, CValue);
|
||||
pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
|
||||
|
||||
fn formatTypeAsCIdentifier(
|
||||
data: Type,
|
||||
comptime fmt: []const u8,
|
||||
options: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
var buffer = [1]u8{0} ** 128;
|
||||
// We don't care if it gets cut off, it's still more unique than a number
|
||||
var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer;
|
||||
|
||||
for (buf) |c, i| {
|
||||
switch (c) {
|
||||
0 => return writer.writeAll(buf[0..i]),
|
||||
'a'...'z', 'A'...'Z', '_', '$' => {},
|
||||
'0'...'9' => if (i == 0) {
|
||||
buf[i] = '_';
|
||||
},
|
||||
else => buf[i] = '_',
|
||||
}
|
||||
}
|
||||
return writer.writeAll(buf);
|
||||
}
|
||||
|
||||
pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) {
|
||||
return .{ .data = t };
|
||||
}
|
||||
|
||||
/// This data is available when outputting .c code for a Module.
|
||||
/// It is not available when generating .h file.
|
||||
|
|
@ -115,6 +143,7 @@ pub const DeclGen = struct {
|
|||
decl: *Decl,
|
||||
fwd_decl: std.ArrayList(u8),
|
||||
error_msg: ?*Module.ErrorMsg,
|
||||
typedefs: TypedefMap,
|
||||
|
||||
fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
|
||||
dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
|
||||
|
|
@ -325,22 +354,55 @@ pub const DeclGen = struct {
|
|||
const child_type = t.optionalChild(&opt_buf);
|
||||
if (t.isPtrLikeOptional()) {
|
||||
return dg.renderType(w, child_type);
|
||||
} else if (dg.typedefs.get(t)) |some| {
|
||||
return w.writeAll(some.name);
|
||||
}
|
||||
|
||||
// TODO this needs to be typedeffed since different structs are different types.
|
||||
try w.writeAll("struct { ");
|
||||
try dg.renderType(w, child_type);
|
||||
try w.writeAll(" payload; bool is_null; }");
|
||||
var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
|
||||
defer buffer.deinit();
|
||||
const bw = buffer.writer();
|
||||
|
||||
try bw.writeAll("typedef struct { ");
|
||||
try dg.renderType(bw, child_type);
|
||||
try bw.writeAll(" payload; bool is_null; } ");
|
||||
const name_index = buffer.items.len;
|
||||
try bw.print("zig_opt_{s}_t;\n", .{typeToCIdentifier(child_type)});
|
||||
|
||||
const rendered = buffer.toOwnedSlice();
|
||||
errdefer dg.typedefs.allocator.free(rendered);
|
||||
const name = rendered[name_index .. rendered.len - 2];
|
||||
|
||||
try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
|
||||
try w.writeAll(name);
|
||||
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
||||
},
|
||||
.ErrorSet => {
|
||||
comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2);
|
||||
try w.writeAll("uint16_t");
|
||||
},
|
||||
.ErrorUnion => {
|
||||
// TODO this needs to be typedeffed since different structs are different types.
|
||||
try w.writeAll("struct { ");
|
||||
try dg.renderType(w, t.errorUnionChild());
|
||||
try w.writeAll(" payload; uint16_t error; }");
|
||||
if (dg.typedefs.get(t)) |some| {
|
||||
return w.writeAll(some.name);
|
||||
}
|
||||
const child_type = t.errorUnionChild();
|
||||
|
||||
var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
|
||||
defer buffer.deinit();
|
||||
const bw = buffer.writer();
|
||||
|
||||
try bw.writeAll("typedef struct { ");
|
||||
try dg.renderType(bw, t.errorUnionChild());
|
||||
try bw.writeAll(" payload; uint16_t error; } ");
|
||||
const name_index = buffer.items.len;
|
||||
try bw.print("zig_err_union_{s}_t;\n", .{typeToCIdentifier(child_type)});
|
||||
|
||||
const rendered = buffer.toOwnedSlice();
|
||||
errdefer dg.typedefs.allocator.free(rendered);
|
||||
const name = rendered[name_index .. rendered.len - 2];
|
||||
|
||||
try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
|
||||
try w.writeAll(name);
|
||||
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
||||
},
|
||||
.Null, .Undefined => unreachable, // must be const or comptime
|
||||
else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig");
|
|||
const link = @import("../link.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const C = @This();
|
||||
const Type = @import("../type.zig").Type;
|
||||
|
||||
pub const base_tag: link.File.Tag = .c;
|
||||
pub const zig_h = @embedFile("C/zig.h");
|
||||
|
|
@ -28,9 +29,11 @@ pub const DeclBlock = struct {
|
|||
/// Per-function data.
|
||||
pub const FnBlock = struct {
|
||||
fwd_decl: std.ArrayListUnmanaged(u8),
|
||||
typedefs: codegen.TypedefMap.Unmanaged,
|
||||
|
||||
pub const empty: FnBlock = .{
|
||||
.fwd_decl = .{},
|
||||
.typedefs = .{},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
|
|||
pub fn freeDecl(self: *C, decl: *Module.Decl) void {
|
||||
decl.link.c.code.deinit(self.base.allocator);
|
||||
decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
|
||||
var it = decl.fn_link.c.typedefs.iterator();
|
||||
while (it.next()) |some| {
|
||||
self.base.allocator.free(some.value.rendered);
|
||||
}
|
||||
decl.fn_link.c.typedefs.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
|
|
@ -81,8 +89,10 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
|||
defer tracy.end();
|
||||
|
||||
const fwd_decl = &decl.fn_link.c.fwd_decl;
|
||||
const typedefs = &decl.fn_link.c.typedefs;
|
||||
const code = &decl.link.c.code;
|
||||
fwd_decl.shrinkRetainingCapacity(0);
|
||||
typedefs.clearRetainingCapacity();
|
||||
code.shrinkRetainingCapacity(0);
|
||||
|
||||
var object: codegen.Object = .{
|
||||
|
|
@ -91,6 +101,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
|||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
.typedefs = typedefs.promote(module.gpa),
|
||||
},
|
||||
.gpa = module.gpa,
|
||||
.code = code.toManaged(module.gpa),
|
||||
|
|
@ -98,9 +109,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
|||
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
||||
};
|
||||
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
||||
defer object.value_map.deinit();
|
||||
defer object.code.deinit();
|
||||
defer object.dg.fwd_decl.deinit();
|
||||
defer {
|
||||
object.value_map.deinit();
|
||||
object.code.deinit();
|
||||
object.dg.fwd_decl.deinit();
|
||||
var it = object.dg.typedefs.iterator();
|
||||
while (it.next()) |some| {
|
||||
module.gpa.free(some.value.rendered);
|
||||
}
|
||||
object.dg.typedefs.deinit();
|
||||
}
|
||||
|
||||
codegen.genDecl(&object) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
|
|
@ -111,6 +129,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
|||
};
|
||||
|
||||
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
||||
typedefs.* = object.dg.typedefs.unmanaged;
|
||||
object.dg.typedefs.unmanaged = .{};
|
||||
code.* = object.code.moveToUnmanaged();
|
||||
|
||||
// Free excess allocated memory for this Decl.
|
||||
|
|
@ -142,7 +162,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
|||
defer all_buffers.deinit();
|
||||
|
||||
// This is at least enough until we get to the function bodies without error handling.
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 2);
|
||||
|
||||
var file_size: u64 = zig_h.len;
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
|
|
@ -150,22 +170,25 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
|||
.iov_len = zig_h.len,
|
||||
});
|
||||
|
||||
var error_defs_buf = std.ArrayList(u8).init(comp.gpa);
|
||||
defer error_defs_buf.deinit();
|
||||
var err_typedef_buf = std.ArrayList(u8).init(comp.gpa);
|
||||
defer err_typedef_buf.deinit();
|
||||
const err_typedef_writer = err_typedef_buf.writer();
|
||||
const err_typedef_item = all_buffers.addOneAssumeCapacity();
|
||||
|
||||
var it = module.global_error_set.iterator();
|
||||
while (it.next()) |entry| {
|
||||
try error_defs_buf.writer().print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value });
|
||||
render_errors: {
|
||||
if (module.global_error_set.size == 0) break :render_errors;
|
||||
var it = module.global_error_set.iterator();
|
||||
while (it.next()) |entry| {
|
||||
try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value });
|
||||
}
|
||||
try err_typedef_writer.writeByte('\n');
|
||||
}
|
||||
try error_defs_buf.writer().writeByte('\n');
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
.iov_base = error_defs_buf.items.ptr,
|
||||
.iov_len = error_defs_buf.items.len,
|
||||
});
|
||||
|
||||
var fn_count: usize = 0;
|
||||
var typedefs = std.HashMap(Type, []const u8, Type.hash, Type.eql, std.hash_map.default_max_load_percentage).init(comp.gpa);
|
||||
defer typedefs.deinit();
|
||||
|
||||
// Forward decls and non-functions first.
|
||||
// Typedefs, forward decls and non-functions first.
|
||||
// TODO: performance investigation: would keeping a list of Decls that we should
|
||||
// generate, rather than querying here, be faster?
|
||||
for (module.decl_table.items()) |kv| {
|
||||
|
|
@ -174,6 +197,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
|||
.most_recent => |tvm| {
|
||||
const buf = buf: {
|
||||
if (tvm.typed_value.val.castTag(.function)) |_| {
|
||||
var it = decl.fn_link.c.typedefs.iterator();
|
||||
while (it.next()) |new| {
|
||||
if (typedefs.get(new.key)) |previous| {
|
||||
try err_typedef_writer.print("typedef {s} {s};\n", .{ previous, new.value.name });
|
||||
} else {
|
||||
try typedefs.ensureCapacity(typedefs.capacity() + 1);
|
||||
try err_typedef_writer.writeAll(new.value.rendered);
|
||||
typedefs.putAssumeCapacityNoClobber(new.key, new.value.name);
|
||||
}
|
||||
}
|
||||
fn_count += 1;
|
||||
break :buf decl.fn_link.c.fwd_decl.items;
|
||||
} else {
|
||||
|
|
@ -190,6 +223,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
|||
}
|
||||
}
|
||||
|
||||
err_typedef_item.* = .{
|
||||
.iov_base = err_typedef_buf.items.ptr,
|
||||
.iov_len = err_typedef_buf.items.len,
|
||||
};
|
||||
file_size += err_typedef_buf.items.len;
|
||||
|
||||
// Now the function bodies.
|
||||
try all_buffers.ensureCapacity(all_buffers.items.len + fn_count);
|
||||
for (module.decl_table.items()) |kv| {
|
||||
|
|
|
|||
|
|
@ -868,11 +868,10 @@ pub const TestContext = struct {
|
|||
std.testing.zig_exe_path,
|
||||
"run",
|
||||
"-cflags",
|
||||
"-std=c89",
|
||||
"-std=c99",
|
||||
"-pedantic",
|
||||
"-Werror",
|
||||
"-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
|
||||
"-Wno-declaration-after-statement",
|
||||
"--",
|
||||
"-lc",
|
||||
exe_path,
|
||||
|
|
|
|||
|
|
@ -258,6 +258,18 @@ pub fn addCases(ctx: *TestContext) !void {
|
|||
\\ return count - 5;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
// Same with non pointer optionals
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var count: c_int = 0;
|
||||
\\ var opt_ptr: ?c_int = count;
|
||||
\\ while (opt_ptr) |_| : (count += 1) {
|
||||
\\ if (count == 4) opt_ptr = null;
|
||||
\\ }
|
||||
\\ return count - 5;
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
ctx.c("empty start function", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue