1 module nopromote; 2 3 import std.stdio; 4 import std.traits; 5 import std.format; 6 7 8 private 9 pragma(LDC_inline_ir) 10 R inlineIR(string s, R, P...)(P); 11 12 13 /// Encapsulate a basic type. This wrapper will never return a value larger than the values operated on. 14 /// Otherwise it behaves like the underlying integer using a simple `alias this`. 15 struct NoPromote(T) { 16 enum string SIZE = (){ 17 import std.conv: to; 18 return (T.sizeof*8).to!string; 19 }(); 20 21 static assert (isIntegral!T); 22 T base; 23 alias base this; 24 25 26 this(TT)(TT value) { 27 base = cast(typeof(base)) value; 28 } 29 30 auto opAssign(R)(R value) { 31 base = cast(typeof(base)) value; 32 return this; 33 } 34 35 36 auto opBinary(string op, TT)(const NoPromote!TT rhs) { 37 /// Non commutative operations one might consider prioritizing the left hand side. 38 /// I decided that this should absolutely be the case for shifts. 39 /// The jury is still out on / and %. 40 static if (op == "<<" || op == ">>" || op == ">>>") { 41 return opLLVM!(op)(this, cast(typeof(this)) rhs); 42 } 43 44 /// The next two ensure that the largest type that the operands already share is used. 45 /// It prefers the signed/unsignedness of the left hand side. 46 else static if(typeof(this).sizeof >= typeof(rhs).sizeof) { 47 return opLLVM!(op)(this, cast(typeof(this)) rhs); 48 } 49 else static if(typeof(this).sizeof < typeof(rhs).sizeof) { 50 return opLLVM!(op)(cast(typeof(rhs)) this, rhs); 51 } 52 53 else 54 return opLLVM!(op)(this, rhs); 55 } 56 57 58 auto opBinaryRight(string op, U)(const NoPromote!U lhs) { 59 return lhs.opBinary!(op)(this); 60 } 61 62 63 auto opBinary(string op, R)(const R rhs) { 64 static if (isIntegral!R) { 65 return this.opBinary!(op)(cast(NoPromote!R) rhs); 66 } 67 } 68 69 70 auto opBinaryRight(string op, L)(const L lhs) { 71 static if (isIntegral!L) { 72 return (cast(NoPromote!L) lhs).opBinary!(op)(this); 73 } 74 } 75 76 77 } 78 79 80 /// 8 bit signed integer. 81 alias i8 = NoPromote!byte; 82 /// 8 bit unsigned integer. 83 alias u8 = NoPromote!ubyte; 84 /// 16 bit signed integer. 85 alias i16 = NoPromote!short; 86 /// 16 bit unsigned integer. 87 alias u16 = NoPromote!ushort; 88 /// 32 bit signed integer. 89 alias i32 = NoPromote!int; 90 /// 32 bit unsigned integer. 91 alias u32 = NoPromote!uint; 92 /// 64 bit signed integer. 93 alias i64 = NoPromote!long; 94 /// 64 bit unsigned integer. 95 alias u64 = NoPromote!ulong; 96 97 98 unittest { 99 i8 a = 115; 100 int i = 10; 101 writeln("byte ", typeof(a + 5).stringof); 102 writeln("int ", typeof(a + i).stringof); 103 } 104 105 106 private 107 auto opLLVM(string op, T)(NoPromote!T lhs, NoPromote!T rhs) { 108 enum string OPERATION = (){ 109 static if (isIntegral!T) switch (op) { 110 case "+": return "add"; 111 case "-": return "sub"; 112 case "*": return "mul"; 113 static if (isSigned!T) { 114 case "/": return "sdiv"; 115 case "%": return "srem"; 116 }else{ 117 case "/": return "udiv"; 118 case "%": return "urem"; 119 } 120 case "<<": return "shl"; 121 case ">>": return "lshr"; 122 case ">>>": return "ashr"; 123 case "&": return "and"; 124 case "|": return "or"; 125 case "^": return "xor"; 126 default: assert(0); 127 } 128 static if (isFloatingPoint!T) switch (op) { 129 case "+": return "fadd"; 130 case "-": return "fsub"; 131 case "*": return "fmul"; 132 case "/": return "fdiv"; 133 case "%": return "frem"; 134 // case "==": 135 default: assert(0); 136 } 137 }(); 138 import std.conv: to; 139 static if (isIntegral!T) 140 enum string TYPE = (){ 141 return "i"~(T.sizeof*8).to!string; 142 }(); 143 else 144 enum string TYPE = (){ 145 return T.stringof; 146 }(); 147 148 return inlineIR!(` 149 %r = `~OPERATION~` `~TYPE~` %0, %1 150 ret `~TYPE~` %r 151 `, T)(lhs.base, rhs.base); 152 }