Browse Source

Improve termination rules checking for missing `return`; Make diverging procedure `-> !` be terminators

gingerBill 5 years ago
parent
commit
59a0bbb385
7 changed files with 90 additions and 44 deletions
  1. 1 7
      core/mem/allocators.odin
  2. 0 8
      core/sync/atomic.odin
  3. 1 2
      src/check_decl.cpp
  4. 2 2
      src/check_expr.cpp
  5. 78 25
      src/check_stmt.cpp
  6. 4 0
      src/ir.cpp
  7. 4 0
      src/llvm_backend.cpp

+ 1 - 7
core/mem/allocators.odin

@@ -280,7 +280,6 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 
 
 		if !(start <= curr_addr && curr_addr < end) {
 		if !(start <= curr_addr && curr_addr < end) {
 			panic("Out of bounds memory address passed to stack allocator (free)");
 			panic("Out of bounds memory address passed to stack allocator (free)");
-			return nil;
 		}
 		}
 
 
 		if curr_addr >= start+uintptr(s.curr_offset) {
 		if curr_addr >= start+uintptr(s.curr_offset) {
@@ -293,7 +292,6 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 
 
 		if old_offset != int(header.prev_offset) {
 		if old_offset != int(header.prev_offset) {
 			panic("Out of order stack allocator free");
 			panic("Out of order stack allocator free");
-			return nil;
 		}
 		}
 
 
 		s.curr_offset = int(old_offset);
 		s.curr_offset = int(old_offset);
@@ -317,7 +315,6 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		curr_addr := uintptr(old_memory);
 		curr_addr := uintptr(old_memory);
 		if !(start <= curr_addr && curr_addr < end) {
 		if !(start <= curr_addr && curr_addr < end) {
 			panic("Out of bounds memory address passed to stack allocator (resize)");
 			panic("Out of bounds memory address passed to stack allocator (resize)");
-			return nil;
 		}
 		}
 
 
 		if curr_addr >= start+uintptr(s.curr_offset) {
 		if curr_addr >= start+uintptr(s.curr_offset) {
@@ -426,7 +423,6 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 
 
 		if !(start <= curr_addr && curr_addr < end) {
 		if !(start <= curr_addr && curr_addr < end) {
 			panic("Out of bounds memory address passed to stack allocator (free)");
 			panic("Out of bounds memory address passed to stack allocator (free)");
-			return nil;
 		}
 		}
 
 
 		if curr_addr >= start+uintptr(s.offset) {
 		if curr_addr >= start+uintptr(s.offset) {
@@ -455,7 +451,6 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		curr_addr := uintptr(old_memory);
 		curr_addr := uintptr(old_memory);
 		if !(start <= curr_addr && curr_addr < end) {
 		if !(start <= curr_addr && curr_addr < end) {
 			panic("Out of bounds memory address passed to stack allocator (resize)");
 			panic("Out of bounds memory address passed to stack allocator (resize)");
-			return nil;
 		}
 		}
 
 
 		if curr_addr >= start+uintptr(s.offset) {
 		if curr_addr >= start+uintptr(s.offset) {
@@ -511,11 +506,10 @@ dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode
 	case .Alloc:
 	case .Alloc:
 		return dynamic_pool_alloc(pool, size);
 		return dynamic_pool_alloc(pool, size);
 	case .Free:
 	case .Free:
-		panic("Allocator_Mode.Free is not supported for a pool");
+		//
 	case .Free_All:
 	case .Free_All:
 		dynamic_pool_free_all(pool);
 		dynamic_pool_free_all(pool);
 	case .Resize:
 	case .Resize:
-		panic("Allocator_Mode.Resize is not supported for a pool");
 		if old_size >= size {
 		if old_size >= size {
 			return old_memory;
 			return old_memory;
 		}
 		}

+ 0 - 8
core/sync/atomic.odin

@@ -53,7 +53,6 @@ atomic_load :: inline proc(dst: ^$T, $order: Ordering) -> T {
 	case .Acquire_Release: panic("there is no such thing as an acquire/release load");
 	case .Acquire_Release: panic("there is no such thing as an acquire/release load");
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_swap :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 atomic_swap :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
@@ -65,7 +64,6 @@ atomic_swap :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_xchg(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_xchg(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_compare_exchange :: inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) {
 atomic_compare_exchange :: inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) {
@@ -146,7 +144,6 @@ atomic_add :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_add(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_add(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_sub :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 atomic_sub :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
@@ -158,7 +155,6 @@ atomic_sub :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_sub(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_sub(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_and :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 atomic_and :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
@@ -170,7 +166,6 @@ atomic_and :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_and(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_and(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_nand :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 atomic_nand :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
@@ -182,7 +177,6 @@ atomic_nand :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_nand(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_nand(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_or :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 atomic_or :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
@@ -194,7 +188,6 @@ atomic_or :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_or(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_or(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 
 atomic_xor :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 atomic_xor :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
@@ -206,6 +199,5 @@ atomic_xor :: inline proc(dst: ^$T, val: T, $order: Ordering) -> T {
 	case .Sequentially_Consistent: return intrinsics.atomic_xor(dst, val);
 	case .Sequentially_Consistent: return intrinsics.atomic_xor(dst, val);
 	}
 	}
 	panic("unknown order");
 	panic("unknown order");
-	return T{};
 }
 }
 
 

+ 1 - 2
src/check_decl.cpp

@@ -1,4 +1,3 @@
-bool check_is_terminating(Ast *node);
 void check_stmt          (CheckerContext *ctx, Ast *node, u32 flags);
 void check_stmt          (CheckerContext *ctx, Ast *node, u32 flags);
 
 
 // NOTE(bill): 'content_name' is for debugging and error messages
 // NOTE(bill): 'content_name' is for debugging and error messages
@@ -1257,7 +1256,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
 		check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls);
 		check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls);
 
 
 		if (type->Proc.result_count > 0) {
 		if (type->Proc.result_count > 0) {
-			if (!check_is_terminating(body)) {
+			if (!check_is_terminating(body, str_lit(""))) {
 				if (token.kind == Token_Ident) {
 				if (token.kind == Token_Ident) {
 					error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string));
 					error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string));
 				} else {
 				} else {

+ 2 - 2
src/check_expr.cpp

@@ -70,8 +70,8 @@ void     check_entity_decl              (CheckerContext *c, Entity *e, DeclInfo
 void     check_const_decl               (CheckerContext *c, Entity *e, Ast *type_expr, Ast *init_expr, Type *named_type);
 void     check_const_decl               (CheckerContext *c, Entity *e, Ast *type_expr, Ast *init_expr, Type *named_type);
 void     check_proc_body                (CheckerContext *c, Token token, DeclInfo *decl, Type *type, Ast *body);
 void     check_proc_body                (CheckerContext *c, Token token, DeclInfo *decl, Type *type, Ast *body);
 void     update_expr_type               (CheckerContext *c, Ast *e, Type *type, bool final);
 void     update_expr_type               (CheckerContext *c, Ast *e, Type *type, bool final);
-bool     check_is_terminating           (Ast *node);
-bool     check_has_break                (Ast *stmt, bool implicit);
+bool     check_is_terminating           (Ast *node, String const &label);
+bool     check_has_break                (Ast *stmt, String const &label, bool implicit);
 void     check_stmt                     (CheckerContext *c, Ast *node, u32 flags);
 void     check_stmt                     (CheckerContext *c, Ast *node, u32 flags);
 void     check_stmt_list                (CheckerContext *c, Array<Ast *> const &stmts, u32 flags);
 void     check_stmt_list                (CheckerContext *c, Array<Ast *> const &stmts, u32 flags);
 void     check_init_constant            (CheckerContext *c, Entity *e, Operand *operand);
 void     check_init_constant            (CheckerContext *c, Entity *e, Operand *operand);

+ 78 - 25
src/check_stmt.cpp

@@ -1,3 +1,16 @@
+bool is_divigering_stmt(Ast *stmt) {
+	if (stmt->kind != Ast_ExprStmt) {
+		return false;
+	}
+	Ast *expr = unparen_expr(stmt->ExprStmt.expr);
+	if (expr->kind != Ast_CallExpr) {
+		return false;
+	}
+	Type *t = type_of_expr(expr->CallExpr.proc);
+	t = base_type(t);
+	return t->kind == Type_Proc && t->Proc.diverging;
+}
+
 void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags) {
 void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags) {
 	if (stmts.count == 0) {
 	if (stmts.count == 0) {
 		return;
 		return;
@@ -39,6 +52,8 @@ void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags)
 			new_flags |= Stmt_FallthroughAllowed;
 			new_flags |= Stmt_FallthroughAllowed;
 		}
 		}
 
 
+		check_stmt(ctx, n, new_flags);
+
 		if (i+1 < max_non_constant_declaration) {
 		if (i+1 < max_non_constant_declaration) {
 			switch (n->kind) {
 			switch (n->kind) {
 			case Ast_ReturnStmt:
 			case Ast_ReturnStmt:
@@ -48,14 +63,18 @@ void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags)
 			case Ast_BranchStmt:
 			case Ast_BranchStmt:
 				error(n, "Statements after this '%.*s' are never executed", LIT(n->BranchStmt.token.string));
 				error(n, "Statements after this '%.*s' are never executed", LIT(n->BranchStmt.token.string));
 				break;
 				break;
+
+			case Ast_ExprStmt:
+				if (is_divigering_stmt(n)) {
+					error(n, "Statements after a non-diverging procedure call are never executed");
+				}
+				break;
 			}
 			}
 		}
 		}
-
-		check_stmt(ctx, n, new_flags);
 	}
 	}
 }
 }
 
 
-bool check_is_terminating_list(Array<Ast *> const &stmts) {
+bool check_is_terminating_list(Array<Ast *> const &stmts, String const &label) {
 	// Iterate backwards
 	// Iterate backwards
 	for (isize n = stmts.count-1; n >= 0; n--) {
 	for (isize n = stmts.count-1; n >= 0; n--) {
 		Ast *stmt = stmts[n];
 		Ast *stmt = stmts[n];
@@ -63,18 +82,20 @@ bool check_is_terminating_list(Array<Ast *> const &stmts) {
 			// Okay
 			// Okay
 		} else if (stmt->kind == Ast_ValueDecl && !stmt->ValueDecl.is_mutable) {
 		} else if (stmt->kind == Ast_ValueDecl && !stmt->ValueDecl.is_mutable) {
 			// Okay
 			// Okay
+		} else if (is_divigering_stmt(stmt)) {
+			return true;
 		} else {
 		} else {
-			return check_is_terminating(stmt);
+			return check_is_terminating(stmt, label);
 		}
 		}
 	}
 	}
 
 
 	return false;
 	return false;
 }
 }
 
 
-bool check_has_break_list(Array<Ast *> const &stmts, bool implicit) {
+bool check_has_break_list(Array<Ast *> const &stmts, String const &label, bool implicit) {
 	for_array(i, stmts) {
 	for_array(i, stmts) {
 		Ast *stmt = stmts[i];
 		Ast *stmt = stmts[i];
-		if (check_has_break(stmt, implicit)) {
+		if (check_has_break(stmt, label, implicit)) {
 			return true;
 			return true;
 		}
 		}
 	}
 	}
@@ -82,25 +103,56 @@ bool check_has_break_list(Array<Ast *> const &stmts, bool implicit) {
 }
 }
 
 
 
 
-bool check_has_break(Ast *stmt, bool implicit) {
+bool check_has_break(Ast *stmt, String const &label, bool implicit) {
 	switch (stmt->kind) {
 	switch (stmt->kind) {
 	case Ast_BranchStmt:
 	case Ast_BranchStmt:
 		if (stmt->BranchStmt.token.kind == Token_break) {
 		if (stmt->BranchStmt.token.kind == Token_break) {
-			return implicit;
+			if (stmt->BranchStmt.label == nullptr) {
+				return implicit;
+			}
+			if (stmt->BranchStmt.label->kind == Ast_Ident &&
+			    stmt->BranchStmt.label->Ident.token.string == label) {
+				return true;
+			}
 		}
 		}
 		break;
 		break;
+
 	case Ast_BlockStmt:
 	case Ast_BlockStmt:
-		return check_has_break_list(stmt->BlockStmt.stmts, implicit);
+		return check_has_break_list(stmt->BlockStmt.stmts, label, implicit);
 
 
 	case Ast_IfStmt:
 	case Ast_IfStmt:
-		if (check_has_break(stmt->IfStmt.body, implicit) ||
-		    (stmt->IfStmt.else_stmt != nullptr && check_has_break(stmt->IfStmt.else_stmt, implicit))) {
+		if (check_has_break(stmt->IfStmt.body, label, implicit) ||
+		    (stmt->IfStmt.else_stmt != nullptr && check_has_break(stmt->IfStmt.else_stmt, label, implicit))) {
 			return true;
 			return true;
 		}
 		}
 		break;
 		break;
 
 
 	case Ast_CaseClause:
 	case Ast_CaseClause:
-		return check_has_break_list(stmt->CaseClause.stmts, implicit);
+		return check_has_break_list(stmt->CaseClause.stmts, label, implicit);
+
+	case Ast_SwitchStmt:
+		if (label != "" && check_has_break(stmt->SwitchStmt.body, label, false)) {
+			return true;
+		}
+		break;
+
+	case Ast_TypeSwitchStmt:
+		if (label != "" && check_has_break(stmt->TypeSwitchStmt.body, label, false)) {
+			return true;
+		}
+		break;
+
+	case Ast_ForStmt:
+		if (label != "" && check_has_break(stmt->ForStmt.body, label, false)) {
+			return true;
+		}
+		break;
+
+	case Ast_RangeStmt:
+		if (label != "" && check_has_break(stmt->RangeStmt.body, label, false)) {
+			return true;
+		}
+		break;
 	}
 	}
 
 
 	return false;
 	return false;
@@ -110,41 +162,42 @@ bool check_has_break(Ast *stmt, bool implicit) {
 
 
 // NOTE(bill): The last expression has to be a 'return' statement
 // NOTE(bill): The last expression has to be a 'return' statement
 // TODO(bill): This is a mild hack and should be probably handled properly
 // TODO(bill): This is a mild hack and should be probably handled properly
-bool check_is_terminating(Ast *node) {
+bool check_is_terminating(Ast *node, String const &label) {
 	switch (node->kind) {
 	switch (node->kind) {
 	case_ast_node(rs, ReturnStmt, node);
 	case_ast_node(rs, ReturnStmt, node);
 		return true;
 		return true;
 	case_end;
 	case_end;
 
 
 	case_ast_node(bs, BlockStmt, node);
 	case_ast_node(bs, BlockStmt, node);
-		return check_is_terminating_list(bs->stmts);
+		return check_is_terminating_list(bs->stmts, label);
 	case_end;
 	case_end;
 
 
 	case_ast_node(es, ExprStmt, node);
 	case_ast_node(es, ExprStmt, node);
-		return check_is_terminating(es->expr);
+		return check_is_terminating(es->expr, label);
 	case_end;
 	case_end;
 
 
 	case_ast_node(is, IfStmt, node);
 	case_ast_node(is, IfStmt, node);
 		if (is->else_stmt != nullptr) {
 		if (is->else_stmt != nullptr) {
-			if (check_is_terminating(is->body) &&
-			    check_is_terminating(is->else_stmt)) {
+			if (check_is_terminating(is->body, label) &&
+			    check_is_terminating(is->else_stmt, label)) {
 			    return true;
 			    return true;
 		    }
 		    }
 		}
 		}
 	case_end;
 	case_end;
 
 
 	case_ast_node(ws, WhenStmt, node);
 	case_ast_node(ws, WhenStmt, node);
+		// TODO(bill): Is this logic correct for when statements?
 		if (ws->else_stmt != nullptr) {
 		if (ws->else_stmt != nullptr) {
-			if (check_is_terminating(ws->body) &&
-			    check_is_terminating(ws->else_stmt)) {
+			if (check_is_terminating(ws->body, label) &&
+			    check_is_terminating(ws->else_stmt, label)) {
 			    return true;
 			    return true;
 		    }
 		    }
 		}
 		}
 	case_end;
 	case_end;
 
 
 	case_ast_node(fs, ForStmt, node);
 	case_ast_node(fs, ForStmt, node);
-		if (fs->cond == nullptr && !check_has_break(fs->body, true)) {
-			return check_is_terminating(fs->body);
+		if (fs->cond == nullptr && !check_has_break(fs->body, label, true)) {
+			return true;
 		}
 		}
 	case_end;
 	case_end;
 
 
@@ -164,8 +217,8 @@ bool check_is_terminating(Ast *node) {
 			if (cc->list.count == 0) {
 			if (cc->list.count == 0) {
 				has_default = true;
 				has_default = true;
 			}
 			}
-			if (!check_is_terminating_list(cc->stmts) ||
-			    check_has_break_list(cc->stmts, true)) {
+			if (!check_is_terminating_list(cc->stmts, label) ||
+			    check_has_break_list(cc->stmts, label, true)) {
 				return false;
 				return false;
 			}
 			}
 		}
 		}
@@ -180,8 +233,8 @@ bool check_is_terminating(Ast *node) {
 			if (cc->list.count == 0) {
 			if (cc->list.count == 0) {
 				has_default = true;
 				has_default = true;
 			}
 			}
-			if (!check_is_terminating_list(cc->stmts) ||
-			    check_has_break_list(cc->stmts, true)) {
+			if (!check_is_terminating_list(cc->stmts, label) ||
+			    check_has_break_list(cc->stmts, label, true)) {
 				return false;
 				return false;
 			}
 			}
 		}
 		}

+ 4 - 0
src/ir.cpp

@@ -3173,6 +3173,10 @@ irValue *ir_emit_call(irProcedure *p, irValue *value, Array<irValue *> const &ar
 		}
 		}
 	}
 	}
 
 
+	defer (if (pt->Proc.diverging) {
+		ir_emit_unreachable(p);
+	});
+
 	irValue *context_ptr = nullptr;
 	irValue *context_ptr = nullptr;
 	if (pt->Proc.calling_convention == ProcCC_Odin) {
 	if (pt->Proc.calling_convention == ProcCC_Odin) {
 		context_ptr = ir_find_or_generate_context_ptr(p);
 		context_ptr = ir_find_or_generate_context_ptr(p);

+ 4 - 0
src/llvm_backend.cpp

@@ -6902,6 +6902,10 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args,
 		context_ptr = lb_find_or_generate_context_ptr(p);
 		context_ptr = lb_find_or_generate_context_ptr(p);
 	}
 	}
 
 
+	defer (if (pt->Proc.diverging) {
+		LLVMBuildUnreachable(p->builder);
+	});
+
 	set_procedure_abi_types(heap_allocator(), pt);
 	set_procedure_abi_types(heap_allocator(), pt);
 
 
 	bool is_c_vararg = pt->Proc.c_vararg;
 	bool is_c_vararg = pt->Proc.c_vararg;