Explorar o código

Merge pull request #5556 from odin-lang/bill/division-by-zero

Define Integer Division By Zero
gingerBill hai 1 mes
pai
achega
1524b40bc9
Modificáronse 6 ficheiros con 431 adicións e 46 borrados
  1. 24 0
      src/build_settings.cpp
  2. 73 2
      src/check_expr.cpp
  3. 261 27
      src/llvm_backend_expr.cpp
  4. 10 4
      src/llvm_backend_proc.cpp
  5. 32 1
      src/main.cpp
  6. 31 12
      src/parser.cpp

+ 24 - 0
src/build_settings.cpp

@@ -352,12 +352,28 @@ u64 get_vet_flag_from_name(String const &name) {
 enum OptInFeatureFlags : u64 {
 	OptInFeatureFlag_NONE            = 0,
 	OptInFeatureFlag_DynamicLiterals = 1u<<0,
+
+	OptInFeatureFlag_IntegerDivisionByZero_Trap = 1u<<1,
+	OptInFeatureFlag_IntegerDivisionByZero_Zero = 1u<<2,
+	OptInFeatureFlag_IntegerDivisionByZero_Self = 1u<<3,
+
+	OptInFeatureFlag_IntegerDivisionByZero_ALL = OptInFeatureFlag_IntegerDivisionByZero_Trap|OptInFeatureFlag_IntegerDivisionByZero_Zero|OptInFeatureFlag_IntegerDivisionByZero_Self,
+
 };
 
 u64 get_feature_flag_from_name(String const &name) {
 	if (name == "dynamic-literals") {
 		return OptInFeatureFlag_DynamicLiterals;
 	}
+	if (name == "integer-division-by-zero:trap") {
+		return OptInFeatureFlag_IntegerDivisionByZero_Trap;
+	}
+	if (name == "integer-division-by-zero:zero") {
+		return OptInFeatureFlag_IntegerDivisionByZero_Zero;
+	}
+	if (name == "integer-division-by-zero:self") {
+		return OptInFeatureFlag_IntegerDivisionByZero_Self;
+	}
 	return OptInFeatureFlag_NONE;
 }
 
@@ -404,6 +420,12 @@ String linker_choices[Linker_COUNT] = {
 	str_lit("radlink"),
 };
 
+enum IntegerDivisionByZeroKind : u8 {
+	IntegerDivisionByZero_Trap,
+	IntegerDivisionByZero_Zero,
+	IntegerDivisionByZero_Self,
+};
+
 // This stores the information for the specify architecture of this build
 struct BuildContext {
 	// Constants
@@ -485,6 +507,8 @@ struct BuildContext {
 	bool   keep_object_files;
 	bool   disallow_do;
 
+	IntegerDivisionByZeroKind integer_division_by_zero_behaviour;
+
 	LinkerChoice linker_choice;
 
 	StringSet custom_attributes;

+ 73 - 2
src/check_expr.cpp

@@ -129,6 +129,8 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type
 
 gb_internal bool is_exact_value_zero(ExactValue const &v);
 
+gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(CheckerContext *c, Ast *node);
+
 enum LoadDirectiveResult {
 	LoadDirective_Success  = 0,
 	LoadDirective_Error    = 1,
@@ -4309,7 +4311,25 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ
 			}
 
 			if (fail) {
-				error(y->expr, "Division by zero not allowed");
+				if (is_type_integer(x->type) || (x->mode == Addressing_Constant && x->value.kind == ExactValue_Integer)) {
+					if (check_for_integer_division_by_zero(c, node) != IntegerDivisionByZero_Trap) {
+						// Okay
+						break;
+					}
+				}
+
+				switch (op.kind) {
+				case Token_Mod:
+				case Token_ModMod:
+				case Token_ModEq:
+				case Token_ModModEq:
+					error(y->expr, "Division by zero through '%.*s' not allowed", LIT(token_strings[op.kind]));
+					break;
+				case Token_Quo:
+				case Token_QuoEq:
+					error(y->expr, "Division by zero not allowed");
+					break;
+				}
 				x->mode = Addressing_Invalid;
 				return;
 			}
@@ -4349,7 +4369,43 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ
 			}
 		}
 
-		x->value = exact_binary_operator_value(op.kind, a, b);
+		match_exact_values(&a, &b);
+
+
+		IntegerDivisionByZeroKind zero_behaviour = check_for_integer_division_by_zero(c, node);
+		if (zero_behaviour != IntegerDivisionByZero_Trap &&
+		    b.kind == ExactValue_Integer && big_int_is_zero(&b.value_integer) &&
+		    (op.kind == Token_QuoEq || op.kind == Token_Mod || op.kind == Token_ModMod)) {
+		    	if (op.kind == Token_QuoEq) {
+		    		if (zero_behaviour == IntegerDivisionByZero_Zero) {
+			    		// x/0 == 0
+					x->value = b;
+				} else {
+			    		// x/0 == x
+					x->value = a;
+				}
+			} else {
+		    		/*
+					NOTE(bill): @integer division by zero rules
+
+		    			truncated: r = a - b*trunc(a/b)
+		    			floored:   r = a - b*floor(a/b)
+
+		    			IFF a/0 == 0, then (a%0 == a) or (a%%0 == a)
+		    			IFF a/0 == a, then (a%0 == 0) or (a%%0 == 0)
+		    		*/
+
+		    		if (zero_behaviour == IntegerDivisionByZero_Zero) {
+			    		// x%0 == x
+					x->value = a;
+				} else {
+			    		// x%0 == 0
+					x->value = b;
+				}
+			}
+		} else {
+			x->value = exact_binary_operator_value(op.kind, a, b);
+		}
 
 		if (is_type_typed(x->type)) {
 			if (node != nullptr) {
@@ -9602,6 +9658,21 @@ gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCom
 	return cl->elems.count > 0;
 }
 
+gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(CheckerContext *c, Ast *node) {
+	// TODO(bill): per file `#+feature` flags
+	u64 flags = check_feature_flags(c, node);
+	if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Trap) != 0) {
+		return IntegerDivisionByZero_Trap;
+	}
+	if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Zero) != 0) {
+		return IntegerDivisionByZero_Zero;
+	}
+	if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Self) != 0) {
+		return IntegerDivisionByZero_Self;
+	}
+	return build_context.integer_division_by_zero_behaviour;
+}
+
 gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) {
 	ExprKind kind = Expr_Expr;
 	ast_node(cl, CompoundLit, node);

+ 261 - 27
src/llvm_backend_expr.cpp

@@ -283,6 +283,33 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x,
 	return res;
 }
 
