From 9a8759efefa24f5ae1e6d91b67dca72357a99ff9 Mon Sep 17 00:00:00 2001 From: Ginger Bill Date: Mon, 17 Jul 2017 15:08:36 +0100 Subject: [PATCH] Polymorphic type specialization for procedures --- LICENSE | 2 +- code/demo.odin | 168 ++++++++++++++++++++++++++++++++++++++++++--- src/check_decl.cpp | 18 ++--- src/check_expr.cpp | 147 +++++++++++++++++++++++++-------------- src/check_stmt.cpp | 19 +++-- src/ir.cpp | 29 +++++--- src/parser.cpp | 3 + src/types.cpp | 14 ++-- 8 files changed, 309 insertions(+), 91 deletions(-) diff --git a/LICENSE b/LICENSE index 7c4776103..f50c5ada3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 Ginger Bill. All rights reserved. +Copyright (c) 2016-2017 Ginger Bill. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/code/demo.odin b/code/demo.odin index 6e0ae84fa..a1ffabb6e 100644 --- a/code/demo.odin +++ b/code/demo.odin @@ -1,5 +1,116 @@ import "fmt.odin"; +Table :: struct(Key, Value: type) { + Slot :: struct { + occupied: bool; + hash: u32; + key: Key; + value: Value; + } + SIZE_MIN :: 32; + + count: int; + allocator: Allocator; + slots: []Slot; +} + +allocate :: proc(table: ^$T/Table, capacity: int) { + c := context; + if table.allocator.procedure != nil do c.allocator = table.allocator; + + push_context c { + table.slots = make([]T.Slot, max(capacity, T.SIZE_MIN)); + } +} + +expand :: proc(table: ^$T/Table) { + c := context; + if table.allocator.procedure != nil do c.allocator = table.allocator; + + push_context c { + old_slots := table.slots; + + cap := max(2*cap(table.slots), T.SIZE_MIN); + allocate(table, cap); + + for s in old_slots do if s.occupied { + put(table, s.key, s.value); + } + + free(old_slots); + } +} + +put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) { +// put :: proc(table: ^Table($K, $V), key: K, value: V) { + hash := get_hash(key); // Ad-hoc method which would fail in differentcope + index := find_index(table, key, hash); + if index < 0 { + if f64(table.count) >= 0.75*cast(f64)cap(table.slots) { + expand(table); + } + assert(table.count <= cap(table.slots)); + + hash := get_hash(key); + index = cast(int)(hash % cast(u32)cap(table.slots)); + + for table.slots[index].occupied { + index += 1; + if index >= cap(table.slots) { + index = 0; + } + } + + table.count++; + } + + slot := &table.slots[index]; + slot.occupied = true; + slot.hash = hash; + slot.key = key; + slot.value = value; +} + + +// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) { +find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) { + hash := get_hash(key); + index := find_index(table, key, hash); + if index < 0 { + return Value{}, false; + } + return table.slots[index].value, true; +} + +find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int { + if cap(table.slots) <= 0 do return -1; + + slot := int(hash % cast(u32)cap(table.slots)); + + index := slot; + for table.slots[index].occupied { + if table.slots[index].hash == hash { + if table.slots[index].key == key { + return index; + } + } + + index++; + if index >= cap(table.slots) do index = 0; + } + + return -1; +} + +get_hash :: proc(s: string) -> u32 { + // djb2 + hash: u32 = 5381; + for i in 0..len(s) do hash = (hash<<5) + hash + u32(s[i]); + return hash; +} + + + Vector :: struct(N: int, T: type) { using _: raw_union { using e: [N]T; @@ -16,14 +127,51 @@ Vector :: struct(N: int, T: type) { Vector3 :: Vector(3, f32); -main :: proc() { - v: Vector3; - v[0] = 1; - v[1] = 4; - v[2] = 9; - fmt.println(v.e); - v.x = 4; - v.y = 9; - v.z = 16; - fmt.println(v.v); +add :: proc(a, b: $T/Vector) -> T { + c := a; + for i in 0..3 { + c[i] += b[i]; + } + return c; +} + +foo1 :: proc(a: type/Vector) { fmt.println("foo1", a{}); } +// foo2 :: proc(a: type/Vector(3, f32)) {} +foo3 :: proc(a: type/Vector(3, $T)) {fmt.println("foo3", a{}); } +// foo4 :: proc(a: type/Vector3) {} + + + +main :: proc() { + foo1(Vector(3, f32)); + foo1(Vector3); + foo3(Vector(3, f32)); + foo3(Vector3); + + + a, b: Vector3; + a[0] = 1; + a[1] = 4; + a[2] = 9; + + b.x = 3; + b.y = 4; + b.z = 5; + + v := add(a, b); + fmt.println(v.v); + + + table: Table(string, int); + + for i in 0..36 do put(&table, "Hellope", i); + for i in 0..42 do put(&table, "World!", i); + + + found, _ := find(&table, "Hellope"); + fmt.printf("found is %v\n", found); + + found, _ = find(&table, "World!"); + fmt.printf("found is %v\n", found); + } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 7c33cf9e5..38923e494 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -60,7 +60,9 @@ Type *check_init_variable(Checker *c, Entity *e, Operand *operand, String contex t = default_type(t); } if (is_type_polymorphic(t)) { - error(e->token, "Invalid use of a polymorphic type in %.*s", LIT(context_name)); + gbString str = type_to_string(t); + defer (gb_string_free(str)); + error(e->token, "Invalid use of a polymorphic type `%s` in %.*s", str, LIT(context_name)); e->type = t_invalid; return nullptr; } @@ -400,17 +402,16 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) { check_open_scope(c, pl->type); defer (check_close_scope(c)); - #if 0 - if (e->token.string == "sort") { - gb_printf_err("%.*s\n", LIT(e->token.string)); - } - #endif + + auto prev_context = c->context; c->context.allow_polymorphic_types = true; check_procedure_type(c, proc_type, pl->type); c->context = prev_context; + TypeProc *pt = &proc_type->Proc; + bool is_foreign = (pl->tags & ProcTag_foreign) != 0; bool is_link_name = (pl->tags & ProcTag_link_name) != 0; bool is_export = (pl->tags & ProcTag_export) != 0; @@ -419,7 +420,6 @@ void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) { bool is_require_results = (pl->tags & ProcTag_require_results) != 0; - TypeProc *pt = &proc_type->Proc; if (d->scope->is_file && e->token.string == "main") { if (pt->param_count != 0 || @@ -559,7 +559,9 @@ void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count e->type = check_type(c, type_expr); } if (e->type != nullptr && is_type_polymorphic(base_type(e->type))) { - error(e->token, "Invalid use of a polymorphic type in %.*s", LIT(context_name)); + gbString str = type_to_string(e->type); + defer (gb_string_free(str)); + error(e->token, "Invalid use of a polymorphic type `%s` in %.*s", str, LIT(context_name)); e->type = t_invalid; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 239d9d482..9e7f66330 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -156,6 +156,7 @@ bool find_or_generate_polymorphic_procedure(Checker *c, Entity *base_entity, Typ // // /////////////////////////////////////////////////////////////////////////////// + if (base_entity == nullptr) { return false; } @@ -163,6 +164,7 @@ bool find_or_generate_polymorphic_procedure(Checker *c, Entity *base_entity, Typ if (!is_type_proc(base_entity->type)) { return false; } + String name = base_entity->token.string; Type *src = base_type(base_entity->type); Type *dst = nullptr; @@ -372,6 +374,9 @@ bool find_or_generate_polymorphic_procedure_from_parameters(Checker *c, Entity * return find_or_generate_polymorphic_procedure(c, base_entity, nullptr, operands, poly_proc_data, false); } +bool check_type_specialization_to(Checker *c, Type *type, Type *specialization, bool modify_type = false); +bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool compound, bool modify_type); + i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) { if (operand->mode == Addressing_Invalid || @@ -467,10 +472,11 @@ i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) { } } +#if 0 if (are_types_identical(dst, src) && (!is_type_named(dst) || !is_type_named(src))) { return 1; } - +#endif if (is_type_bit_field_value(operand->type) && is_type_integer(type)) { Type *bfv = base_type(operand->type); @@ -504,6 +510,13 @@ i64 check_distance_between_types(Checker *c, Operand *operand, Type *type) { } #endif + if (is_type_polymorphic(dst) && !is_type_polymorphic(src)) { + bool modify_type = !c->context.no_polymorphic_errors; + if (is_polymorphic_type_assignable(c, type, s, false, modify_type)) { + return 2; + } + } + if (is_type_union(dst)) { for (isize i = 0; i < dst->Union.variant_count; i++) { Type *vt = dst->Union.variants[i]; @@ -679,21 +692,21 @@ void check_assignment(Checker *c, Operand *operand, Type *type, String context_n defer (gb_string_free(op_type_str)); defer (gb_string_free(expr_str)); - if (operand->mode == Addressing_Builtin) { - // TODO(bill): is this a good enough error message? + switch (operand->mode) { + case Addressing_Builtin: // TODO(bill): Actually allow built in procedures to be passed around and thus be created on use error(operand->expr, "Cannot assign built-in procedure `%s` in %.*s", expr_str, LIT(context_name)); - } else if (operand->mode == Addressing_Type) { - // TODO(bill): is this a good enough error message? - // TODO(bill): Actually allow built in procedures to be passed around and thus be created on use + break; + case Addressing_Type: error(operand->expr, "Cannot assign `%s` which is a type in %.*s", op_type_str, LIT(context_name)); - } else { + break; + default: // TODO(bill): is this a good enough error message? error(operand->expr, "Cannot assign value `%s` of type `%s` to `%s` in %.*s", @@ -701,6 +714,7 @@ void check_assignment(Checker *c, Operand *operand, Type *type, String context_n op_type_str, type_str, LIT(context_name)); + break; } operand->mode = Addressing_Invalid; @@ -1087,7 +1101,8 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node, Arraykind != s->kind) { return false; } + // gb_printf_err("#1 %s %s\n", type_to_string(type), type_to_string(specialization)); + if (t->kind != Type_Record) { + return false; + } + bool show_stuff = false && modify_type; - source = base_type(source); - GB_ASSERT(source->kind == Type_Record && source->Record.kind == TypeRecord_Struct); + if (show_stuff) gb_printf_err("#1 %s %s\n", type_to_string(type), type_to_string(specialization)); + if (t->Record.polymorphic_parent == specialization) { + return true; + } + if (show_stuff) gb_printf_err("#2 %s %s\n", type_to_string(t->Record.polymorphic_parent), type_to_string(specialization)); + if (t->Record.polymorphic_parent == s->Record.polymorphic_parent) { + GB_ASSERT(s->Record.polymorphic_params != nullptr); + GB_ASSERT(t->Record.polymorphic_params != nullptr); - return are_types_identical(source->Record.polymorphic_parent, specific); + if (show_stuff) gb_printf_err("#3 %s -> %s\n", type_to_string(type), type_to_string(specialization)); + + TypeTuple *s_tuple = &s->Record.polymorphic_params->Tuple; + TypeTuple *t_tuple = &t->Record.polymorphic_params->Tuple; + GB_ASSERT(t_tuple->variable_count == s_tuple->variable_count); + for (isize i = 0; i < s_tuple->variable_count; i++) { + Entity *s_e = s_tuple->variables[i]; + Entity *t_e = t_tuple->variables[i]; + Type *st = s_e->type; + Type *tt = t_e->type; + if (show_stuff) gb_printf_err("\t@ %s -> %s\n", type_to_string(st), type_to_string(tt)); + if (show_stuff && base_type(st)->kind == Type_Generic) gb_printf_err("\t$%.*s\n", LIT(base_type(st)->Generic.name)); + bool ok = is_polymorphic_type_assignable(c, st, tt, true, modify_type); + if (show_stuff) gb_printf_err("\t$ %s -> %s\n\n", type_to_string(st), type_to_string(tt)); + } + + + if (modify_type) { + // NOTE(bill): This is needed in order to change the actual type but still have the types defined within it + gb_memmove(specialization, type, gb_size_of(Type)); + } + + return true; + } + + return false; } bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool compound, bool modify_type) { @@ -1565,17 +1620,21 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c o.type = source; switch (poly->kind) { case Type_Basic: - if (compound) return are_types_identical(poly, source); + if (compound) return are_types_identical(source, poly); return check_is_assignable_to(c, &o, poly); - case Type_Named: + case Type_Named: { + if (check_type_specialization_to(c, source, poly, modify_type)) { + return true; + } if (compound) return are_types_identical(poly, source); return check_is_assignable_to(c, &o, poly); + } case Type_Generic: { - if (poly->Generic.specific != nullptr) { - Type *s = poly->Generic.specific; - if (!is_polymorphic_type_assignable_to_specific(c, source, s)) { + if (poly->Generic.specialized != nullptr) { + Type *s = poly->Generic.specialized; + if (!check_type_specialization_to(c, source, s, modify_type)) { return false; } } @@ -1587,7 +1646,7 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c } case Type_Pointer: if (source->kind == Type_Pointer) { - return is_polymorphic_type_assignable(c, poly->Pointer.elem, source->Atomic.elem, true, modify_type); + return is_polymorphic_type_assignable(c, poly->Pointer.elem, source->Pointer.elem, true, modify_type); } return false; case Type_Atomic: @@ -1640,7 +1699,6 @@ bool is_polymorphic_type_assignable(Checker *c, Type *poly, Type *source, bool c case Type_Record: if (source->kind == Type_Record) { - // TODO(bill): Polymorphic type assignment // return check_is_assignable_to(c, &o, poly); } return false; @@ -1718,33 +1776,6 @@ Type *determine_type_from_polymorphic(Checker *c, Type *poly_type, Operand opera return t_invalid; } -bool check_type_specialization_to(Checker *c, Type *type, Type *specialization) { - if (type == nullptr || - type == t_invalid) { - return true; - } - - Type *t = base_type(type); - Type *s = base_type(specialization); - if (t->kind != s->kind) { - return false; - } - // gb_printf_err("#1 %s %s\n", type_to_string(type), type_to_string(specialization)); - if (t->kind != Type_Record) { - return false; - } - // gb_printf_err("#2 %s %s\n", type_to_string(type), type_to_string(specialization)); - if (t->Record.polymorphic_parent == specialization) { - return true; - } - // gb_printf_err("#3 %s %s\n", type_to_string(t->Record.polymorphic_parent), type_to_string(specialization)); - if (t->Record.polymorphic_parent == s->Record.polymorphic_parent) { - return true; - } - - - return false; -} Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_variadic_, bool *success_, Array *operands) { if (_params == nullptr) { @@ -4323,7 +4354,7 @@ Entity *check_selector(Checker *c, Operand *operand, AstNode *node, Type *type_h Entity *e = scope_lookup_entity(c->context.scope, op_name); bool is_alias = false; - while (e->kind == Entity_Alias) { + while (e != nullptr && e->kind == Entity_Alias) { GB_ASSERT(e->Alias.base != nullptr); e = e->Alias.base; is_alias = true; @@ -6549,6 +6580,10 @@ CallArgumentError check_polymorphic_struct_type(Checker *c, Operand *operand, As Entity *p = tuple->variables[j]; Operand o = ordered_operands[j]; if (p->kind == Entity_TypeName) { + if (is_type_polymorphic(o.type)) { + // NOTE(bill): Do not add polymorphic version to the gen_types + ok = false; + } if (!are_types_identical(o.type, p->type)) { ok = false; } @@ -7619,12 +7654,14 @@ ExprKind check_expr_base_internal(Checker *c, Operand *o, AstNode *node, Type *t if (!valid) { gbString str = expr_to_string(o->expr); + gbString type_str = type_to_string(o->type); + defer (gb_string_free(str)); + defer (gb_string_free(type_str)); if (is_const) { error(o->expr, "Cannot index a constant `%s`", str); } else { - error(o->expr, "Cannot index `%s`", str); + error(o->expr, "Cannot index `%s` of type `%s`", str, type_str); } - gb_string_free(str); o->mode = Addressing_Invalid; o->expr = node; return kind; @@ -8009,6 +8046,12 @@ gbString write_expr_to_string(gbString str, AstNode *node) { str = write_expr_to_string(str, ta->type); str = gb_string_appendc(str, ")"); case_end; + case_ast_node(tc, TypeCast, node); + str = gb_string_appendc(str, "cast("); + str = write_expr_to_string(str, tc->type); + str = gb_string_appendc(str, ")"); + str = write_expr_to_string(str, tc->expr); + case_end; case_ast_node(ie, IndexExpr, node); str = write_expr_to_string(str, ie->expr); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 51e6adf79..5df8694b1 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1608,16 +1608,21 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) { Entity *e = nullptr; bool is_selector = false; - if (expr->kind == AstNode_Ident) { - Operand o = {}; + Operand o = {}; + switch (expr->kind) { + case AstNode_Ident: e = check_ident(c, &o, expr, nullptr, nullptr, true); - } else if (expr->kind == AstNode_SelectorExpr) { - Operand o = {}; + break; + case AstNode_SelectorExpr: e = check_selector(c, &o, expr, nullptr); is_selector = true; - } else if (expr->kind == AstNode_Implicit) { + break; + case AstNode_Implicit: error(us->token, "`using` applied to an implicit value"); continue; + default: + error(us->token, "`using` can only be applied to an entity, got %.*s", LIT(ast_node_strings[expr->kind])); + continue; } if (!check_using_stmt_entity(c, us, expr, is_selector, e)) { @@ -1722,7 +1727,9 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) { if (init_type == nullptr) { init_type = t_invalid; } else if (is_type_polymorphic(base_type(init_type))) { - error(vd->type, "Invalid use of a polymorphic type in variable declaration"); + gbString str = type_to_string(init_type); + error(vd->type, "Invalid use of a polymorphic type `%s` in variable declaration", str); + gb_string_free(str); init_type = t_invalid; } } diff --git a/src/ir.cpp b/src/ir.cpp index c3fa9902f..98ebfa4c6 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -2826,6 +2826,9 @@ irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) { } if (are_types_identical(src, dst)) { + if (!are_types_identical(src_type, t)) { + return ir_emit_transmute(proc, value, t); + } return value; } @@ -5752,19 +5755,24 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) { continue; } - bool polymorphic = is_type_polymorphic(e->type); - if (polymorphic && !is_type_struct(e->type)) { - continue; - } - if (map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) { - // NOTE(bill): Nothing depends upon it so doesn't need to be built - continue; - } + String entity_name = e->token.string; if (e->kind == Entity_TypeName) { + bool polymorphic_struct = false; + if (e->type != nullptr && e->kind == Entity_TypeName) { + Type *bt = base_type(e->type); + if (bt->kind == Type_Record) { + polymorphic_struct = bt->Record.is_polymorphic; + } + } + + if (!polymorphic_struct && map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) { + continue; + } + // NOTE(bill): Generate a new name // parent_proc.name-guid - String ts_name = e->token.string; + String ts_name = entity_name; isize name_len = proc->name.len + 1 + ts_name.len + 1 + 10 + 1; u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len); @@ -5787,6 +5795,9 @@ void ir_build_constant_value_decl(irProcedure *proc, AstNodeValueDecl *vd) { auto procs = *found; for_array(i, procs) { Entity *e = procs[i]; + if (map_get(&proc->module->min_dep_map, hash_pointer(e)) == nullptr) { + continue; + } DeclInfo *d = decl_info_of_entity(info, e); ir_build_poly_proc(proc, &d->proc_lit->ProcLit, e); } diff --git a/src/parser.cpp b/src/parser.cpp index c691340a1..1b95b0cc1 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2215,6 +2215,9 @@ AstNode *convert_stmt_to_body(AstFile *f, AstNode *stmt) { syntax_error(stmt, "Expected a normal statement rather than a block statement"); return stmt; } + if (stmt->kind == AstNode_EmptyStmt) { + syntax_error(stmt, "Expected a non-empty statement"); + } GB_ASSERT(is_ast_node_stmt(stmt) || is_ast_node_decl(stmt)); Token open = ast_node_token(stmt); Token close = ast_node_token(stmt); diff --git a/src/types.cpp b/src/types.cpp index a4381ed47..b097432f4 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -108,7 +108,7 @@ struct TypeRecord { TYPE_KIND(Generic, struct { \ i64 id; \ String name; \ - Type * specific; \ + Type * specialized; \ }) \ TYPE_KIND(Pointer, struct { Type *elem; }) \ TYPE_KIND(Atomic, struct { Type *elem; }) \ @@ -486,11 +486,11 @@ Type *make_type_basic(gbAllocator a, BasicType basic) { return t; } -Type *make_type_generic(gbAllocator a, i64 id, String name, Type *specific) { +Type *make_type_generic(gbAllocator a, i64 id, String name, Type *specialized) { Type *t = alloc_type(a, Type_Generic); t->Generic.id = id; t->Generic.name = name; - t->Generic.specific = specific; + t->Generic.specialized = specialized; return t; } @@ -1632,6 +1632,10 @@ Selection lookup_field_with_selection(gbAllocator a, Type *type_, String field_n } } } + if (type->kind == Type_Generic && type->Generic.specialized != nullptr) { + Type *specialized = type->Generic.specialized; + return lookup_field_with_selection(a, specialized, field_name, is_type, sel); + } } else if (type->Record.kind == Type_Union) { if (field_name == "__tag") { @@ -2291,9 +2295,9 @@ gbString write_type_to_string(gbString str, Type *type) { String name = type->Generic.name; str = gb_string_appendc(str, "$"); str = gb_string_append_length(str, name.text, name.len); - if (type->Generic.specific != nullptr) { + if (type->Generic.specialized != nullptr) { str = gb_string_appendc(str, "/"); - str = write_type_to_string(str, type->Generic.specific); + str = write_type_to_string(str, type->Generic.specialized); } } break;