From 0f399a72941c7cebcb5ad0580a9d94d1a7a37ac0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 Feb 2020 11:10:28 +0000 Subject: [PATCH] Add `union #maybe` --- core/fmt/fmt.odin | 13 +++++- core/runtime/core.odin | 3 +- examples/demo/demo.odin | 23 ++++++++++ src/check_expr.cpp | 98 +++++++++++++++++++++++++---------------- src/check_type.cpp | 6 +++ src/ir.cpp | 60 +++++++++++++++++++------ src/ir_print.cpp | 32 ++++++++++++-- src/parser.cpp | 23 ++++++++-- src/parser.hpp | 1 + src/types.cpp | 51 ++++++++++++++++++--- 10 files changed, 244 insertions(+), 66 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index ec9a0888d..c061c1c47 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1525,10 +1525,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { return; } + + if info.maybe && len(info.variants) == 1 && reflect.is_pointer(info.variants[0]) { + if v.data == nil { + strings.write_string(fi.buf, "nil"); + } else { + id := info.variants[0].id; + fmt_arg(fi, any{v.data, id}, verb); + } + return; + } + + tag: i64 = -1; tag_ptr := uintptr(v.data) + info.tag_offset; tag_any := any{rawptr(tag_ptr), info.tag_type.id}; - tag: i64 = -1; switch i in tag_any { case u8: tag = i64(i); case i8: tag = i64(i); diff --git a/core/runtime/core.odin b/core/runtime/core.odin index ad8b83fee..4ffd716ef 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -111,6 +111,7 @@ Type_Info_Union :: struct { tag_type: ^Type_Info, custom_align: bool, no_nil: bool, + maybe: bool, }; Type_Info_Enum :: struct { base: ^Type_Info, @@ -1131,7 +1132,7 @@ __dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap: array.allocator = context.allocator; } assert(array.allocator.procedure != nil); - + if cap <= array.cap do return true; old_size := array.cap * elem_size; diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index a6b7e44f5..9704d3e36 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1907,6 +1907,28 @@ constant_literal_expressions :: proc() { fmt.println(STRING_CONST[3:][:4]); } +union_maybe :: proc() { + fmt.println("\n#union #maybe"); + + Maybe :: union(T: typeid) #maybe {T}; + + i: Maybe(u8); + p: Maybe(^u8); // No tag is stored for pointers, nil is the sentinel value + + #assert(size_of(i) == size_of(u8) + size_of(u8)); + #assert(size_of(p) == size_of(^u8)); + + i = 123; + x := i.?; + y, y_ok := p.?; + p = &x; + z, z_ok := p.?; + + fmt.println(i, p); + fmt.println(x, &x); + fmt.println(y, y_ok); + fmt.println(z, z_ok); +} main :: proc() { when true { @@ -1937,5 +1959,6 @@ main :: proc() { threading_example(); soa_struct_layout(); constant_literal_expressions(); + union_maybe(); } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b854a693b..52c1f38b7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8572,8 +8572,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type o->expr = node; return kind; } - Type *t = check_type(c, ta->type); - if (o->mode == Addressing_Constant) { gbString expr_str = expr_to_string(o->expr); error(o->expr, "A type assertion cannot be applied to a constant expression: '%s'", expr_str); @@ -8594,54 +8592,80 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type bool src_is_ptr = is_type_pointer(o->type); Type *src = type_deref(o->type); - Type *dst = t; Type *bsrc = base_type(src); - Type *bdst = base_type(dst); - if (is_type_union(src)) { - bool ok = false; - for_array(i, bsrc->Union.variants) { - Type *vt = bsrc->Union.variants[i]; - if (are_types_identical(vt, dst)) { - ok = true; - break; - } + if (ta->type != nullptr && ta->type->kind == Ast_UnaryExpr && ta->type->UnaryExpr.op.kind == Token_Question) { + if (!is_type_union(src)) { + gbString str = type_to_string(o->type); + error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %s", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; } - - if (!ok) { - gbString expr_str = expr_to_string(o->expr); - gbString dst_type_str = type_to_string(t); - defer (gb_string_free(expr_str)); - defer (gb_string_free(dst_type_str)); - if (bsrc->Union.variants.count == 0) { - error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str); - } else { - error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str); - } + if (bsrc->Union.variants.count != 1) { + error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %lld", cast(long long)bsrc->Union.variants.count); o->mode = Addressing_Invalid; o->expr = node; return kind; } add_type_info_type(c, o->type); - add_type_info_type(c, t); + add_type_info_type(c, bsrc->Union.variants[0]); - o->type = t; + o->type = bsrc->Union.variants[0]; o->mode = Addressing_OptionalOk; - } else if (is_type_any(src)) { - o->type = t; - o->mode = Addressing_OptionalOk; - - add_type_info_type(c, o->type); - add_type_info_type(c, t); } else { - gbString str = type_to_string(o->type); - error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str); - gb_string_free(str); - o->mode = Addressing_Invalid; - o->expr = node; - return kind; + Type *t = check_type(c, ta->type); + Type *dst = t; + Type *bdst = base_type(dst); + + + if (is_type_union(src)) { + bool ok = false; + for_array(i, bsrc->Union.variants) { + Type *vt = bsrc->Union.variants[i]; + if (are_types_identical(vt, dst)) { + ok = true; + break; + } + } + + if (!ok) { + gbString expr_str = expr_to_string(o->expr); + gbString dst_type_str = type_to_string(t); + defer (gb_string_free(expr_str)); + defer (gb_string_free(dst_type_str)); + if (bsrc->Union.variants.count == 0) { + error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str); + } else { + error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str); + } + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } + + add_type_info_type(c, o->type); + add_type_info_type(c, t); + + o->type = t; + o->mode = Addressing_OptionalOk; + } else if (is_type_any(src)) { + o->type = t; + o->mode = Addressing_OptionalOk; + + add_type_info_type(c, o->type); + add_type_info_type(c, t); + } else { + gbString str = type_to_string(o->type); + error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } } add_package_dependency(c, "runtime", "type_assertion_check"); diff --git a/src/check_type.cpp b/src/check_type.cpp index f21c1563b..f21ffc956 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -748,11 +748,17 @@ void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, ArrayUnion.variants = variants; union_type->Union.no_nil = ut->no_nil; + union_type->Union.maybe = ut->maybe; if (union_type->Union.no_nil) { if (variants.count < 2) { error(ut->align, "A union with #no_nil must have at least 2 variants"); } } + if (union_type->Union.maybe) { + if (variants.count != 1) { + error(ut->align, "A union with #maybe must have at 1 variant, got %lld", cast(long long)variants.count); + } + } if (ut->align != nullptr) { i64 custom_align = 1; diff --git a/src/ir.cpp b/src/ir.cpp index 5d075d7dd..2f4d491e7 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1222,7 +1222,11 @@ irValue *ir_instr_union_tag_ptr(irProcedure *p, irValue *address) { // i->UnionTagPtr.type = alloc_type_pointer(t_type_info_ptr); Type *u = type_deref(ir_type(address)); + if (is_type_union_maybe_pointer(u)) { + GB_PANIC("union #maybe UnionTagPtr"); + } i->UnionTagPtr.type = alloc_type_pointer(union_tag_type(u)); + return v; } @@ -1234,6 +1238,9 @@ irValue *ir_instr_union_tag_value(irProcedure *p, irValue *address) { if (address) address->uses += 1; Type *u = type_deref(ir_type(address)); + if (is_type_union_maybe_pointer(u)) { + GB_PANIC("union #maybe UnionTagValue"); + } i->UnionTagPtr.type = union_tag_type(u); return v; } @@ -4351,7 +4358,9 @@ irValue *ir_emit_union_tag_value(irProcedure *proc, irValue *u) { irValue *ir_emit_comp_against_nil(irProcedure *proc, TokenKind op_kind, irValue *x) { Type *t = ir_type(x); - if (is_type_cstring(t)) { + if (is_type_pointer(t)) { + return ir_emit_comp(proc, op_kind, x, v_raw_nil); + } else if (is_type_cstring(t)) { irValue *ptr = ir_emit_conv(proc, x, t_u8_ptr); return ir_emit_comp(proc, op_kind, ptr, v_raw_nil); } else if (is_type_any(t)) { @@ -5232,8 +5241,12 @@ void ir_emit_store_union_variant(irProcedure *proc, irValue *parent, irValue *va Type *t = type_deref(ir_type(parent)); - irValue *tag_ptr = ir_emit_union_tag_ptr(proc, parent); - ir_emit_store(proc, tag_ptr, ir_const_union_tag(t, variant_type)); + if (is_type_union_maybe_pointer(t)) { + // No tag needed! + } else { + irValue *tag_ptr = ir_emit_union_tag_ptr(proc, parent); + ir_emit_store(proc, tag_ptr, ir_const_union_tag(t, variant_type)); + } } irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) { @@ -5728,22 +5741,41 @@ irValue *ir_emit_union_cast(irProcedure *proc, irValue *value, Type *type, Token GB_ASSERT_MSG(is_type_union(src), "%s", type_to_string(src_type)); Type *dst = tuple->Tuple.variables[0]->type; - irValue *value_ = ir_address_from_load_or_generate_local(proc, value); - irValue *tag = ir_emit_load(proc, ir_emit_union_tag_ptr(proc, value_)); - irValue *dst_tag = ir_const_union_tag(src, dst); - - irBlock *ok_block = ir_new_block(proc, nullptr, "union_cast.ok"); - irBlock *end_block = ir_new_block(proc, nullptr, "union_cast.end"); - irValue *cond = ir_emit_comp(proc, Token_CmpEq, tag, dst_tag); - ir_emit_if(proc, cond, ok_block, end_block); - ir_start_block(proc, ok_block); + irValue *tag = nullptr; + irValue *dst_tag = nullptr; + irValue *cond = nullptr; + irValue *data = nullptr; irValue *gep0 = ir_emit_struct_ep(proc, v, 0); irValue *gep1 = ir_emit_struct_ep(proc, v, 1); - irValue *data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0))); + if (is_type_union_maybe_pointer(src)) { + data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0))); + } else { + tag = ir_emit_load(proc, ir_emit_union_tag_ptr(proc, value_)); + dst_tag = ir_const_union_tag(src, dst); + } + + irBlock *ok_block = ir_new_block(proc, nullptr, "union_cast.ok"); + irBlock *end_block = ir_new_block(proc, nullptr, "union_cast.end"); + + if (data != nullptr) { + GB_ASSERT(is_type_union_maybe_pointer(src)); + cond = ir_emit_comp_against_nil(proc, Token_NotEq, data); + } else { + cond = ir_emit_comp(proc, Token_CmpEq, tag, dst_tag); + } + + ir_emit_if(proc, cond, ok_block, end_block); + ir_start_block(proc, ok_block); + + + + if (data == nullptr) { + data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0))); + } ir_emit_store(proc, gep0, data); ir_emit_store(proc, gep1, v_true); @@ -11465,6 +11497,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info irValue *tag_type_ptr = ir_emit_struct_ep(proc, tag, 2); irValue *custom_align_ptr = ir_emit_struct_ep(proc, tag, 3); irValue *no_nil_ptr = ir_emit_struct_ep(proc, tag, 4); + irValue *maybe_ptr = ir_emit_struct_ep(proc, tag, 5); isize variant_count = gb_max(0, t->Union.variants.count); irValue *memory_types = ir_type_info_member_types_offset(proc, variant_count); @@ -11494,6 +11527,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info ir_emit_store(proc, custom_align_ptr, is_custom_align); ir_emit_store(proc, no_nil_ptr, ir_const_bool(t->Union.no_nil)); + ir_emit_store(proc, maybe_ptr, ir_const_bool(t->Union.maybe)); } break; diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 2baf1c2a0..a3f28c59f 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -508,13 +508,25 @@ void ir_print_type(irFileBuffer *f, irModule *m, Type *t, bool in_struct) { // NOTE(bill): The zero size array is used to fix the alignment used in a structure as // LLVM takes the first element's alignment as the entire alignment (like C) i64 align = type_align_of(t); + + if (is_type_union_maybe_pointer_original_alignment(t)) { + ir_write_byte(f, '{'); + ir_print_type(f, m, t->Union.variants[0]); + ir_write_byte(f, '}'); + return; + } + i64 block_size = t->Union.variant_block_size; ir_write_byte(f, '{'); ir_print_alignment_prefix_hack(f, align); - ir_fprintf(f, ", [%lld x i8], ", block_size); - // ir_print_type(f, m, t_type_info_ptr); - ir_print_type(f, m, union_tag_type(t)); + if (is_type_union_maybe_pointer(t)) { + ir_fprintf(f, ", "); + ir_print_type(f, m, t->Union.variants[0]); + } else { + ir_fprintf(f, ", [%lld x i8], ", block_size); + ir_print_type(f, m, union_tag_type(t)); + } ir_write_byte(f, '}'); } return; @@ -1850,6 +1862,12 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) { case irInstr_UnionTagPtr: { Type *et = ir_type(instr->UnionTagPtr.address); + + Type *ut = type_deref(et); + if (is_type_union_maybe_pointer(ut)) { + GB_PANIC("union #maybe UnionTagPtr"); + } + ir_fprintf(f, "%%%d = getelementptr inbounds ", value->index); Type *t = base_type(type_deref(et)); GB_ASSERT(is_type_union(t)); @@ -1869,10 +1887,16 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) { case irInstr_UnionTagValue: { Type *et = ir_type(instr->UnionTagValue.address); - ir_fprintf(f, "%%%d = extractvalue ", value->index); Type *t = base_type(et); + + if (is_type_union_maybe_pointer(t)) { + GB_PANIC("union #maybe UnionTagValue"); + } + + ir_fprintf(f, "%%%d = extractvalue ", value->index); GB_ASSERT(is_type_union(t)); + ir_print_type(f, m, et); ir_write_byte(f, ' '); ir_print_value(f, m, instr->UnionTagValue.address, et); diff --git a/src/parser.cpp b/src/parser.cpp index ecb02c803..2e2a1b97e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -929,7 +929,7 @@ Ast *ast_struct_type(AstFile *f, Token token, Array fields, isize field_c } -Ast *ast_union_type(AstFile *f, Token token, Array variants, Ast *polymorphic_params, Ast *align, bool no_nil, +Ast *ast_union_type(AstFile *f, Token token, Array variants, Ast *polymorphic_params, Ast *align, bool no_nil, bool maybe, Token where_token, Array const &where_clauses) { Ast *result = alloc_ast_node(f, Ast_UnionType); result->UnionType.token = token; @@ -937,6 +937,7 @@ Ast *ast_union_type(AstFile *f, Token token, Array variants, Ast *polymor result->UnionType.polymorphic_params = polymorphic_params; result->UnionType.align = align; result->UnionType.no_nil = no_nil; + result->UnionType.maybe = maybe; result->UnionType.where_token = where_token; result->UnionType.where_clauses = where_clauses; return result; @@ -2091,6 +2092,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { Ast *polymorphic_params = nullptr; Ast *align = nullptr; bool no_nil = false; + bool maybe = false; CommentGroup *docs = f->lead_comment; Token start_token = f->curr_token; @@ -2118,10 +2120,19 @@ Ast *parse_operand(AstFile *f, bool lhs) { syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); } no_nil = true; - } else { + } else if (tag.string == "maybe") { + if (maybe) { + syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); + } + maybe = true; + }else { syntax_error(tag, "Invalid union tag '#%.*s'", LIT(tag.string)); } } + if (no_nil && maybe) { + syntax_error(f->curr_token, "#maybe and #no_nil cannot be applied together"); + } + Token where_token = {}; Array where_clauses = {}; @@ -2150,7 +2161,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { Token close = expect_token(f, Token_CloseBrace); - return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, where_token, where_clauses); + return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, maybe, where_token, where_clauses); } break; case Token_enum: { @@ -2350,6 +2361,12 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { operand = ast_type_assertion(f, operand, token, type); } break; + case Token_Question: { + Token question = expect_token(f, Token_Question); + Ast *type = ast_unary_expr(f, question, nullptr); + operand = ast_type_assertion(f, operand, token, type); + } break; + default: syntax_error(f->curr_token, "Expected a selector"); advance_token(f); diff --git a/src/parser.hpp b/src/parser.hpp index 983db1042..cdfd4eba1 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -515,6 +515,7 @@ AST_KIND(_TypeBegin, "", bool) \ Array variants; \ Ast *polymorphic_params; \ Ast * align; \ + bool maybe; \ bool no_nil; \ Token where_token; \ Array where_clauses; \ diff --git a/src/types.cpp b/src/types.cpp index e120c77c0..d2a040b0b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -152,6 +152,7 @@ struct TypeUnion { Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; bool no_nil; + bool maybe; bool is_polymorphic; bool is_poly_specialized; }; @@ -1219,6 +1220,32 @@ bool is_type_map(Type *t) { return t->kind == Type_Map; } +bool is_type_union_maybe_pointer(Type *t) { + t = base_type(t); + if (t->kind == Type_Union && t->Union.maybe) { + if (t->Union.variants.count == 1) { + return is_type_pointer(t->Union.variants[0]); + } + } + return false; +} + + +bool is_type_union_maybe_pointer_original_alignment(Type *t) { + t = base_type(t); + if (t->kind == Type_Union && t->Union.maybe) { + if (t->Union.variants.count == 1) { + Type *v = t->Union.variants[0]; + if (is_type_pointer(v)) { + return type_align_of(v) == type_align_of(t); + } + } + } + return false; +} + + + bool is_type_integer_endian_big(Type *t) { t = core_type(t); @@ -2024,6 +2051,7 @@ i64 union_tag_size(Type *u) { Type *union_tag_type(Type *u) { i64 s = union_tag_size(u); switch (s) { + case 0: return t_u8; case 1: return t_u8; case 2: return t_u16; case 4: return t_u32; @@ -2934,14 +2962,23 @@ i64 type_size_of_internal(Type *t, TypePath *path) { } } - // NOTE(bill): Align to tag - i64 tag_size = union_tag_size(t); - i64 size = align_formula(max, tag_size); - // NOTE(bill): Calculate the padding between the common fields and the tag - t->Union.tag_size = tag_size; - t->Union.variant_block_size = size - field_size; + i64 size = 0; - return align_formula(size + tag_size, align); + if (is_type_union_maybe_pointer(t)) { + size = max; + t->Union.tag_size = 0; + t->Union.variant_block_size = size; + } else { + // NOTE(bill): Align to tag + i64 tag_size = union_tag_size(t); + size = align_formula(max, tag_size); + // NOTE(bill): Calculate the padding between the common fields and the tag + t->Union.tag_size = tag_size; + t->Union.variant_block_size = size - field_size; + + size += tag_size; + } + return align_formula(size, align); } break;