1 /// Copyright: Copyright (c) 2017-2019 Andrey Penechko.
2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
3 /// Authors: Andrey Penechko.
4 module vox.fe.ast.expr.binary_op;
5 
6 import vox.all;
7 
8 enum BinaryOpFlags : ushort
9 {
10 	isAssignment = AstFlags.userFlag << 0,
11 }
12 
13 @(AstType.expr_bin_op)
14 struct BinaryExprNode {
15 	mixin ExpressionNodeData!(AstType.expr_bin_op, 0);
16 	BinOp op;
17 	AstIndex left;
18 	AstIndex right;
19 
20 	bool isAssignment() { return cast(bool)(flags & BinaryOpFlags.isAssignment); }
21 }
22 
23 void print_binary_op(BinaryExprNode* node, ref AstPrintState state)
24 {
25 	if (node.type) state.print("BINOP ", node.type.printer(state.context), " ", node.op);
26 	else state.print("BINOP ", node.op);
27 	print_ast(node.left, state);
28 	print_ast(node.right, state);
29 }
30 
31 void post_clone_binary_op(BinaryExprNode* node, ref CloneState state)
32 {
33 	state.fixAstIndex(node.left);
34 	state.fixAstIndex(node.right);
35 }
36 
37 void name_register_nested_binary_op(BinaryExprNode* node, ref NameRegisterState state) {
38 	node.state = AstNodeState.name_register_nested;
39 	require_name_register(node.left, state);
40 	require_name_register(node.right, state);
41 	node.state = AstNodeState.name_register_nested_done;
42 }
43 
44 void name_resolve_binary_op(BinaryExprNode* node, ref NameResolveState state) {
45 	node.state = AstNodeState.name_resolve;
46 	require_name_resolve(node.left, state);
47 	require_name_resolve(node.right, state);
48 	node.state = AstNodeState.name_resolve_done;
49 }
50 
51 void type_check_binary_op(BinaryExprNode* node, ref TypeCheckState state)
52 {
53 	CompilationContext* c = state.context;
54 
55 	node.state = AstNodeState.type_check;
56 	require_type_check(node.left, state);
57 	require_type_check(node.right, state);
58 	assert(node.left.get_expr_type(c), format("left(%s).type: is null", node.left.astType(c)));
59 	assert(node.right.get_expr_type(c), format("right(%s).type: is null", node.right.astType(c)));
60 
61 	setResultType(node, c);
62 	TypeNode* leftType = node.left.get_expr(c).type.get_type(c);
63 	node.op = selectTypedOpcode(node.op, leftType.isSigned, leftType.isFloat);
64 	node.state = AstNodeState.type_check_done;
65 }
66 
67 ExprValue ir_gen_expr_binary_op(ref IrGenState gen, IrIndex currentBlock, ref IrLabel nextStmt, BinaryExprNode* b)
68 {
69 	CompilationContext* c = gen.context;
70 
71 	if (b.op == BinOp.LOGIC_AND || b.op == BinOp.LOGIC_OR)
72 	{
73 		IrLabel afterChild = IrLabel(currentBlock);
74 		IrIndex irValue = makeBoolValue(gen, cast(ExpressionNode*)b, currentBlock, afterChild);
75 		currentBlock = afterChild.blockIndex;
76 		gen.builder.addJumpToLabel(currentBlock, nextStmt);
77 		return ExprValue(irValue);
78 	}
79 
80 	if (b.isAssignment)
81 	{
82 		IrLabel afterLeft = IrLabel(currentBlock);
83 		ExprValue leftLvalue = ir_gen_expr(gen, b.left, currentBlock, afterLeft);
84 		currentBlock = afterLeft.blockIndex;
85 
86 		IrLabel afterRight = IrLabel(currentBlock);
87 		ExprValue rightLvalue = ir_gen_expr(gen, b.right, currentBlock, afterRight);
88 		currentBlock = afterRight.blockIndex;
89 		IrIndex rightRvalue = rightLvalue.rvalue(gen, b.loc, currentBlock);
90 
91 		ExpressionNode* leftExpr = b.left.get_expr(c);
92 		ExpressionNode* rightExpr = b.right.get_expr(c);
93 
94 		c.assertf(leftLvalue.irValue.isDefined, leftExpr.loc, "%s null IR val", leftExpr.astType);
95 		c.assertf(rightLvalue.irValue.isDefined, rightExpr.loc, "%s null IR val", rightExpr.astType);
96 
97 		if (b.op == BinOp.ASSIGN) {
98 			leftLvalue.store(gen, b.loc, currentBlock, rightRvalue);
99 			gen.builder.addJumpToLabel(currentBlock, nextStmt);
100 			return rightLvalue;
101 		}
102 		else if (b.op == BinOp.PTR_PLUS_INT_ASSIGN)
103 		{
104 			IrIndex leftRvalue = leftLvalue.rvalue(gen, b.loc, currentBlock);
105 			assert(leftExpr.type.get_type(c).isPointer && rightExpr.type.get_type(c).isInteger);
106 			IrIndex irValue = buildGEP(gen, b.loc, currentBlock, leftRvalue, rightRvalue);
107 			leftLvalue.store(gen, b.loc, currentBlock, irValue);
108 			gen.builder.addJumpToLabel(currentBlock, nextStmt);
109 			return ExprValue(irValue);
110 		}
111 		else
112 		{
113 			IrIndex leftRvalue = leftLvalue.rvalue(gen, b.loc, currentBlock);
114 			IrIndex irValue;
115 
116 			if (leftRvalue.isSimpleConstant && rightRvalue.isSimpleConstant)
117 			{
118 				irValue = calcBinOp(binOpAssignToRegularOp(b.op), leftRvalue, rightRvalue, c);
119 			}
120 			else
121 			{
122 				ExtraInstrArgs extra = {
123 					opcode : binOpcode(b.op, b.loc, c),
124 					type : leftExpr.type.gen_ir_type(c),
125 					argSize : leftExpr.type.typeArgSize(c)
126 				};
127 				irValue = gen.builder.emitInstr!(IrOpcode.generic_binary)(
128 					currentBlock, extra, leftRvalue, rightRvalue).result;
129 			}
130 
131 			leftLvalue.store(gen, b.loc, currentBlock, irValue);
132 			gen.builder.addJumpToLabel(currentBlock, nextStmt);
133 			return ExprValue(irValue);
134 		}
135 	}
136 	else
137 	{
138 		IrLabel fake;
139 		IrIndex irValue = visitBinOpImpl!true(gen, currentBlock, nextStmt, fake, b);
140 		assert(fake.numPredecessors == 0);
141 		return ExprValue(irValue);
142 	}
143 }
144 
145 void ir_gen_branch_binary_op(ref IrGenState gen, IrIndex currentBlock, ref IrLabel trueExit, ref IrLabel falseExit, BinaryExprNode* b)
146 {
147 	CompilationContext* c = gen.context;
148 	if (b.isAssignment)
149 	{
150 		c.error(b.loc, "Cannot assign inside condition");
151 	}
152 	if (b.op == BinOp.LOGIC_AND)
153 	{
154 		IrLabel cond2Label = IrLabel(currentBlock);
155 		ir_gen_branch(gen, b.left, currentBlock, cond2Label, falseExit);
156 
157 		if (cond2Label.numPredecessors != 0)
158 		{
159 			IrIndex cond2Block = cond2Label.blockIndex;
160 			gen.builder.sealBlock(cond2Block);
161 			ir_gen_branch(gen, b.right, cond2Block, trueExit, falseExit);
162 		}
163 
164 		return;
165 	}
166 	else if (b.op == BinOp.LOGIC_OR)
167 	{
168 		IrLabel cond2Label = IrLabel(currentBlock);
169 		ir_gen_branch(gen, b.left, currentBlock, trueExit, cond2Label);
170 
171 		if (cond2Label.numPredecessors != 0)
172 		{
173 			IrIndex cond2Block = cond2Label.blockIndex;
174 			gen.builder.sealBlock(cond2Block);
175 			ir_gen_branch(gen, b.right, cond2Block, trueExit, falseExit);
176 		}
177 
178 		return;
179 	}
180 	visitBinOpImpl!false(gen, currentBlock, trueExit, falseExit, b);
181 }
182 
183 // In value mode only uses trueExit as nextStmt
184 IrIndex visitBinOpImpl(bool forValue)(ref IrGenState gen, ref IrIndex currentBlock, ref IrLabel trueExit, ref IrLabel falseExit, BinaryExprNode* b)
185 {
186 	CompilationContext* c = gen.context;
187 
188 	IrLabel afterLeft = IrLabel(currentBlock);
189 	ExprValue leftLvalue = ir_gen_expr(gen, b.left, currentBlock, afterLeft);
190 	currentBlock = afterLeft.blockIndex;
191 
192 	IrLabel afterRight = IrLabel(currentBlock);
193 	ExprValue rightLvalue = ir_gen_expr(gen, b.right, currentBlock, afterRight);
194 	currentBlock = afterRight.blockIndex;
195 
196 	ExpressionNode* leftExpr = b.left.get_expr(c);
197 	ExpressionNode* rightExpr = b.right.get_expr(c);
198 
199 	c.assertf(leftLvalue.irValue.isDefined, leftExpr.loc, "%s null IR val", leftExpr.astType);
200 	c.assertf(rightLvalue.irValue.isDefined, rightExpr.loc, "%s null IR val", rightExpr.astType);
201 
202 	auto leftValue = leftLvalue.rvalue(gen, b.loc, currentBlock);
203 	auto rightValue = rightLvalue.rvalue(gen, b.loc, currentBlock);
204 
205 	// constant folding
206 	if (leftValue.isSimpleConstant && rightValue.isSimpleConstant)
207 	{
208 		IrIndex value = calcBinOp(b.op, leftValue, rightValue, c);
209 		static if (forValue)
210 		{
211 			c.assertf(value.isDefined, b.loc, "%s null IR val", b.astType);
212 			return value;
213 		}
214 		else
215 		{
216 			if (c.constants.get(value).i8)
217 				gen.builder.addJumpToLabel(currentBlock, trueExit);
218 			else
219 				gen.builder.addJumpToLabel(currentBlock, falseExit);
220 			return IrIndex();
221 		}
222 	}
223 
224 	static if (forValue)
225 	{
226 		IrIndex resType = b.type.gen_ir_type(c);
227 		IrIndex irValue;
228 		switch(b.op) with(BinOp)
229 		{
230 			case EQUAL, NOT_EQUAL:
231 			case SGT, SGE, SLT, SLE:
232 			case UGT, UGE, ULT, ULE:
233 			case FGT, FGE, FLT, FLE:
234 				ExtraInstrArgs extra = { cond : convertBinOpToIrCond(b.op), type : resType };
235 				irValue = gen.builder.emitInstr!(IrOpcode.set_binary_cond)(currentBlock, extra, leftValue, rightValue).result;
236 				break;
237 
238 			case PTR_PLUS_INT:
239 				assert(leftExpr.type.get_type(c).isPointer && rightExpr.type.get_type(c).isInteger);
240 				irValue = buildGEP(gen, b.loc, currentBlock, leftValue, rightValue);
241 				break;
242 
243 			case PTR_DIFF:
244 				assert(leftExpr.type.get_type(c).isPointer && rightExpr.type.get_type(c).isPointer);
245 
246 				ExtraInstrArgs extra = { type : resType, argSize : leftExpr.type.typeArgSize(c) };
247 				irValue = gen.builder.emitInstr!(IrOpcode.sub)(currentBlock, extra, leftValue, rightValue).result;
248 
249 				// divide by elem size
250 				TypeNode* baseType = leftExpr.type.get_type(c).as_ptr.base.get_type(c);
251 				uint elemSize = baseType.sizealign(c).size;
252 				if (elemSize == 1 || baseType.isVoid) break;
253 
254 				ExtraInstrArgs extra2 = { type : resType, argSize : leftExpr.type.typeArgSize(c) };
255 				IrIndex elemSizeValue = c.constants.add(resType, elemSize);
256 				irValue = gen.builder.emitInstr!(IrOpcode.sdiv)(currentBlock, extra2, irValue, elemSizeValue).result;
257 				break;
258 
259 			case INT_PLUS, INT_MINUS, INT_SMUL, INT_UMUL, INT_SDIV, INT_UDIV, INT_SREM, INT_UREM:
260 			case FLT_PLUS, FLT_MINUS, FLT_MUL, FLT_DIV:
261 			case SHL, SHR, ASHR, XOR, BITWISE_AND, BITWISE_OR:
262 				ExtraInstrArgs extra = {
263 					opcode : binOpcode(b.op, b.loc, c),
264 					type : resType,
265 					argSize : b.type.typeArgSize(c)
266 				};
267 				irValue = gen.builder.emitInstr!(IrOpcode.generic_binary)(
268 					currentBlock, extra, leftValue, rightValue).result;
269 				break;
270 			default: c.internal_error(b.loc, "Opcode `%s` is not implemented", b.op);
271 		}
272 		c.assertf(irValue.isDefined, b.loc, "%s null IR val", b.astType);
273 		gen.builder.addJumpToLabel(currentBlock, trueExit);
274 		return irValue;
275 	}
276 	else // branch
277 	{
278 		switch(b.op) with(BinOp)
279 		{
280 			case EQUAL, NOT_EQUAL:
281 			case SGT, SGE, SLT, SLE:
282 			case UGT, UGE, ULT, ULE:
283 			case FGT, FGE, FLT, FLE:
284 				auto branch = gen.builder.addBinBranch(
285 					currentBlock, convertBinOpToIrCond(b.op), leftExpr.type.typeArgSize(c),
286 					leftValue, rightValue, trueExit, falseExit);
287 				break;
288 			default: c.internal_error(b.loc, "Opcode `%s` is not implemented", b.op);
289 		}
290 		return IrIndex();
291 	}
292 }
293 
294 IrOpcode binOpcode(BinOp binop, TokenIndex loc, CompilationContext* context)
295 {
296 	switch(binop) with(BinOp)
297 	{
298 		case INT_PLUS, INT_PLUS_ASSIGN:   return IrOpcode.add;
299 		case INT_MINUS, INT_MINUS_ASSIGN: return IrOpcode.sub;
300 		case INT_SMUL, INT_SMUL_ASSIGN:   return IrOpcode.smul;
301 		case INT_UMUL, INT_UMUL_ASSIGN:   return IrOpcode.umul;
302 		case INT_SDIV, INT_SDIV_ASSIGN:   return IrOpcode.sdiv;
303 		case INT_UDIV, INT_UDIV_ASSIGN:   return IrOpcode.udiv;
304 		case INT_SREM, INT_SREM_ASSIGN:   return IrOpcode.srem;
305 		case INT_UREM, INT_UREM_ASSIGN:   return IrOpcode.urem;
306 
307 		case FLT_PLUS, FLT_PLUS_ASSIGN:   return IrOpcode.fadd;
308 		case FLT_MINUS, FLT_MINUS_ASSIGN: return IrOpcode.fsub;
309 		case FLT_DIV, FLT_DIV_ASSIGN:     return IrOpcode.fdiv;
310 		case FLT_MUL, FLT_MUL_ASSIGN:     return IrOpcode.fmul;
311 
312 		case SHL, SHL_ASSIGN: return IrOpcode.shl;
313 		case SHR, SHR_ASSIGN: return IrOpcode.lshr;
314 		case ASHR, ASHR_ASSIGN: return IrOpcode.ashr;
315 		case XOR, XOR_ASSIGN: return IrOpcode.xor;
316 		case BITWISE_AND, BITWISE_AND_ASSIGN: return IrOpcode.and;
317 		case BITWISE_OR, BITWISE_OR_ASSIGN: return IrOpcode.or;
318 		default:
319 			context.internal_error(loc, "assign op %s not implemented", binop);
320 	}
321 }
322 
323 IrIndex calcBinOp(BinOp op, IrIndex left, IrIndex right, CompilationContext* c)
324 {
325 	c.assertf(left.isSimpleConstant, "%s is not a constant", left);
326 	c.assertf(right.isSimpleConstant, "%s is not a constant", right);
327 
328 	IrConstant leftCon = c.constants.get(left);
329 	IrConstant rightCon = c.constants.get(right);
330 
331 	bool is_f64() { return leftCon.type == makeIrType(IrBasicType.f64); }
332 
333 	switch(op)
334 	{
335 		case BinOp.EQUAL:     return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.i64 == rightCon.i64));
336 		case BinOp.NOT_EQUAL: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.i64 != rightCon.i64));
337 
338 		case BinOp.SGT: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.i64 >  rightCon.i64));
339 		case BinOp.SGE: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.i64 >= rightCon.i64));
340 		case BinOp.SLT: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.i64 <  rightCon.i64));
341 		case BinOp.SLE: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.i64 <= rightCon.i64));
342 
343 		case BinOp.UGT: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.u64 >  rightCon.u64));
344 		case BinOp.UGE: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.u64 >= rightCon.u64));
345 		case BinOp.ULT: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.u64 <  rightCon.u64));
346 		case BinOp.ULE: return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.u64 <= rightCon.u64));
347 
348 		case BinOp.FGT: if (is_f64) return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f64 >  rightCon.f64)); return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f32 >  rightCon.f32));
349 		case BinOp.FGE: if (is_f64) return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f64 >= rightCon.f64)); return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f32 >= rightCon.f32));
350 		case BinOp.FLT: if (is_f64) return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f64 <  rightCon.f64)); return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f32 <  rightCon.f32));
351 		case BinOp.FLE: if (is_f64) return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f64 <= rightCon.f64)); return c.constants.add(makeIrType(IrBasicType.i8), cast(ubyte)(leftCon.f32 <= rightCon.f32));
352 
353 		case BinOp.INT_PLUS: return c.constants.add(leftCon.type, leftCon.i64 + rightCon.i64);
354 		case BinOp.FLT_PLUS:
355 			if (is_f64) return c.constants.add(leftCon.f64 + rightCon.f64);
356 			return c.constants.add(leftCon.f32 + rightCon.f32);
357 		case BinOp.INT_MINUS:
358 			return c.constants.add(leftCon.type, leftCon.i64 - rightCon.i64);
359 		case BinOp.FLT_MINUS:
360 			if (is_f64) return c.constants.add(leftCon.f64 - rightCon.f64);
361 			return c.constants.add(leftCon.f32 - rightCon.f32);
362 		case BinOp.INT_SMUL: return c.constants.add(leftCon.type, leftCon.i64 * rightCon.i64);
363 		case BinOp.INT_UMUL: return c.constants.add(leftCon.type, leftCon.u64 * rightCon.u64);
364 		case BinOp.FLT_MUL:
365 			if (is_f64) return c.constants.add(leftCon.f64 * rightCon.f64);
366 			return c.constants.add(leftCon.f32 * rightCon.f32);
367 		case BinOp.INT_SDIV: return c.constants.add(leftCon.type, leftCon.i64 / rightCon.i64);
368 		case BinOp.INT_UDIV: return c.constants.add(leftCon.type, leftCon.u64 / rightCon.u64);
369 		case BinOp.FLT_DIV:
370 			if (is_f64) return c.constants.add(leftCon.f64 / rightCon.f64);
371 			return c.constants.add(leftCon.f32 / rightCon.f32);
372 
373 		case BinOp.INT_SREM: return c.constants.add(leftCon.type, leftCon.i64 % rightCon.i64);
374 		case BinOp.INT_UREM: return c.constants.add(leftCon.type, leftCon.u64 % rightCon.u64);
375 
376 		case BinOp.SHL:
377 			IrBasicType basicType = leftCon.type.basicType(c);
378 			ulong result;
379 			switch(basicType) with(IrBasicType) {
380 				case i8:  result = leftCon.i32 << rightCon.i64; break; // (leftCon.i8  << rightCon.i64 &    0b111)) & 0xFF; break;
381 				case i16: result = leftCon.i32 << rightCon.i64; break; // (leftCon.i16 << rightCon.i64 &   0b1111)) & 0xFFFF; break;
382 				case i32: result = leftCon.i32 << rightCon.i64; break; // (leftCon.i32 << rightCon.i64 &  0b11111)) & 0xFFFF_FFFF; break;
383 				case i64: result = leftCon.i64 << rightCon.i64; break; // (leftCon.i64 << rightCon.i64 & 0b111111)) & 0xFFFF_FFFF_FFFF_FFFF; break;
384 				default: c.internal_error("Invalid constant type %s", basicType);
385 			}
386 			return c.constants.add(leftCon.type, result);
387 		case BinOp.SHR:
388 			IrBasicType basicType = leftCon.type.basicType(c);
389 			ulong result;
390 			switch(basicType) with(IrBasicType) {
391 				case i8:  result = leftCon.i32 >>> rightCon.i64; break; // (leftCon.i8  >>> (rightCon.i64 &    0b111)) & 0xFF; break;
392 				case i16: result = leftCon.i32 >>> rightCon.i64; break; // (leftCon.i16 >>> (rightCon.i64 &   0b1111)) & 0xFFFF; break;
393 				case i32: result = leftCon.i32 >>> rightCon.i64; break; // (leftCon.i32 >>> (rightCon.i64 &  0b11111)) & 0xFFFF_FFFF; break;
394 				case i64: result = leftCon.i64 >>> rightCon.i64; break; // (leftCon.i64 >>> (rightCon.i64 & 0b111111)) & 0xFFFF_FFFF_FFFF_FFFF; break;
395 				default: c.internal_error("Invalid constant type %s", basicType);
396 			}
397 			return c.constants.add(leftCon.type, result);
398 		case BinOp.ASHR:
399 			IrBasicType basicType = leftCon.type.basicType(c);
400 			ulong result;
401 			switch(basicType) with(IrBasicType) {
402 				case i8:  result = leftCon.i32 >> rightCon.i64; break; // (leftCon.i8  >> (rightCon.i64 &    0b111)) & 0xFF; break;
403 				case i16: result = leftCon.i32 >> rightCon.i64; break; // (leftCon.i16 >> (rightCon.i64 &   0b1111)) & 0xFFFF; break;
404 				case i32: result = leftCon.i32 >> rightCon.i64; break; // (leftCon.i32 >> (rightCon.i64 &  0b11111)) & 0xFFFF_FFFF; break;
405 				case i64: result = leftCon.i64 >> rightCon.i64; break; // (leftCon.i64 >> (rightCon.i64 & 0b111111)) & 0xFFFF_FFFF_FFFF_FFFF; break;
406 				default: c.internal_error("Invalid constant type %s", basicType);
407 			}
408 			return c.constants.add(leftCon.type, result);
409 
410 		case BinOp.BITWISE_OR:    return c.constants.add(leftCon.type, leftCon.i64 | rightCon.i64);
411 		case BinOp.BITWISE_AND:   return c.constants.add(leftCon.type, leftCon.i64 & rightCon.i64);
412 		case BinOp.XOR:           return c.constants.add(leftCon.type, leftCon.i64 ^ rightCon.i64);
413 
414 		default: c.internal_error("Opcode `%s` is not implemented", op);
415 	}
416 }
417 
418 IrBinaryCondition convertBinOpToIrCond(BinOp op)
419 {
420 	switch(op) with(BinOp) with(IrBinaryCondition)
421 	{
422 		case EQUAL:         return eq;
423 		case NOT_EQUAL:     return ne;
424 		case SGT:           return sgt;
425 		case SGE:           return sge;
426 		case SLT:           return slt;
427 		case SLE:           return sle;
428 		case UGT:           return ugt;
429 		case UGE:           return uge;
430 		case ULT:           return ult;
431 		case ULE:           return ule;
432 		case FGT:           return fgt;
433 		case FGE:           return fge;
434 		case FLT:           return flt;
435 		case FLE:           return fle;
436 		default: assert(false, "Unexpected BinOp");
437 	}
438 }
439 
440 void setResultType(BinaryExprNode* b, CompilationContext* c)
441 {
442 	AstIndex resType = CommonAstNodes.type_error;
443 	b.type = resType;
444 	AstIndex leftTypeIndex = b.left.get_expr_type(c);
445 	TypeNode* leftType = leftTypeIndex.get_type(c);
446 	AstIndex rightTypeIndex = b.right.get_expr_type(c);
447 	TypeNode* rightType = rightTypeIndex.get_type(c);
448 
449 	if (leftType.astType == AstType.decl_enum) {
450 		leftTypeIndex = leftType.as_enum.memberType;
451 		leftType = leftTypeIndex.get_type(c);
452 	}
453 	if (rightType.astType == AstType.decl_enum) {
454 		rightTypeIndex = rightType.as_enum.memberType;
455 		rightType = rightTypeIndex.get_type(c);
456 	}
457 
458 	if (leftTypeIndex.isErrorType || rightTypeIndex.isErrorType) return;
459 
460 	switch(b.op) with(BinOp)
461 	{
462 		// logic ops. Requires both operands to be bool
463 		case LOGIC_AND, LOGIC_OR:
464 			autoconvToBool(b.left, c);
465 			autoconvToBool(b.right, c);
466 			resType = CommonAstNodes.type_bool;
467 			break;
468 		// logic ops. Requires both operands to be of the same type
469 		case EQUAL, NOT_EQUAL, GENERIC_GREATER, GENERIC_GREATER_EQUAL, GENERIC_LESS, GENERIC_LESS_EQUAL:
470 			if (leftType.isPointer && rightType.isPointer)
471 			{
472 				if (
473 					same_type(leftTypeIndex, rightTypeIndex, c) ||
474 					leftType.as_ptr.isVoidPtr(c) ||
475 					rightType.as_ptr.isVoidPtr(c))
476 				{
477 					resType = CommonAstNodes.type_bool;
478 					break;
479 				}
480 			}
481 
482 			if (autoconvToCommonType(b.left, b.right, c)) {
483 				resType = CommonAstNodes.type_bool;
484 			}
485 			else
486 				c.error(b.loc, "Cannot compare `%s` and `%s`",
487 					leftType.typeName(c),
488 					rightType.typeName(c));
489 			break;
490 
491 		case GENERIC_MINUS:
492 			if (leftType.isPointer && rightType.isPointer) // handle ptr - ptr
493 			{
494 				if (same_type(leftTypeIndex, rightTypeIndex, c))
495 				{
496 					b.op = BinOp.PTR_DIFF;
497 					resType = CommonAstNodes.type_i64;
498 					break;
499 				}
500 				else
501 				{
502 					c.error(b.loc, "cannot subtract pointers to different types: `%s` and `%s`",
503 						leftType.printer(c), rightType.printer(c));
504 					break;
505 				}
506 			} else if (leftType.isPointer && rightType.isInteger) { // handle ptr - int
507 				b.op = BinOp.PTR_PLUS_INT;
508 				(cast(IntLiteralExprNode*)b.right.get_node(c)).negate(b.loc, *c);
509 				resType = leftTypeIndex;
510 				break;
511 			}
512 			goto case GENERIC_DIV;
513 
514 		case GENERIC_PLUS:
515 			// handle int + ptr and ptr + int
516 			if (leftType.isPointer && rightType.isInteger) {
517 				b.op = BinOp.PTR_PLUS_INT;
518 				resType = leftTypeIndex;
519 				break;
520 			} else if (leftType.isInteger && rightType.isPointer) {
521 				b.op = BinOp.PTR_PLUS_INT;
522 				// canonicalize
523 				swap(b.left, b.right);
524 				resType = leftTypeIndex;
525 				break;
526 			}
527 
528 			goto case GENERIC_DIV;
529 
530 		// arithmetic op int float
531 		case GENERIC_DIV, GENERIC_MUL:
532 			if (autoconvToCommonType(b.left, b.right, c))
533 			{
534 				resType = b.left.get_expr_type(c);
535 			}
536 			else
537 			{
538 				c.error(b.loc, "Cannot perform `%s` %s `%s` operation",
539 					leftType.typeName(c), binOpStrings[b.op],
540 					rightType.typeName(c));
541 			}
542 			break;
543 
544 		// integer only
545 		case GENERIC_INT_REM, SHL, SHR, ASHR, BITWISE_AND, BITWISE_OR, XOR:
546 			if (leftType.isInteger && rightType.isInteger && autoconvToCommonType(b.left, b.right, c))
547 			{
548 				resType = b.left.get_expr_type(c);
549 			}
550 			else
551 			{
552 				c.error(b.loc, "Cannot perform `%s` %s `%s` operation",
553 					leftType.typeName(c), binOpStrings[b.op],
554 					rightType.typeName(c));
555 			}
556 			break;
557 
558 		case GENERIC_MINUS_ASSIGN:
559 			if (leftType.isPointer && rightType.isInteger) {
560 				b.op = BinOp.PTR_PLUS_INT_ASSIGN;
561 				(cast(IntLiteralExprNode*)b.right.get_node(c)).negate(b.loc, *c);
562 				resType = leftTypeIndex;
563 				break;
564 			}
565 			goto case BITWISE_AND_ASSIGN;
566 
567 		case GENERIC_PLUS_ASSIGN:
568 			if (leftType.isPointer && rightType.isInteger) {
569 				b.op = BinOp.PTR_PLUS_INT_ASSIGN;
570 				resType = leftTypeIndex;
571 				break;
572 			}
573 			goto case BITWISE_AND_ASSIGN;
574 
575 		case GENERIC_DIV_ASSIGN, GENERIC_MUL_ASSIGN:
576 			bool success = autoconvTo(b.right, leftTypeIndex, c);
577 			if (!success)
578 				c.error(b.loc, "Cannot perform `%s` %s `%s` operation",
579 					leftType.typeName(c), binOpStrings[b.op],
580 					rightType.typeName(c));
581 			resType = CommonAstNodes.type_void;
582 			break;
583 
584 		case BITWISE_AND_ASSIGN, BITWISE_OR_ASSIGN, GENERIC_INT_REM_ASSIGN,
585 			SHL_ASSIGN, SHR_ASSIGN, ASHR_ASSIGN, XOR_ASSIGN:
586 			bool success = leftType.isInteger && rightType.isInteger && autoconvTo(b.right, leftTypeIndex, c);
587 			if (!success)
588 				c.error(b.loc, "Cannot perform `%s` %s `%s` operation",
589 					leftType.typeName(c), binOpStrings[b.op],
590 					rightType.typeName(c));
591 			resType = CommonAstNodes.type_void;
592 			break;
593 
594 		case ASSIGN:
595 			bool success = autoconvTo(b.right, leftTypeIndex, c);
596 			if (!success)
597 				c.error(b.loc, "Cannot perform `%s` %s `%s` operation",
598 					leftType.typeName(c), binOpStrings[b.op],
599 					rightType.typeName(c));
600 			resType = CommonAstNodes.type_void;
601 			break;
602 
603 		default:
604 			c.internal_error(b.loc, "Unimplemented op %s", b.op);
605 	}
606 	assert(resType.isDefined);
607 	b.type = resType;
608 }
609 
610 BinOp selectTypedOpcode(BinOp op, bool isSigned, bool isFloat)
611 {
612 	switch(op) with(BinOp) {
613 		case GENERIC_GREATER:
614 			if (isFloat) return FGT;
615 			if (isSigned) return SGT;
616 			return UGT;
617 		case GENERIC_GREATER_EQUAL:
618 			if (isFloat) return FGE;
619 			if (isSigned) return SGE;
620 			return UGE;
621 		case GENERIC_LESS:
622 			if (isFloat) return FLT;
623 			if (isSigned) return SLT;
624 			return ULT;
625 		case GENERIC_LESS_EQUAL:
626 			if (isFloat) return FLE;
627 			if (isSigned) return SLE;
628 			return ULE;
629 		case GENERIC_INT_REM:
630 			if (isSigned) return INT_SREM;
631 			return INT_UREM;
632 		case GENERIC_PLUS:
633 			if (isFloat) return FLT_PLUS;
634 			return INT_PLUS;
635 		case GENERIC_MINUS:
636 			if (isFloat) return FLT_MINUS;
637 			return INT_MINUS;
638 		case GENERIC_MUL:
639 			if (isFloat) return FLT_MUL;
640 			if (isSigned) return INT_SMUL;
641 			return INT_UMUL;
642 		case GENERIC_DIV:
643 			if (isFloat) return FLT_DIV;
644 			if (isSigned) return INT_SDIV;
645 			return INT_UDIV;
646 		case GENERIC_PLUS_ASSIGN:
647 			if (isFloat) return FLT_PLUS_ASSIGN;
648 			return INT_PLUS_ASSIGN;
649 		case GENERIC_MINUS_ASSIGN:
650 			if (isFloat) return FLT_MINUS_ASSIGN;
651 			return INT_MINUS_ASSIGN;
652 		case GENERIC_MUL_ASSIGN:
653 			if (isFloat) return FLT_MUL_ASSIGN;
654 			if (isSigned) return INT_SMUL_ASSIGN;
655 			return INT_UMUL_ASSIGN;
656 		case GENERIC_DIV_ASSIGN:
657 			if (isFloat) return FLT_DIV_ASSIGN;
658 			if (isSigned) return INT_SDIV_ASSIGN;
659 			return INT_UDIV_ASSIGN;
660 		case GENERIC_INT_REM_ASSIGN:
661 			if (isSigned) return INT_SREM_ASSIGN;
662 			return INT_UREM_ASSIGN;
663 		default: return op;
664 	}
665 }
666 
667 BinOp binOpAssignToRegularOp(BinOp op) {
668 	switch(op) with(BinOp) {
669 		case BITWISE_AND_ASSIGN: return BITWISE_AND;
670 		case BITWISE_OR_ASSIGN: return BITWISE_OR;
671 		case XOR_ASSIGN: return XOR;
672 		case SHL_ASSIGN: return SHL;
673 		case SHR_ASSIGN: return SHR;
674 		case ASHR_ASSIGN: return ASHR;
675 		case INT_PLUS_ASSIGN: return INT_PLUS;
676 		case INT_MINUS_ASSIGN: return INT_MINUS;
677 		case INT_SMUL_ASSIGN: return INT_SMUL;
678 		case INT_UMUL_ASSIGN: return INT_UMUL;
679 		case INT_SDIV_ASSIGN: return INT_SDIV;
680 		case INT_UDIV_ASSIGN: return INT_UDIV;
681 		case INT_SREM_ASSIGN: return INT_SREM;
682 		case INT_UREM_ASSIGN: return INT_UREM;
683 		case FLT_PLUS_ASSIGN: return FLT_PLUS;
684 		case FLT_MINUS_ASSIGN: return FLT_MINUS;
685 		case FLT_MUL_ASSIGN: return FLT_MUL;
686 		case FLT_DIV_ASSIGN: return FLT_DIV;
687 		case PTR_PLUS_INT_ASSIGN: return PTR_PLUS_INT;
688 		default: assert(false);
689 	}
690 }
691 
692 
693 ///
694 string[] binOpStrings = gatherStrings!BinOp;
695 
696 enum BinOp : ubyte {
697 	// logic ops
698 	@("&&") LOGIC_AND,         // &&
699 	@("||") LOGIC_OR,          // ||
700 
701 	// comparisons are converted into IrBinaryCondition, order is important
702 	@("==") EQUAL,             // ==
703 	@("!=") NOT_EQUAL,         // !=
704 
705 	// generic compare
706 	@(">")  GENERIC_GREATER,       // >
707 	@(">=") GENERIC_GREATER_EQUAL, // >=
708 	@("<")  GENERIC_LESS,          // <
709 	@("<=") GENERIC_LESS_EQUAL,    // <=
710 
711 	// integer compare
712 	// signed
713 	@("s>")  SGT,              // >
714 	@("s>=") SGE,              // >=
715 	@("s<")  SLT,              // <
716 	@("s<=") SLE,              // <=
717 
718 	// unsigned
719 	@("u>")  UGT,              // >
720 	@("u>=") UGE,              // >=
721 	@("u<")  ULT,              // <
722 	@("u<=") ULE,              // <=
723 
724 	// float compare
725 	@("f>")  FGT,              // >
726 	@("f>=") FGE,              // >=
727 	@("f<")  FLT,              // <
728 	@("f<=") FLE,              // <=
729 
730 	// arithmetic ops
731 	@("&") BITWISE_AND,        // &
732 	@("|") BITWISE_OR,         // |
733 	@("^") XOR,                // ^
734 
735 	@("<<") SHL,               // <<
736 	@(">>") SHR,               // >>
737 	@(">>>") ASHR,             // >>>
738 
739 	@("+") GENERIC_PLUS,       // +
740 	@("-") GENERIC_MINUS,      // -
741 	@("*") GENERIC_MUL,        // *
742 	@("/") GENERIC_DIV,        // /
743 	@("%") GENERIC_INT_REM,    // %
744 
745 	@("+") FLT_PLUS,           // +
746 	@("-") FLT_MINUS,          // -
747 	@("*") FLT_MUL,            // *
748 	@("/") FLT_DIV,            // /
749 
750 	@("+") INT_PLUS,           // +
751 	@("-") INT_MINUS,          // -
752 	@("*") INT_SMUL,           // *
753 	@("*") INT_UMUL,           // *
754 	@("/") INT_SDIV,           // /
755 	@("/") INT_UDIV,           // /
756 	@("%") INT_SREM,           // %
757 	@("%") INT_UREM,           // %
758 
759 	@("-") PTR_DIFF,           // ptr - ptr
760 	@("+") PTR_PLUS_INT,       // ptr + int and ptr - int
761 
762 	@("=")   ASSIGN,           // =
763 
764 	@("&=")  BITWISE_AND_ASSIGN, // &=
765 	@("|=")  BITWISE_OR_ASSIGN,  // |=
766 	@("^=")  XOR_ASSIGN,         // ^=
767 
768 	@("<<=") SHL_ASSIGN,         // <<=
769 	@(">>=") SHR_ASSIGN,         // >>=
770 	@(">>>=") ASHR_ASSIGN,       // >>>=
771 
772 
773 	@("+=")  GENERIC_PLUS_ASSIGN,    // +=
774 	@("-=")  GENERIC_MINUS_ASSIGN,   // -=
775 	@("*=")  GENERIC_MUL_ASSIGN,     // *=
776 	@("/=")  GENERIC_DIV_ASSIGN,     // /=
777 	@("%=")  GENERIC_INT_REM_ASSIGN, // %=
778 
779 	@("+=")  INT_PLUS_ASSIGN,    // +=
780 	@("-=")  INT_MINUS_ASSIGN,   // -=
781 	@("*=")  INT_SMUL_ASSIGN,    // *=
782 	@("*=")  INT_UMUL_ASSIGN,    // *=
783 	@("/=")  INT_SDIV_ASSIGN,    // /=
784 	@("/=")  INT_UDIV_ASSIGN,    // /=
785 	@("%=")  INT_SREM_ASSIGN,    // %=
786 	@("%=")  INT_UREM_ASSIGN,    // %=
787 
788 	@("+=")  FLT_PLUS_ASSIGN,    // +=
789 	@("-=")  FLT_MINUS_ASSIGN,   // -=
790 	@("*=")  FLT_MUL_ASSIGN,     // *=
791 	@("/=")  FLT_DIV_ASSIGN,     // /=
792 
793 	@("+")   PTR_PLUS_INT_ASSIGN,// ptr -= / += int
794 
795 	// member access
796 	@(".") DOT,                // .
797 }
798 
799 private string[] gatherStrings(alias _enum)()
800 {
801 	string[] res = new string[__traits(allMembers, _enum).length];
802 	foreach (i, m; __traits(allMembers, _enum))
803 	{
804 		res[i] = __traits(getAttributes, __traits(getMember, _enum, m))[0];
805 	}
806 	return res;
807 }