+gb_internal IntegerDivisionByZeroKind lb_check_for_integer_division_by_zero_behaviour(lbProcedure *p) {
+	AstFile *file = nullptr;
+
+	if (p->body && p->body->file()) {
+		file = p->body->file();
+	} else if (p->type_expr && p->type_expr->file()) {
+		file = p->type_expr->file();
+	} else if (p->entity && p->entity->file) {
+		file = p->entity->file;
+	}
+
+	if (file != nullptr && file->feature_flags_set) {
+		u64 flags = file->feature_flags;
+		if (flags & OptInFeatureFlag_IntegerDivisionByZero_Trap) {
+			return IntegerDivisionByZero_Trap;
+		}
+		if (flags & OptInFeatureFlag_IntegerDivisionByZero_Zero) {
+			return IntegerDivisionByZero_Zero;
+		}
+		if (flags & OptInFeatureFlag_IntegerDivisionByZero_Self) {
+			return IntegerDivisionByZero_Self;
+		}
+	}
+	return build_context.integer_division_by_zero_behaviour;
+}
+
+
 gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type, lbValue *res_) {
 	GB_ASSERT(is_type_array_like(type));
 	Type *elem_type = base_array_type(type);
@@ -354,7 +381,6 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu
 			}
 
 		} else {
-
 			switch (op) {
 			case Token_Add:
 				z = LLVMBuildAdd(p->builder, x, y, "");
@@ -366,17 +392,15 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu
 				z = LLVMBuildMul(p->builder, x, y, "");
 				break;
 			case Token_Quo:
-				if (is_type_unsigned(integral_type)) {
-					z = LLVMBuildUDiv(p->builder, x, y, "");
-				} else {
-					z = LLVMBuildSDiv(p->builder, x, y, "");
+				{
+					auto *call = is_type_unsigned(integral_type) ? LLVMBuildUDiv : LLVMBuildSDiv;
+					z = call(p->builder, x, y, "");
 				}
 				break;
 			case Token_Mod:
-				if (is_type_unsigned(integral_type)) {
-					z = LLVMBuildURem(p->builder, x, y, "");
-				} else {
-					z = LLVMBuildSRem(p->builder, x, y, "");
+				{
+					auto *call = is_type_unsigned(integral_type) ? LLVMBuildURem : LLVMBuildSRem;
+					z = call(p->builder, x, y, "");
 				}
 				break;
 			case Token_ModMod:
@@ -1111,6 +1135,229 @@ gb_internal lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue l
 	return {};
 }
 
+gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, LLVMValueRef rhs, bool is_signed) {
+	LLVMTypeRef type = LLVMTypeOf(rhs);
+	GB_ASSERT(LLVMTypeOf(lhs) == type);
+
+	auto *call = is_signed ? LLVMBuildSDiv : LLVMBuildUDiv;
+
+
+	LLVMValueRef incoming_values[2] = {};
+	LLVMBasicBlockRef incoming_blocks[2] = {};
+
+	lbBlock *safe_block      = lb_create_block(p, "div.safe");
+	lbBlock *edge_case_block = lb_create_block(p, "div.edge");
+	lbBlock *done_block      = lb_create_block(p, "div.done");
+
+	LLVMValueRef zero = LLVMConstNull(type);
+	LLVMValueRef dem_check = LLVMBuildICmp(p->builder, LLVMIntNE, rhs, zero, "");
+	lbValue cond = {dem_check, t_untyped_bool};
+
+	lb_emit_if(p, cond, safe_block, edge_case_block);
+
+	lb_start_block(p, safe_block);
+	incoming_values[0] = call(p->builder, lhs, rhs, "");
+
+	lb_emit_jump(p, done_block);
+
+	lb_start_block(p, edge_case_block);
+
+
+	switch (lb_check_for_integer_division_by_zero_behaviour(p))  {
+	case IntegerDivisionByZero_Trap:
+		lb_call_intrinsic(p, "llvm.trap", nullptr, 0, nullptr, 0);
+		LLVMBuildUnreachable(p->builder);
+		break;
+	case IntegerDivisionByZero_Zero:
+		incoming_values[1] = zero;
+		break;
+	case IntegerDivisionByZero_Self:
+		incoming_values[1] = lhs;
+		break;
+	}
+
+	lb_emit_jump(p, done_block);
+	lb_start_block(p, done_block);
+
+	LLVMValueRef res = incoming_values[0];
+
+	switch (lb_check_for_integer_division_by_zero_behaviour(p))  {
+	case IntegerDivisionByZero_Trap:
+	case IntegerDivisionByZero_Self:
+		res = incoming_values[0];
+		break;
+	case IntegerDivisionByZero_Zero:
+		res = LLVMBuildPhi(p->builder, type, "");
+
+		GB_ASSERT(p->curr_block->preds.count >= 2);
+		incoming_blocks[0] = p->curr_block->preds[0]->block;
+		incoming_blocks[1] = p->curr_block->preds[1]->block;
+
+		LLVMAddIncoming(res, incoming_values, incoming_blocks, 2);
+		break;
+	}
+
+	return res;
+}
+
+gb_internal LLVMValueRef lb_integer_division_intrinsics(lbProcedure *p, LLVMValueRef lhs, LLVMValueRef rhs, LLVMValueRef scale, Type *platform_type, char const *name) {
+	LLVMTypeRef type = LLVMTypeOf(rhs);
+	GB_ASSERT(LLVMTypeOf(lhs) == type);
+
+	LLVMValueRef incoming_values[2] = {};
+	LLVMBasicBlockRef incoming_blocks[2] = {};
+
+	lbBlock *safe_block      = lb_create_block(p, "div.safe");
+	lbBlock *edge_case_block = lb_create_block(p, "div.edge");
+	lbBlock *done_block      = lb_create_block(p, "div.done");
+
+	LLVMValueRef zero = LLVMConstNull(type);
+	LLVMValueRef dem_check = LLVMBuildICmp(p->builder, LLVMIntNE, rhs, zero, "");
+	lbValue cond = {dem_check, t_untyped_bool};
+
+	lb_emit_if(p, cond, safe_block, edge_case_block);
+
+	lb_start_block(p, safe_block);
+
+	{
+		LLVMTypeRef types[1] = {lb_type(p->module, platform_type)};
+
+		LLVMValueRef args[3] = {
+			lhs,
+			rhs,
+			scale };
+
+		incoming_values[0] = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types));
+	}
+
+	lb_emit_jump(p, done_block);
+
+	lb_start_block(p, edge_case_block);
+
+
+	switch (lb_check_for_integer_division_by_zero_behaviour(p))  {
+	case IntegerDivisionByZero_Trap:
+		lb_call_intrinsic(p, "llvm.trap", nullptr, 0, nullptr, 0);
+		LLVMBuildUnreachable(p->builder);
+		break;
+	case IntegerDivisionByZero_Zero:
+		incoming_values[1] = zero;
+		break;
+	case IntegerDivisionByZero_Self:
+		incoming_values[1] = lhs;
+		break;
+	}
+
+	lb_emit_jump(p, done_block);
+	lb_start_block(p, done_block);
+
+	LLVMValueRef res = incoming_values[0];
+
+	switch (lb_check_for_integer_division_by_zero_behaviour(p))  {
+	case IntegerDivisionByZero_Trap:
+	case IntegerDivisionByZero_Self:
+		res = incoming_values[0];
+		break;
+	case IntegerDivisionByZero_Zero:
+		res = LLVMBuildPhi(p->builder, type, "");
+
+		GB_ASSERT(p->curr_block->preds.count >= 2);
+		incoming_blocks[0] = p->curr_block->preds[0]->block;
+		incoming_blocks[1] = p->curr_block->preds[1]->block;
+
+		LLVMAddIncoming(res, incoming_values, incoming_blocks, 2);
+		break;
+	}
+
+	return res;
+}
+
+
+gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLVMValueRef rhs, bool is_unsigned, bool is_floored) {
+	LLVMTypeRef type = LLVMTypeOf(rhs);
+	GB_ASSERT(LLVMTypeOf(lhs) == type);
+
+	LLVMValueRef incoming_values[2] = {};
+	LLVMBasicBlockRef incoming_blocks[2] = {};
+
+	lbBlock *safe_block      = lb_create_block(p, "div.safe");
+	lbBlock *edge_case_block = lb_create_block(p, "div.edge");
+	lbBlock *done_block      = lb_create_block(p, "div.done");
+
+	LLVMValueRef zero = LLVMConstNull(type);
+	LLVMValueRef dem_check = LLVMBuildICmp(p->builder, LLVMIntNE, rhs, zero, "");
+	lbValue cond = {dem_check, t_untyped_bool};
+
+	lb_emit_if(p, cond, safe_block, edge_case_block);
+
+	lb_start_block(p, safe_block);
+
+	if (is_floored) { // %%
+		if (is_unsigned) {
+			incoming_values[0] = LLVMBuildURem(p->builder, lhs, rhs, "");
+		} else {
+			LLVMValueRef a = LLVMBuildSRem(p->builder, lhs, rhs, "");
+			LLVMValueRef b = LLVMBuildAdd(p->builder, a, rhs, "");
+			LLVMValueRef c = LLVMBuildSRem(p->builder, b, rhs, "");
+			incoming_values[0] = c;
+		}
+	} else { // %
+		if (is_unsigned) {
+			incoming_values[0] = LLVMBuildURem(p->builder, lhs, rhs, "");
+		} else {
+			incoming_values[0] = LLVMBuildSRem(p->builder, lhs, rhs, "");
+		}
+	}
+
+	lb_emit_jump(p, done_block);
+
+	lb_start_block(p, edge_case_block);
+
+	/*
+		NOTE(bill): @integer division by zero rules
+
+		truncated: r = a - b*trunc(a/b)
+		floored:   r = a - b*floor(a/b)
+
+		IFF a/0 == 0, then (a%0 == a) or (a%%0 == a)
+	*/
+
+	switch (lb_check_for_integer_division_by_zero_behaviour(p))  {
+	case IntegerDivisionByZero_Trap:
+		lb_call_intrinsic(p, "llvm.trap", nullptr, 0, nullptr, 0);
+		LLVMBuildUnreachable(p->builder);
+		break;
+	case IntegerDivisionByZero_Zero:
+		incoming_values[1] = lhs;
+		break;
+	case IntegerDivisionByZero_Self:
+		incoming_values[1] = zero;
+		break;
+	}
+
+	lb_emit_jump(p, done_block);
+	lb_start_block(p, done_block);
+
+	LLVMValueRef res = incoming_values[0];
+
+	switch (lb_check_for_integer_division_by_zero_behaviour(p))  {
+	case IntegerDivisionByZero_Trap:
+	case IntegerDivisionByZero_Self:
+		res = incoming_values[0];
+		break;
+	case IntegerDivisionByZero_Zero:
+		res = LLVMBuildPhi(p->builder, type, "");
+
+		GB_ASSERT(p->curr_block->preds.count >= 2);
+		incoming_blocks[0] = p->curr_block->preds[0]->block;
+		incoming_blocks[1] = p->curr_block->preds[1]->block;
+
+		LLVMAddIncoming(res, incoming_values, incoming_blocks, 2);
+		break;
+	}
+
+	return res;
+}
 
 
 gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type) {
@@ -1350,33 +1597,20 @@ handle_op:;
 		if (is_type_float(integral_type)) {
 			res.value = LLVMBuildFDiv(p->builder, lhs.value, rhs.value, "");
 			return res;
-		} else if (is_type_unsigned(integral_type)) {
-			res.value = LLVMBuildUDiv(p->builder, lhs.value, rhs.value, "");
+		} else {
+			res.value = lb_integer_division(p, lhs.value, rhs.value, !is_type_unsigned(integral_type));
 			return res;
 		}
-		res.value = LLVMBuildSDiv(p->builder, lhs.value, rhs.value, "");
-		return res;
 	case Token_Mod:
 		if (is_type_float(integral_type)) {
 			res.value = LLVMBuildFRem(p->builder, lhs.value, rhs.value, "");
 			return res;
-		} else if (is_type_unsigned(integral_type)) {
-			res.value = LLVMBuildURem(p->builder, lhs.value, rhs.value, "");
-			return res;
 		}
-		res.value = LLVMBuildSRem(p->builder, lhs.value, rhs.value, "");
+		res.value = lb_integer_modulo(p, lhs.value, rhs.value, is_type_unsigned(integral_type), /*is_floored*/false);
 		return res;
 	case Token_ModMod:
-		if (is_type_unsigned(integral_type)) {
-			res.value = LLVMBuildURem(p->builder, lhs.value, rhs.value, "");
-			return res;
-		} else {
-			LLVMValueRef a = LLVMBuildSRem(p->builder, lhs.value, rhs.value, "");
-			LLVMValueRef b = LLVMBuildAdd(p->builder, a, rhs.value, "");
-			LLVMValueRef c = LLVMBuildSRem(p->builder, b, rhs.value, "");
-			res.value = c;
-			return res;
-		}
+		res.value = lb_integer_modulo(p, lhs.value, rhs.value, is_type_unsigned(integral_type), /*is_floored*/true);
+		return res;
 
 	case Token_And:
 		res.value = LLVMBuildAnd(p->builder, lhs.value, rhs.value, "");

+ 10 - 4
src/llvm_backend_proc.cpp

@@ -3302,16 +3302,22 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 			}
 			GB_ASSERT(name != nullptr);
 
