Arbitrary-bit fields inside structs
All checks were successful
CI / ci (ReleaseFast, ubuntu-latest) (pull_request) Successful in 2m25s
CI / ci (ReleaseSmall, ubuntu-latest) (pull_request) Successful in 2m22s
CI / ci (ReleaseSafe, ubuntu-latest) (pull_request) Successful in 2m29s
CI / ci (Debug, ubuntu-latest) (pull_request) Successful in 3m47s
CI / ci (ReleaseFast, ubuntu-latest) (push) Successful in 2m39s
CI / ci (ReleaseSmall, ubuntu-latest) (push) Successful in 2m37s
CI / ci (ReleaseSafe, ubuntu-latest) (push) Successful in 2m44s
CI / ci (Debug, ubuntu-latest) (push) Successful in 4m2s

This commit is contained in:
David Gonzalez Martin 2025-04-20 21:20:12 -06:00
parent b8873564af
commit 2be7142608
4 changed files with 346 additions and 14 deletions

View File

@ -516,6 +516,7 @@ pub const Type = struct {
.integer => |integer| integer.signed,
.bits => |bits| bits.backing_type.is_signed(),
.enumerator => |enumerator| enumerator.backing_type.is_signed(),
.alias => |alias| alias.type.is_signed(),
else => @trap(),
};
}
@ -602,6 +603,7 @@ pub const Type = struct {
.array => |array| array.element_type.get_bit_size() * array.element_count,
.structure => |structure| structure.bit_size,
.enumerator => |enumerator| enumerator.backing_type.get_bit_size(),
.alias => |alias| alias.type.get_bit_size(),
else => @trap(),
};
return bit_size;
@ -3880,7 +3882,7 @@ pub const Module = struct {
const backing_type = maybe_backing_type orelse blk: {
const bits_needed = 64 - @clz(highest_value);
const int_type = module.integer_type(bits_needed, false);
const int_type = module.integer_type(if (bits_needed == 0) 1 else bits_needed, false);
break :blk int_type;
};
@ -5472,7 +5474,10 @@ pub const Module = struct {
pub fn check_types(module: *Module, expected_type: *Type, source_type: *Type) void {
if (expected_type != source_type) {
const dst_p_src_i = expected_type.bb == .pointer and source_type.bb == .integer;
const result = dst_p_src_i;
const dst_alias_src_not = expected_type.bb == .alias and expected_type.bb.alias.type == source_type;
const src_alias_dst_not = source_type.bb == .alias and source_type.bb.alias.type == expected_type;
const both_alias_to_same_type = expected_type.bb == .alias and source_type.bb == .alias and expected_type.bb.alias.type == source_type.bb.alias.type;
const result = dst_p_src_i or dst_alias_src_not or src_alias_dst_not or both_alias_to_same_type;
if (!result) {
module.report_error();
}
@ -6782,7 +6787,11 @@ pub const Module = struct {
}
const llvm_value = extended_value.llvm orelse unreachable;
const destination_type = value_type.llvm.abi.?;
const extension_instruction = switch (extended_value.type.?.bb.integer.signed) {
const extension_type = switch (extended_value.type.?.bb) {
.alias => |alias| alias.type,
else => extended_value.type.?,
};
const extension_instruction = switch (extension_type.bb.integer.signed) {
true => module.llvm.builder.create_sign_extend(llvm_value, destination_type),
false => module.llvm.builder.create_zero_extend(llvm_value, destination_type),
};
@ -8794,7 +8803,8 @@ pub const Abi = struct {
switch (ty.bb) {
.void, .noreturn => result[current_index] = .none,
.bits => result[current_index] = .integer,
.bits => |bits| return classify(bits.backing_type, options),
.enumerator => |enumerator| return classify(enumerator.backing_type, options),
.pointer => result[current_index] = .integer,
.integer => |integer| {
if (integer.bit_count <= 64) {
@ -8930,6 +8940,9 @@ pub const Abi = struct {
fn get_int_type_at_offset(module: *Module, ty: *Type, offset: u32, source_type: *Type, source_offset: u32) *Type {
switch (ty.bb) {
.enumerator => |enumerator| {
return get_int_type_at_offset(module, enumerator.backing_type, offset, if (source_type == ty) enumerator.backing_type else source_type, source_offset);
},
.bits => |bits| {
return get_int_type_at_offset(module, bits.backing_type, offset, if (source_type == ty) bits.backing_type else source_type, source_offset);
},
@ -8945,7 +8958,9 @@ pub const Abi = struct {
}
},
else => {
const byte_count = @min(ty.get_byte_size() - source_offset, 8);
const original_byte_count = ty.get_byte_size();
assert(original_byte_count != source_offset);
const byte_count = @min(original_byte_count - source_offset, 8);
const bit_count = byte_count * 8;
return module.integer_type(@intCast(bit_count), integer_type.signed);
},
@ -8954,7 +8969,12 @@ pub const Abi = struct {
.pointer => return if (offset == 0) ty else @trap(),
.structure => {
if (get_member_at_offset(ty, offset)) |field| {
return get_int_type_at_offset(module, field.type, @intCast(offset - field.byte_offset), source_type, source_offset);
// TODO: this is a addition of mine, since we don't allow arbitrary-bit fields inside structs
const field_type = switch (field.type.bb) {
.integer, .enumerator => module.align_integer_type(field.type),
else => field.type,
};
return get_int_type_at_offset(module, field_type, @intCast(offset - field.byte_offset), source_type, source_offset);
}
unreachable;
},
@ -8964,7 +8984,7 @@ pub const Abi = struct {
const element_offset = (offset / element_size) * element_size;
return get_int_type_at_offset(module, element_type, @intCast(offset - element_offset), source_type, source_offset);
},
.alias => |alias| return get_int_type_at_offset(module, alias.type, offset, source_type, source_offset),
.alias => |alias| return get_int_type_at_offset(module, alias.type, offset, if (ty == source_type) alias.type else source_type, source_offset),
else => |t| @panic(@tagName(t)),
}

View File

@ -1,9 +1,85 @@
mode_t = typealias u64;
int = typealias s32;
usize = typealias u64;
ssize = typealias s64;
File = typealias s32;
[extern] memcmp = fn [cc(c)] (a: &u8, b: &u8, byte_count: u64) s32;
uid_t = typealias u32;
gid_t = typealias u32;
off_t = typealias s64;
ino_t = typealias u64;
dev_t = typealias u64;
timespec = struct
{
seconds: ssize,
nanoseconds: ssize,
};
Stat = struct
{
dev: dev_t,
ino: ino_t,
nlink: usize,
mode: u32,
uid: uid_t,
gid: gid_t,
_: u32,
rdev: dev_t,
size: off_t,
blksize: ssize,
blocks: s64,
atim: timespec,
mtime: timespec,
ctim: timespec,
_: [3]ssize,
}
OAccessMode = enum u2
{
read_only = 0,
write_only = 1,
read_write = 2,
}
O = bits u32
{
access_mode: OAccessMode,
_: u4,
creat: u1,
excl: u1,
noctty: u1,
trunc: u1,
append: u1,
nonblock: u1,
dsync: u1,
async: u1,
direct: u1,
_: u1,
directory: u1,
nofollow: u1,
noatime: u1,
cloexec: u1,
sync: u1,
path: u1,
tmpfile: u1,
_: u9,
}
[extern] memcmp = fn [cc(c)] (a: &u8, b: &u8, byte_count: usize) int;
[extern] memcpy = fn [cc(c)] (destination: &u8, source: &u8, byte_count: u64) &u8;
[extern] exit = fn [cc(c)] (exit_code: s32) noreturn;
[extern] mkdir = fn [cc(c)] (path: &u8, mode: mode_t) s32;
[extern] exit = fn [cc(c)] (exit_code: int) noreturn;
[extern] realpath = fn [cc(c)] (source_path: &u8, resolved_path: &u8) &u8;
[extern] mkdir = fn [cc(c)] (path: &u8, mode: mode_t) int;
[extern] open = fn [cc(c)] (path: &u8, o: O, ...) int;
[extern] close = fn [cc(c)] (fd: File) int;
[extern] fstat = fn [cc(c)] (fd: File, s: &Stat) int;
[extern] write = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize;
[extern] read = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize;
assert = fn (ok: u1) void
{
@ -177,6 +253,109 @@ os_make_directory = fn (path: &u8) void
>result = mkdir(path, 0o755);
}
OpenFlags = bits
{
truncate: u1,
execute: u1,
write: u1,
read: u1,
create: u1,
directory: u1,
}
OpenPermissions = bits
{
read: u1,
write: u1,
execute: u1,
}
os_file_open = fn (path: &u8, flags: OpenFlags, permissions: OpenPermissions) File
{
>access_mode: OAccessMode = undefined;
if (flags.read and flags.write)
{
access_mode = .read_write;
}
else if (flags.read)
{
access_mode = .read_only;
}
else if (flags.write)
{
access_mode = .write_only;
}
else
{
unreachable;
}
>o: O = {
.access_mode = access_mode,
.trunc = flags.truncate,
.creat = flags.create,
.directory = flags.directory,
};
>mode: mode_t = #select(permissions.execute, 0o755, 0o644);
>fd = open(path, o, mode);
return fd;
}
os_file_close = fn (fd: File) void
{
>result = close(fd);
assert(result == 0);
}
os_file_is_valid = fn (fd: File) u1
{
return fd >= 0;
}
os_file_get_size = fn (fd: File) u64
{
>stat: Stat = undefined;
>result = fstat(fd, &stat);
assert(result == 0);
return #extend(stat.size);
}
os_file_read_partially = fn (fd: File, pointer: &u8, length: u64) u64
{
>result = read(fd, pointer, length);
assert(result > 0);
return #extend(result);
}
os_file_read = fn (fd: File, buffer: []u8, byte_count: u64) void
{
assert(byte_count <= buffer.length);
>total_read_byte_count: u64 = 0;
while (total_read_byte_count < byte_count)
{
>read_byte_count = os_file_read_partially(fd, buffer.pointer + total_read_byte_count, byte_count - total_read_byte_count);
total_read_byte_count += read_byte_count;
}
}
os_path_absolute_stack = fn (buffer: []u8, relative_file_path: &u8) []u8
{
>syscall_result = realpath(relative_file_path, buffer.pointer);
>result: []u8 = zero;
if (syscall_result)
{
result = c_string_to_slice(syscall_result);
assert(result.length < buffer.length);
}
return result;
}
Arena = struct
{
reserved_size: u64,
@ -264,6 +443,13 @@ arena_allocate_bytes = fn (arena: &Arena, size: u64, alignment: u64) &u8
return result;
}
arena_duplicate_string = fn (arena: &Arena, string: []u8) []u8
{
>result = arena_allocate_bytes(arena, string.length + 1, 1);
memcpy(result, string.pointer, string.length);
return result[..string.length];
}
arena_join_string = fn (arena: &Arena, pieces: [][]u8) []u8
{
>size: u64 = 0;
@ -288,6 +474,32 @@ arena_join_string = fn (arena: &Arena, pieces: [][]u8) []u8
return pointer[..size];
}
file_read = fn (arena: &Arena, path: []u8) []u8
{
>fd = os_file_open(path.pointer, { .read = 1 }, { .read = 1 });
>result: []u8 = zero;
if (os_file_is_valid(fd))
{
>file_size = os_file_get_size(fd);
>file_buffer = arena_allocate_bytes(arena, file_size, 1);
result = file_buffer[..file_size];
os_file_read(fd, result, file_size);
os_file_close(fd);
}
return result;
}
path_absolute = fn (arena: &Arena, relative_file_path: &u8) []u8
{
>buffer: [4096]u8 = undefined;
>stack_slice = os_path_absolute_stack(buffer[..], relative_file_path);
>result = arena_duplicate_string(arena, stack_slice);
return result;
}
GlobalState = struct
{
arena: &Arena,
@ -335,9 +547,47 @@ CompileFile = struct
base_cache_dir = "bb-cache";
compile_file = fn (arena: &Arena, compile: CompileFile) void
CPUArchitecture = enum
{
>relative_file_path = compile.relative_file_path;
x86_64,
}
OperatingSystem = enum
{
linux,
}
Target = struct
{
cpu: CPUArchitecture,
os: OperatingSystem,
}
target_get_native = fn () Target
{
return { .cpu = .x86_64, .os = .linux };
}
CompileOptions = struct
{
content: []u8,
path: []u8,
executable: []u8,
name: []u8,
objects: [][]u8,
target: Target,
build_mode: BuildMode,
has_debug_info: u1,
silent: u1,
}
compile = fn (arena: &Arena, options: CompileOptions) void
{
}
compile_file = fn (arena: &Arena, compile_options: CompileFile) void
{
>relative_file_path = compile_options.relative_file_path;
if (relative_file_path.length < 5)
{
fail();
@ -367,10 +617,51 @@ compile_file = fn (arena: &Arena, compile: CompileFile) void
>outputh_path_dir = arena_join_string(arena, [
base_cache_dir,
#select(is_compiler, "/compiler/", "/"),
#enum_name(compile.build_mode),
#enum_name(compile_options.build_mode),
"_",
#select(compile.has_debug_info, "di", "nodi"),
#select(compile_options.has_debug_info, "di", "nodi"),
][..]);
os_make_directory(base_cache_dir.pointer);
if (is_compiler)
{
>compiler_dir = arena_join_string(arena, [ base_cache_dir, "/compiler" ][..]);
os_make_directory(compiler_dir.pointer);
}
os_make_directory(outputh_path_dir.pointer);
>outputh_path_base = arena_join_string(arena, [ outputh_path_dir, "/", base_name ][..]);
>output_object_path = arena_join_string(arena, [ outputh_path_base, ".o" ][..]);
>output_executable_path = outputh_path_base;
>file_content = file_read(arena, relative_file_path);
>file_path = path_absolute(arena, relative_file_path.pointer);
>c_abi_object_path = ""; // TODO
>objects: [][]u8 = undefined;
if (string_equal(base_name, "c_abi"))
{
objects = [ output_object_path, c_abi_object_path ][..];
}
else
{
objects = [ output_object_path ][..];
}
>options: CompileOptions = {
.executable = output_executable_path,
.objects = objects,
.name = base_name,
.build_mode = compile_options.build_mode,
.content = file_content,
.path = file_path,
.has_debug_info = compile_options.has_debug_info,
.target = target_get_native(),
.silent = compile_options.silent,
};
compile(arena, options);
}
[export] main = fn [cc(c)] (argument_count: u32, argv: &&u8) s32

View File

@ -322,4 +322,5 @@ const names = &[_][]const u8{
"slice_of_slices",
"type_alias",
"integer_formats",
"return_small_struct",
};

View File

@ -0,0 +1,20 @@
S = struct
{
a: u1,
b: u1,
}
foo = fn [cc(c)] () S
{
return { .a = 1, .b = 0 };
}
[export] main = fn [cc(c)] () s32
{
>s = foo();
if (s.a != 1) #trap();
if (s.b != 0) #trap();
return 0;
}