Implement computing struct layout (size, alignment, field offsets) in type checking

This commit is contained in:
Nikita Lisitsa 2026-03-15 20:12:01 +03:00
parent 0d6b491fd4
commit 8c0b371fdb
4 changed files with 119 additions and 8 deletions

View file

@ -15,6 +15,8 @@ foreign func putchar(c: i32) -> i32
func print(c: u8):
putchar(c as i32)
let x = 0
func test1():
print('H')
print('e')
@ -42,9 +44,20 @@ func test() -> i32:
else:
return b()
print('O')
print('K')
print('\n')
struct interval:
min: f32
max: f32
struct box2f:
x: interval
y: interval
struct weird:
a: i32
b: u8
c: f32 -> f32
d: f64
e: f16
//func test1():
// let str = ['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n']

View file

@ -11,11 +11,24 @@
namespace pslang::ast
{
struct field_layout
{
std::size_t offset = 0;
};
struct field_definition
{
std::string name;
ast::type_ptr type;
ast::location location;
field_layout layout = {};
};
struct struct_layout
{
std::size_t size = 0;
std::size_t alignment = 1;
};
struct struct_definition
@ -24,6 +37,8 @@ namespace pslang::ast
std::vector<field_definition> fields;
ast::location prelude_location;
ast::location location;
struct_layout layout = {};
};
struct field_access

View file

@ -382,13 +382,13 @@ namespace pslang::ast
put_indent(out, options);
out << "field { name = \"" << node.name << "\", type = ";
print(out, *node.type);
out << " }\n";
out << ", offset = " << node.layout.offset << " }\n";
}
void apply(struct_definition const & node)
{
put_indent(out, options);
out << "struct { name = \"" << node.name << "\" }\n";
out << "struct { name = \"" << node.name << "\", size = " << node.layout.size << ", align = " << node.layout.alignment << " }\n";
for (auto const & field : node.fields)
child(field);
}

View file

@ -5,6 +5,7 @@
#include <pslang/ast/error.hpp>
#include <pslang/types/print.hpp>
#include <pslang/types/type.hpp>
#include <pslang/types/type_visitor.hpp>
#include <unordered_map>
#include <sstream>
@ -35,6 +36,10 @@ namespace pslang::ast
types::type_ptr type;
};
ast::struct_definition * node;
bool layout_being_computed = false;
bool layout_ready = false;
std::vector<field_data> fields;
};
@ -45,11 +50,85 @@ namespace pslang::ast
std::unordered_map<std::string, struct_data> structs;
bool is_function_scope = false;
bool is_global_scope = false;
types::type_ptr expected_return_type = nullptr;
};
void compute_layout(struct_data & data, std::vector<scope> & scopes);
struct size_and_alignment
{
std::size_t size;
std::size_t alignment;
};
struct field_layout_visitor
: types::const_visitor<field_layout_visitor>
{
std::vector<scope> & scopes;
using const_visitor::apply;
size_and_alignment apply(types::unit_type const &)
{
return {.size = 1, .alignment = 1};
}
size_and_alignment apply(types::primitive_type const & type)
{
auto size = types::builtin_type_size(type);
return {.size = size, .alignment = size};
}
size_and_alignment apply(types::array_type const & type)
{
auto base = apply(*type.element_type);
return {.size = base.size * type.size, .alignment = base.alignment};
}
size_and_alignment apply(types::function_type const & type)
{
return {.size = 8, .alignment = 8};
}
size_and_alignment apply(types::named_type const & type)
{
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
if (auto jt = it->structs.find(type.name); jt != it->structs.end())
{
// TODO: better error message (including the resursive inclusion path)
if (!jt->second.layout_ready && jt->second.layout_being_computed)
throw validation_error("Recursive structs are not allowed", jt->second.node->location);
compute_layout(jt->second, scopes);
auto & layout = jt->second.node->layout;
return {.size = layout.size, .alignment = layout.alignment};
}
throw std::runtime_error("Unknown type \"" + type.name + "\"");
}
};
void compute_layout(struct_data & data, std::vector<scope> & scopes)
{
if (data.layout_ready)
return;
data.layout_being_computed = true;
auto & layout = data.node->layout;
for (std::size_t i = 0; i < data.fields.size(); ++i)
{
auto field_layout = field_layout_visitor{{}, scopes}.apply(*data.fields[i].type);
layout.alignment = std::max(layout.alignment, field_layout.alignment);
layout.size = ((layout.size + field_layout.alignment - 1) / field_layout.alignment) * field_layout.alignment;
data.node->fields[i].layout.offset = layout.size;
layout.size += field_layout.size;
}
layout.size = ((layout.size + layout.alignment - 1) / layout.alignment) * layout.alignment;
data.layout_ready = true;
}
struct resolve_types_visitor
: type_visitor<resolve_types_visitor>
{
@ -179,12 +258,13 @@ namespace pslang::ast
resolve_types(scopes, *node.type);
}
void apply(struct_definition const & node)
void apply(struct_definition & node)
{
for (auto const & field : node.fields)
apply(field);
auto & data = scopes.back().structs[node.name];
data.node = &node;
for (auto const & field : node.fields)
data.fields.push_back({.name = field.name, .type = get_type(*field.type)});
}
@ -787,6 +867,9 @@ namespace pslang::ast
populate_globals(scopes, node);
for (auto const & statement : node.statements)
apply(*statement);
for (auto & struct_data : scopes.back().structs)
compute_layout(struct_data.second, scopes);
}
};
@ -795,7 +878,7 @@ namespace pslang::ast
void check_and_infer_types(statement_list_ptr & statements)
{
std::vector<scope> scopes;
scopes.emplace_back().is_global_scope = true;
scopes.emplace_back();
check_visitor visitor{{}, {}, scopes};
visitor.apply(*statements);
}