-			LLVMTypeRef types[1] = {lb_type(p->module, platform_type)};
 			lbValue res = {};
+			res.type = platform_type;
+
+			if (id == BuiltinProc_fixed_point_div ||
+			    id == BuiltinProc_fixed_point_div_sat) {
+				res.value = lb_integer_division_intrinsics(p, x.value, y.value, scale.value, platform_type, name);
+			} else {
+				LLVMTypeRef types[1] = {lb_type(p->module, platform_type)};
 
-			LLVMValueRef args[3] = {
+				LLVMValueRef args[3] = {
 					x.value,
 					y.value,
 					scale.value };
 
-			res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types));
-			res.type = platform_type;
+				res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types));
+			}
 			return lb_emit_conv(p, res, tv.type);
 		}
 

+ 32 - 1
src/main.cpp

@@ -392,6 +392,8 @@ enum BuildFlagKind {
 
 	BuildFlag_PrintLinkerFlags,
 
+	BuildFlag_IntegerDivisionByZero,
+
 	// internal use only
 	BuildFlag_InternalFastISel,
 	BuildFlag_InternalIgnoreLazy,
@@ -613,6 +615,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 
 	add_flag(&build_flags, BuildFlag_PrintLinkerFlags,        str_lit("print-linker-flags"),        BuildFlagParam_None,    Command_build);
 
+	add_flag(&build_flags, BuildFlag_IntegerDivisionByZero,   str_lit("integer-division-by-zero"),  BuildFlagParam_String, Command__does_check);
+
+
 	add_flag(&build_flags, BuildFlag_InternalFastISel,        str_lit("internal-fast-isel"),        BuildFlagParam_None,    Command_all);
 	add_flag(&build_flags, BuildFlag_InternalIgnoreLazy,      str_lit("internal-ignore-lazy"),      BuildFlagParam_None,    Command_all);
 	add_flag(&build_flags, BuildFlag_InternalIgnoreLLVMBuild, str_lit("internal-ignore-llvm-build"),BuildFlagParam_None,    Command_all);
@@ -1515,7 +1520,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
 							} else if (str_eq_ignore_case(value.value_string, str_lit("unix"))) {
 								build_context.ODIN_ERROR_POS_STYLE = ErrorPosStyle_Unix;
 							} else {
-								gb_printf_err("-error-pos-style options are 'unix', 'odin' and 'default' (odin)\n");
+								gb_printf_err("-error-pos-style options are 'unix', 'odin', and 'default' (odin)\n");
 								bad_flags = true;
 							}
 							break;
