aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/lower.c369
1 files changed, 314 insertions, 55 deletions
diff --git a/boot/lower.c b/boot/lower.c
index b2e6de6..1218281 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -26,6 +26,21 @@
#include "catboot.h"
+struct Lower_Context
+{
+ // the translation unit being created
+ struct Unit* unit;
+
+ // text source of the unit in translation
+ // used for attaching diagnostics to correct lines
+ struct Source_File source;
+
+ // monotonic counter of synthesized types
+ uint synthetic_type_counter;
+
+ // TODO: add function & block scope data
+};
+
void
lower_emit_error(struct Unit* unit, struct Span span, struct String message)
{
@@ -84,16 +99,253 @@ lower_seed_primitive(struct Unit* unit, const ascii* name)
return id;
}
+// structural fingerprint for a source type.
+// two type objects describing the same type shape should always
+// produce identical fingerprints.
+struct String
+lower_type_fingerprint(struct Tree_Type* tree_type)
+{
+ if (!tree_type || tree_type->type == TREE_TYPE_NONE) return string_from_static_c_string("void");
+
+ switch (tree_type->type) {
+ case TREE_TYPE_NAME:
+ return tree_type->value.name.name;
+ case TREE_TYPE_REFERENCE:
+ return string_concatenate(
+ ARG_ASCII, "ref(", ARG_STRING,
+ lower_type_fingerprint(tree_type->value.reference.referenced_type), ARG_ASCII, ")",
+ ARG_END);
+ case TREE_TYPE_MAYBE:
+ return string_concatenate(
+ ARG_ASCII, "maybe(", ARG_STRING,
+ lower_type_fingerprint(tree_type->value.maybe.inner_type), ARG_ASCII, ")", ARG_END);
+ case TREE_TYPE_ARRAY:
+ return string_concatenate(
+ ARG_ASCII, "array(", ARG_STRING,
+ lower_type_fingerprint(tree_type->value.array.element_type), ARG_ASCII, ")", ARG_END);
+ case TREE_TYPE_TUPLE: {
+ struct String_Buffer buf = string_buffer_new(256);
+ string_buffer_append_c_str(&buf, "tuple(");
+ bool first = true;
+ FOR_EACH (struct Tree_Type*, current, tree_type->value.tuple.head) {
+ if (!first) string_buffer_append_c_str(&buf, ",");
+ string_buffer_append(&buf, lower_type_fingerprint(current));
+ first = false;
+ }
+ string_buffer_append_c_str(&buf, ")");
+ return string_buffer_to_string(&buf);
+ }
+ case TREE_TYPE_STRUCTURE: {
+ struct String_Buffer buf = string_buffer_new(256);
+ string_buffer_append_c_str(&buf, "struct(");
+ bool first = true;
+ FOR_EACH (struct Tree_Type*, field, tree_type->value.structure.fields) {
+ if (!first) string_buffer_append_c_str(&buf, ",");
+ string_buffer_append(&buf, field->value_name);
+ string_buffer_append_c_str(&buf, ":");
+ string_buffer_append(&buf, lower_type_fingerprint(field));
+ first = false;
+ }
+ string_buffer_append_c_str(&buf, ")");
+ return string_buffer_to_string(&buf);
+ }
+ case TREE_TYPE_FUNCTION: {
+ struct String_Buffer buf = string_buffer_new(256);
+ string_buffer_append_c_str(&buf, "fun(");
+ string_buffer_append(
+ &buf, lower_type_fingerprint(tree_type->value.function.header.return_type));
+ string_buffer_append_c_str(&buf, ";");
+ bool first = true;
+ FOR_EACH (
+ struct Tree_Type*, param, tree_type->value.function.header.parameters_type_and_name) {
+ if (!first) string_buffer_append_c_str(&buf, ",");
+ string_buffer_append(&buf, lower_type_fingerprint(param));
+ first = false;
+ }
+ string_buffer_append_c_str(&buf, ")");
+ return string_buffer_to_string(&buf);
+ }
+ default:
+ return string_from_static_c_string("unknown");
+ }
+}
+
+struct Type_Ref lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type);
+
+// adds a hard dependency on a type to a type object.
+// only by-value references count as hard dependencies,
+// anything else makes this a no-op.
+void
+lower_add_dependency(struct Type* type, struct Type_Ref ref)
+{
+ if (array_length(&ref.mods) > 0) {
+ enum Type_Modifier outer = *array_at(enum Type_Modifier, &ref.mods, 0);
+ if (outer == TYPE_MOD_REFERENCE) return;
+ }
+ array_push(&type->depends_on, &ref.type_id);
+}
+
+// create a synthetic type name for a structural type.
+struct String
+lower_synthesize_type_name(struct Lower_Context* ctx)
+{
+ const ascii* synthetic_type_name_template = "__cat_type_%lu";
+
+ ascii name_buf[64];
+ snprintf(name_buf, sizeof name_buf, synthetic_type_name_template, ctx->synthetic_type_counter++);
+ return string_from_c_string(name_buf);
+}
+
+// look up an existing structural synthetic by hash, or build a fresh one.
+// inner type references are interned, contributing to dependencies.
+Type_Id
+lower_synthesize_structural(struct Lower_Context* ctx, struct Tree_Type* tree_type, uint64 hash)
+{
+ FOR_EACH_ARRAY(struct Type_Hash_To_Id, mapping, &ctx->unit->types.by_hash, {
+ if (mapping.hash == hash) return mapping.id;
+ })
+
+ Type_Id id = array_length(&ctx->unit->types.entries);
+ struct String name = lower_synthesize_type_name(ctx);
+
+ struct Type* type = type_new(id, TYPE_NONE, name, tree_type->span);
+ type->synthetic = true;
+ type->structural_hash = hash;
+ type->depends_on = array_new(Type_Id, 16);
+ array_push(&ctx->unit->types.entries, &type);
+
+ struct Type_Hash_To_Id mapping = { .hash = hash, .id = id };
+ array_push(&ctx->unit->types.by_hash, &mapping);
+
+ switch (tree_type->type) {
+ case TREE_TYPE_MAYBE: {
+ // `T?` -> `struct { bool present; T value; }`
+ type->kind = TYPE_STRUCTURE;
+
+ type->value.structure.fields = array_new(struct Field, 4);
+
+ struct Field present_f = {
+ .name = string_from_static_c_string("present"),
+ .type = {
+ .type_id = ctx->unit->types.primitive_bool_id,
+ .mods = array_new(enum Type_Modifier, 1),
+ },
+ };
+ array_push(&type->value.structure.fields, &present_f);
+ Type_Id bool_id = ctx->unit->types.primitive_bool_id;
+ array_push(&type->depends_on, &bool_id);
+
+ struct Field value_f = {
+ .name = string_from_static_c_string("value"),
+ .type = lower_intern_type_ref(ctx, tree_type->value.maybe.inner_type),
+ };
+ array_push(&type->value.structure.fields, &value_f);
+ lower_add_dependency(type, value_f.type); // possibly hard dependency
+ break;
+ }
+ case TREE_TYPE_ARRAY: {
+ // `[T]` -> `struct { &T data; uint length; }`
+ type->kind = TYPE_STRUCTURE;
+
+ type->value.structure.fields = array_new(struct Field, 4);
+
+ struct Type_Ref inner = lower_intern_type_ref(ctx, tree_type->value.array.element_type);
+ struct Type_Ref data_ref = {
+ .type_id = inner.type_id,
+ .mods = array_new(enum Type_Modifier, 4),
+ };
+ FOR_EACH_ARRAY(enum Type_Modifier, m, &inner.mods, { array_push(&data_ref.mods, &m); })
+ enum Type_Modifier ptr = TYPE_MOD_REFERENCE;
+ array_push(&data_ref.mods, &ptr);
+
+ struct Field data_f = {
+ .name = string_from_static_c_string("data"),
+ .type = data_ref,
+ };
+ array_push(&type->value.structure.fields, &data_f);
+ // no dependency
+
+ struct Field length_f = {
+ .name = string_from_static_c_string("length"),
+ .type = {
+ .type_id = ctx->unit->types.primitive_uint_id,
+ .mods = array_new(enum Type_Modifier, 1),
+ },
+ };
+ array_push(&type->value.structure.fields, &length_f);
+ Type_Id uint_id = ctx->unit->types.primitive_uint_id;
+ array_push(&type->depends_on, &uint_id);
+ break;
+ }
+ case TREE_TYPE_TUPLE: {
+ // `(T1, T2, ...)` -> `struct { T1 _0; T2 _1; ... }`
+ type->kind = TYPE_STRUCTURE;
+
+ type->value.structure.fields = array_new(struct Field, 16);
+ uint idx = 0;
+ FOR_EACH (struct Tree_Type*, current, tree_type->value.tuple.head) {
+ ascii field_buf[16];
+ snprintf(field_buf, sizeof field_buf, "_%lu", idx++);
+ struct Field f = {
+ .name = string_from_c_string(field_buf),
+ .type = lower_intern_type_ref(ctx, current),
+ };
+ array_push(&type->value.structure.fields, &f);
+ lower_add_dependency(type, f.type); // possibly hard dependency for each element
+ }
+ break;
+ }
+ case TREE_TYPE_STRUCTURE: {
+ // `{ x, y T }` -> `struct { uint x; uint y; }`
+ type->kind = TYPE_STRUCTURE;
+
+ type->value.structure.fields = array_new(struct Field, 16);
+ FOR_EACH (struct Tree_Type*, tree_field, tree_type->value.structure.fields) {
+ struct Field f = {
+ .name = tree_field->value_name,
+ .type = lower_intern_type_ref(ctx, tree_field),
+ };
+ array_push(&type->value.structure.fields, &f);
+ lower_add_dependency(type, f.type); // possibly hard dependency for each field
+ }
+ break;
+ }
+ case TREE_TYPE_FUNCTION: {
+ // `fun x(n X) Y` -> `Y x(X n) {}`
+ type->kind = TYPE_FUNCTION;
+
+ struct Tree_Function_Header* header = &tree_type->value.function.header;
+
+ type->value.function.return_type = lower_intern_type_ref(ctx, header->return_type);
+ lower_add_dependency(type, type->value.function.return_type); // possibly hard dependency on return
+
+ type->value.function.params = array_new(struct Type_Ref, 16);
+ bool variadic = false;
+ FOR_EACH (struct Tree_Type*, param, header->parameters_type_and_name) {
+ struct Type_Ref ref = lower_intern_type_ref(ctx, param);
+ array_push(&type->value.function.params, &ref);
+ lower_add_dependency(type, ref); // possibly hard dependency for parameter
+ if (param->variadic) variadic = true;
+ }
+ type->value.function.variadic = variadic;
+ break;
+ }
+ default:
+ // unreachable!
+ break;
+ }
+
+ return id;
+}
+
// turns a source type expression into a concrete type reference object.
-// resolving named types from the type table.
-// unwraps any type modifiers (like references, arrays, etc.),
-// adding them into the type reference object.
+// resolves named types from the type table, peels out reference modifiers,
+// and synthesizes new entries for structural type shapes.
struct Type_Ref
-lower_intern_type_ref(struct Unit* unit, struct Tree_Type* tree_type)
+lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type)
{
- // TODO: support synthetic structural types.
struct Type_Ref ref = {
- .type_id = unit->types.primitive_void_id,
+ .type_id = ctx->unit->types.primitive_void_id,
.mods = array_new(enum Type_Modifier, 4),
};
@@ -111,38 +363,34 @@ lower_intern_type_ref(struct Unit* unit, struct Tree_Type* tree_type)
switch (current->type) {
case TREE_TYPE_NAME: {
Type_Id id;
- // at this point we know all top-level types (independent of order),
- // if we fail to find the type referenced by the name, we know for a fact
- // this type does not exist.
- if (lower_type_lookup_by_name(unit, current->value.name.name, &id)) {
+ if (lower_type_lookup_by_name(ctx->unit, current->value.name.name, &id)) {
ref.type_id = id;
} else {
lower_emit_error(
- unit, current->span,
+ ctx->unit, current->span,
string_concatenate(
ARG_ASCII, "undefined type '", ARG_STRING, current->value.name.name, ARG_ASCII,
"'", ARG_END));
}
return ref;
}
- default:
- lower_emit_error_c(
- unit, current->span,
- "unimplemented: only named types and references are supported for now");
+ case TREE_TYPE_MAYBE:
+ case TREE_TYPE_ARRAY:
+ case TREE_TYPE_TUPLE:
+ case TREE_TYPE_STRUCTURE:
+ case TREE_TYPE_FUNCTION: {
+ struct String fp = lower_type_fingerprint(current);
+ uint64 hash = fnv1a_64(fp);
+ ref.type_id = lower_synthesize_structural(ctx, current, hash);
return ref;
}
-}
-
-// adds a by-value dependency. by-pointer references don't add — a forward
-// declaration of the target is enough to use it through a pointer.
-void
-lower_add_dependency(struct Type* type, struct Type_Ref ref)
-{
- if (array_length(&ref.mods) > 0) {
- enum Type_Modifier outer = *array_at(enum Type_Modifier, &ref.mods, 0);
- if (outer == TYPE_MOD_REFERENCE) return;
+ case TREE_TYPE_MAP:
+ lower_emit_error_c(ctx->unit, current->span, "unimplemented: map types");
+ return ref;
+ default:
+ lower_emit_error_c(ctx->unit, current->span, "unimplemented: this type form");
+ return ref;
}
- array_push(&type->depends_on, &ref.type_id);
}
// is this source statement a function?
@@ -235,10 +483,10 @@ lower_register_type_shell(struct Unit* unit, struct String name, struct Span spa
void
lower_fill_function_signature(
- struct Unit* unit, struct Function* fn, struct Tree_Expression* fn_expr)
+ struct Lower_Context* ctx, struct Function* fn, struct Tree_Expression* fn_expr)
{
fn->is_main = string_equals_c_str(fn->name, "main");
- fn->return_type = lower_intern_type_ref(unit, fn_expr->value.function.header.return_type);
+ fn->return_type = lower_intern_type_ref(ctx, fn_expr->value.function.header.return_type);
fn->params = array_new(struct Param, 16);
bool variadic = false;
@@ -246,7 +494,7 @@ lower_fill_function_signature(
struct Tree_Type*, param_type, fn_expr->value.function.header.parameters_type_and_name) {
struct Param p = {
.name = param_type->value_name,
- .type = lower_intern_type_ref(unit, param_type),
+ .type = lower_intern_type_ref(ctx, param_type),
};
array_push(&fn->params, &p);
if (param_type->variadic) variadic = true;
@@ -258,19 +506,19 @@ lower_fill_function_signature(
}
void
-lower_fill_type_body(struct Unit* unit, struct Type* type, struct Tree_Type* tree_type)
+lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_Type* tree_type)
{
switch (tree_type->type) {
case TREE_TYPE_NAME: {
type->kind = TYPE_ALIAS;
Type_Id target_id;
- if (lower_type_lookup_by_name(unit, tree_type->value.name.name, &target_id)) {
+ if (lower_type_lookup_by_name(ctx->unit, tree_type->value.name.name, &target_id)) {
type->value.alias.target_id = target_id;
// aliases always need their target's full definition.
array_push(&type->depends_on, &target_id);
} else {
lower_emit_error(
- unit, tree_type->span,
+ ctx->unit, tree_type->span,
string_concatenate(
ARG_ASCII, "undefined type '", ARG_STRING, tree_type->value.name.name,
ARG_ASCII, "'", ARG_END));
@@ -283,7 +531,7 @@ lower_fill_type_body(struct Unit* unit, struct Type* type, struct Tree_Type* tre
FOR_EACH (struct Tree_Type*, tree_field, tree_type->value.structure.fields) {
struct Field f = {
.name = tree_field->value_name,
- .type = lower_intern_type_ref(unit, tree_field),
+ .type = lower_intern_type_ref(ctx, tree_field),
};
array_push(&type->value.structure.fields, &f);
lower_add_dependency(type, f.type);
@@ -302,7 +550,7 @@ lower_fill_type_body(struct Unit* unit, struct Type* type, struct Tree_Type* tre
.payload = { 0 },
};
if (c.has_payload) {
- c.payload = lower_intern_type_ref(unit, tree_case);
+ c.payload = lower_intern_type_ref(ctx, tree_case);
lower_add_dependency(type, c.payload);
}
array_push(&type->value.variant.cases, &c);
@@ -312,13 +560,13 @@ lower_fill_type_body(struct Unit* unit, struct Type* type, struct Tree_Type* tre
case TREE_TYPE_FUNCTION: {
type->kind = TYPE_FUNCTION;
struct Tree_Function_Header* header = &tree_type->value.function.header;
- type->value.function.return_type = lower_intern_type_ref(unit, header->return_type);
+ type->value.function.return_type = lower_intern_type_ref(ctx, header->return_type);
lower_add_dependency(type, type->value.function.return_type);
type->value.function.params = array_new(struct Type_Ref, 16);
bool variadic = false;
FOR_EACH (struct Tree_Type*, param_type, header->parameters_type_and_name) {
- struct Type_Ref ref = lower_intern_type_ref(unit, param_type);
+ struct Type_Ref ref = lower_intern_type_ref(ctx, param_type);
array_push(&type->value.function.params, &ref);
lower_add_dependency(type, ref);
if (param_type->variadic) variadic = true;
@@ -327,18 +575,22 @@ lower_fill_type_body(struct Unit* unit, struct Type* type, struct Tree_Type* tre
break;
}
case TREE_TYPE_CLASS:
- lower_emit_error_c(unit, tree_type->span, "unimplemented: class types");
+ lower_emit_error_c(ctx->unit, tree_type->span, "unimplemented: class types");
break;
- default:
- // structural compound forms (array, maybe, tuple, map) at top level
- // belong to step 10 (structural canonicalization).
- lower_emit_error_c(unit, tree_type->span, "unimplemented: this top-level type form");
+ default: {
+ // this is a type alias assigning a name to a structural type.
+ // create a new synthetic type, and point our type as alias towards it.
+ struct Type_Ref ref = lower_intern_type_ref(ctx, tree_type);
+ type->kind = TYPE_ALIAS;
+ type->value.alias.target_id = ref.type_id;
+ array_push(&type->depends_on, &ref.type_id);
break;
}
+ }
}
// lowering pass 1, sub-pass a
-// collection of every single declaration of a type of function,
+// collection of every single declaration of a type or function,
// alongside with initial registration of any referenced dependencies.
void
lower_pass_1_register_shells(struct Unit* unit, struct Tree* tree)
@@ -366,7 +618,7 @@ lower_pass_1_register_shells(struct Unit* unit, struct Tree* tree)
// now that sub-pass a has registered all top-level definitions, we can
// finally build out the type reference dag within the translation unit.
void
-lower_pass_1_fill_bodies(struct Unit* unit, struct Tree* tree)
+lower_pass_1_fill_bodies(struct Lower_Context* ctx, struct Tree* tree)
{
FOR_EACH (struct Tree_Statement*, stmt, tree->top_level_statements) {
struct String name;
@@ -375,9 +627,10 @@ lower_pass_1_fill_bodies(struct Unit* unit, struct Tree* tree)
if (lower_match_function_decl(stmt, &name, &fn_expr)) {
Function_Id id;
// TODO: actually go into the body, only the signature for now.
- if (lower_function_lookup_by_name(unit, name, &id)) {
- struct Function* fn = *array_at(struct Function*, &unit->functions.entries, id);
- if (!fn->ast_body) lower_fill_function_signature(unit, fn, fn_expr);
+ if (lower_function_lookup_by_name(ctx->unit, name, &id)) {
+ struct Function* fn =
+ *array_at(struct Function*, &ctx->unit->functions.entries, id);
+ if (!fn->ast_body) lower_fill_function_signature(ctx, fn, fn_expr);
}
continue;
}
@@ -385,19 +638,19 @@ lower_pass_1_fill_bodies(struct Unit* unit, struct Tree* tree)
struct Tree_Type* tree_type;
if (lower_match_type_decl(stmt, &name, &tree_type)) {
Type_Id id;
- if (lower_type_lookup_by_name(unit, name, &id)) {
- struct Type* type = *array_at(struct Type*, &unit->types.entries, id);
- if (type->kind == TYPE_NONE) lower_fill_type_body(unit, type, tree_type);
+ if (lower_type_lookup_by_name(ctx->unit, name, &id)) {
+ struct Type* type = *array_at(struct Type*, &ctx->unit->types.entries, id);
+ if (type->kind == TYPE_NONE) lower_fill_type_body(ctx, type, tree_type);
}
continue;
}
if (stmt->kind == TREE_STATEMENT_PRAGMA) {
- lower_emit_error_c(unit, stmt->span, "unimplemented: top-level pragmas");
+ lower_emit_error_c(ctx->unit, stmt->span, "unimplemented: top-level pragmas");
continue;
}
- lower_emit_error_c(unit, stmt->span, "unsupported top-level statement");
+ lower_emit_error_c(ctx->unit, stmt->span, "unsupported top-level statement");
}
}
@@ -405,10 +658,10 @@ lower_pass_1_fill_bodies(struct Unit* unit, struct Tree* tree)
// collects all top-level definitions into the translation unit's
// tables and fully maps out the references between them.
void
-lower_pass_1(struct Unit* unit, struct Tree* tree)
+lower_pass_1(struct Lower_Context* ctx, struct Tree* tree)
{
- lower_pass_1_register_shells(unit, tree);
- lower_pass_1_fill_bodies(unit, tree);
+ lower_pass_1_register_shells(ctx->unit, tree);
+ lower_pass_1_fill_bodies(ctx, tree);
}
// return line number for the given byte position in the source. (1-based)
@@ -592,6 +845,12 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
unit->types.primitive_ascii_id = lower_seed_primitive(unit, "ascii");
unit->types.primitive_void_id = lower_seed_primitive(unit, "void");
- if (tree) lower_pass_1(unit, tree);
+ struct Lower_Context ctx = {
+ .unit = unit,
+ .source = source,
+ .synthetic_type_counter = 0,
+ };
+
+ if (tree) lower_pass_1(&ctx, tree);
lower_topological_sort_dependency_graph(unit, source);
}