@@ -1539,6 +1544,20 @@ gb_internal bool parse_build_flags(Array<String> args) {
 							build_context.print_linker_flags = true;
 							break;
 
+						case BuildFlag_IntegerDivisionByZero:
+							GB_ASSERT(value.kind == ExactValue_String);
+							if (str_eq_ignore_case(value.value_string, "trap")) {
+								build_context.integer_division_by_zero_behaviour = IntegerDivisionByZero_Trap;
+							} else if (str_eq_ignore_case(value.value_string, "zero")) {
+								build_context.integer_division_by_zero_behaviour = IntegerDivisionByZero_Zero;
+							} else if (str_eq_ignore_case(value.value_string, "self")) {
+								build_context.integer_division_by_zero_behaviour = IntegerDivisionByZero_Self;
+							}else {
+								gb_printf_err("-integer-division-by-zero options are 'trap', 'zero', and 'self'.\n");
+								bad_flags = true;
+							}
+							break;
+
 						case BuildFlag_InternalFastISel:
 							build_context.fast_isel = true;
 							break;
@@ -2561,7 +2580,19 @@ gb_internal int print_show_help(String const arg0, String command, String option
 		if (print_flag("-ignore-warnings")) {
 			print_usage_line(2, "Ignores warning messages.");
 		}
+	}
 
+	if (check) {
+		if (print_flag("-integer-division-by-zero:<string>")) {
+			print_usage_line(2, "Specifies the default behaviour for integer division by zero.");
+			print_usage_line(2, "Available Options:");
+			print_usage_line(3, "-integer-division-by-zero:trap        Trap on division/modulo/remainder by zero");
+			print_usage_line(3, "-integer-division-by-zero:zero        x/0 == 0 and x%%0 == x and x%%%%0 == x");
+			print_usage_line(3, "-integer-division-by-zero:self        x/0 == x and x%%0 == 0 and x%%%%0 == 0");
+		}
+	}
+
+	if (check) {
 		if (print_flag("-json-errors")) {
 			print_usage_line(2, "Prints the error messages as json to stderr.");
 		}

+ 31 - 12
src/parser.cpp

@@ -6289,7 +6289,7 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
 	return any_correct;
 }
 
-gb_internal String vet_tag_get_token(String s, String *out) {
+gb_internal String vet_tag_get_token(String s, String *out, bool allow_colon) {
 	s = string_trim_whitespace(s);
 	isize n = 0;
 	while (n < s.len) {
@@ -6297,7 +6297,7 @@ gb_internal String vet_tag_get_token(String s, String *out) {
 		isize width = utf8_decode(&s[n], s.len-n, &rune);
 		if (n == 0 && rune == '!') {
 
-		} else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != '-') {
+		} else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != '-' && !(allow_colon && rune == ':')) {
 			isize k = gb_max(gb_max(n, width), 1);
 			*out = substring(s, k, s.len);
 			return substring(s, 0, k);
@@ -6323,7 +6323,7 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s) {
 	u64 vet_not_flags = 0;
 
 	while (s.len > 0) {
-		String p = string_trim_whitespace(vet_tag_get_token(s, &s));
+		String p = string_trim_whitespace(vet_tag_get_token(s, &s, /*allow_colon*/false));
 		if (p.len == 0) {
 			break;
 		}
@@ -6391,7 +6391,7 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) {
 	u64 feature_not_flags = 0;
 
 	while (s.len > 0) {
-		String p = string_trim_whitespace(vet_tag_get_token(s, &s));
+		String p = string_trim_whitespace(vet_tag_get_token(s, &s, /*allow_colon*/true));
 		if (p.len == 0) {
 			break;
 		}
@@ -6413,26 +6413,45 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) {
 			} else {
 				feature_flags     |= flag;
 			}
+			if (is_notted) {
+				switch (flag) {
+				case OptInFeatureFlag_IntegerDivisionByZero_Trap:
+				case OptInFeatureFlag_IntegerDivisionByZero_Zero:
+					syntax_error(token_for_pos, "Feature flag does not support notting with '!' - '%.*s'", LIT(p));
+					break;
+				}
+			}
 		} else {
 			ERROR_BLOCK();
 			syntax_error(token_for_pos, "Invalid feature flag name: %.*s", LIT(p));
 			error_line("\tExpected one of the following\n");
 			error_line("\tdynamic-literals\n");
+			error_line("\tinteger-division-by-zero:trap\n");
+			error_line("\tinteger-division-by-zero:zero\n");
+			error_line("\tinteger-division-by-zero:self\n");
 			return OptInFeatureFlag_NONE;
 		}
 	}
 
+	u64 res = OptInFeatureFlag_NONE;
+
 	if (feature_flags == 0 && feature_not_flags == 0) {
-		return OptInFeatureFlag_NONE;
-	}
-	if (feature_flags == 0 && feature_not_flags != 0) {
-		return OptInFeatureFlag_NONE &~ feature_not_flags;
+		res = OptInFeatureFlag_NONE;
+	} else if (feature_flags == 0 && feature_not_flags != 0) {
+		res = OptInFeatureFlag_NONE &~ feature_not_flags;
+	} else if (feature_flags != 0 && feature_not_flags == 0) {
+		res = feature_flags;
+	} else {
+		GB_ASSERT(feature_flags != 0 && feature_not_flags != 0);
+		res = feature_flags &~ feature_not_flags;
 	}
-	if (feature_flags != 0 && feature_not_flags == 0) {
-		return feature_flags;
+
+	u64 idbz_count = gb_count_set_bits(res & OptInFeatureFlag_IntegerDivisionByZero_ALL);
+	if (idbz_count > 1) {
+		syntax_error(token_for_pos, "Only one integer-division-by-zero feature flag can be enabled");
 	}
-	GB_ASSERT(feature_flags != 0 && feature_not_flags != 0);
-	return feature_flags &~ feature_not_flags;
+
+	return res;
 }
 
 gb_internal String dir_from_path(String path) {