Appearance
| 1 | namespace IR is | |
| 2 | use System.Reflection.Metadata.ILOpCode | |
| 3 | use IO.Std | |
| 4 | ||
| 5 | use Semantic.Types.Type | |
| 6 | use NAMED_TYPE = Semantic.Types.NAMED | |
| 7 | ||
| 8 | // How a value becomes a decimal, or stops being one. `is_identity` | |
| 9 | // is the decimal-to-decimal case, where nothing has to happen; | |
| 10 | // otherwise the conversion is one static method on `System.Decimal` | |
| 11 | // and these are the types it goes between. | |
| 12 | class DECIMAL_CONVERSION( | |
| 13 | is_identity: bool, | |
| 14 | method_name: string, | |
| 15 | parameter_type: Type, | |
| 16 | return_type: Type | |
| 17 | ) | |
| 18 | ||
| 19 | // The decimal tables are built by complete_initialization, which | |
| 20 | // every entry point calls before reading them. | |
| 21 | @suppress("field-definite-assignment") | |
| 22 | class VALUE_CONVERTER( | |
| 23 | _logger: Logging.Logger, | |
| 24 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup | |
| 25 | ) is | |
| 26 | _instructions: Collections.MAP[string, ILOpCode]? | |
| 27 | ||
| 28 | // System.Decimal has no `conv.*` IL opcode. Conversion to or | |
| 29 | // from decimal is a static call to `op_Implicit` / `op_Explicit` | |
| 30 | // on the Decimal type, overloaded per neighbouring type; this | |
| 31 | // is the set of neighbours those overloads cover. Populated by | |
| 32 | // complete_initialization alongside _instructions. | |
| 33 | _decimal_neighbours: Collections.SET[string] | |
| 34 | _decimal_implicit_sources: Collections.SET[string] | |
| 35 | ||
| 36 | super() | |
| 37 | ||
| 38 | complete_initialization() is | |
| 39 | if _instructions? then | |
| 40 | return | |
| 41 | fi | |
| 42 | ||
| 43 | _instructions = Collections.MAP[string, ILOpCode]() | |
| 44 | ||
| 45 | add_instruction(_innate_symbol_lookup.get_bool_type(), ILOpCode.CONV_U1) | |
| 46 | ||
| 47 | add_instruction(_innate_symbol_lookup.get_ubyte_type(), ILOpCode.CONV_U1) | |
| 48 | add_instruction(_innate_symbol_lookup.get_byte_type(), ILOpCode.CONV_I1) | |
| 49 | ||
| 50 | add_instruction(_innate_symbol_lookup.get_char_type(), ILOpCode.CONV_U2) | |
| 51 | ||
| 52 | add_instruction(_innate_symbol_lookup.get_ushort_type(), ILOpCode.CONV_U2) | |
| 53 | add_instruction(_innate_symbol_lookup.get_short_type(), ILOpCode.CONV_I2) | |
| 54 | ||
| 55 | add_instruction(_innate_symbol_lookup.get_uint_type(), ILOpCode.CONV_U4) | |
| 56 | add_instruction(_innate_symbol_lookup.get_int_type(), ILOpCode.CONV_I4) | |
| 57 | ||
| 58 | add_instruction(_innate_symbol_lookup.get_ulong_type(), ILOpCode.CONV_U8) | |
| 59 | add_instruction(_innate_symbol_lookup.get_long_type(), ILOpCode.CONV_I8) | |
| 60 | ||
| 61 | add_instruction(_innate_symbol_lookup.get_uword_type(), ILOpCode.CONV_U) | |
| 62 | add_instruction(_innate_symbol_lookup.get_word_type(), ILOpCode.CONV_I) | |
| 63 | ||
| 64 | add_instruction(_innate_symbol_lookup.get_single_type(), ILOpCode.CONV_R4) | |
| 65 | add_instruction(_innate_symbol_lookup.get_double_type(), ILOpCode.CONV_R8) | |
| 66 | ||
| 67 | _decimal_neighbours = Collections.SET[string]() | |
| 68 | _decimal_implicit_sources = Collections.SET[string]() | |
| 69 | ||
| 70 | // char is omitted intentionally — Decimal's conversion | |
| 71 | // overloads here are the ones taking or returning a CIL | |
| 72 | // primitive, and char is not among them. | |
| 73 | // System.Decimal does declare op_Implicit(char) and | |
| 74 | // an op_Explicit returning char - TYPE_CASTER.find_user_defined_conversion | |
| 75 | // reaches those through the general methodref machinery | |
| 76 | // instead, so `cast char(d)` and `cast decimal(c)` still | |
| 77 | // work even though this fast-path table doesn't cover them. | |
| 78 | register_decimal_neighbour(_innate_symbol_lookup.get_byte_type(), true) | |
| 79 | register_decimal_neighbour(_innate_symbol_lookup.get_ubyte_type(), true) | |
| 80 | register_decimal_neighbour(_innate_symbol_lookup.get_short_type(), true) | |
| 81 | register_decimal_neighbour(_innate_symbol_lookup.get_ushort_type(), true) | |
| 82 | register_decimal_neighbour(_innate_symbol_lookup.get_int_type(), true) | |
| 83 | register_decimal_neighbour(_innate_symbol_lookup.get_uint_type(), true) | |
| 84 | register_decimal_neighbour(_innate_symbol_lookup.get_long_type(), true) | |
| 85 | register_decimal_neighbour(_innate_symbol_lookup.get_ulong_type(), true) | |
| 86 | register_decimal_neighbour(_innate_symbol_lookup.get_single_type(), false) | |
| 87 | register_decimal_neighbour(_innate_symbol_lookup.get_double_type(), false) | |
| 88 | si | |
| 89 | ||
| 90 | add_instruction(type: Type, op_code: ILOpCode) is | |
| 91 | _instructions![type.symbol.qualified_name] = op_code | |
| 92 | si | |
| 93 | ||
| 94 | register_decimal_neighbour(type: Type, is_implicit_to_decimal: bool) is | |
| 95 | let qn = type.symbol.qualified_name | |
| 96 | _decimal_neighbours.add(qn) | |
| 97 | if is_implicit_to_decimal then | |
| 98 | _decimal_implicit_sources.add(qn) | |
| 99 | fi | |
| 100 | si | |
| 101 | ||
| 102 | get_instruction(type: Type?) -> ILOpCode? is | |
| 103 | complete_initialization() | |
| 104 | ||
| 105 | if !type? then | |
| 106 | return null | |
| 107 | fi | |
| 108 | ||
| 109 | let name = type.symbol.qualified_name | |
| 110 | ||
| 111 | let instructions = _instructions | |
| 112 | ||
| 113 | if instructions? /\ instructions.contains_key(name) then | |
| 114 | return instructions[name] | |
| 115 | fi | |
| 116 | return null | |
| 117 | si | |
| 118 | ||
| 119 | // How a value of `source` on the stack becomes a `target`, when | |
| 120 | // at least one of them is decimal. Null when neither is, or | |
| 121 | // when the pair has no `Decimal::op_Implicit`/`op_Explicit` | |
| 122 | // overload. | |
| 123 | // | |
| 124 | // `is_identity` is the decimal-to-decimal case, which is not a | |
| 125 | // call: the stack stays typed as decimal either way. Otherwise | |
| 126 | // the conversion is that one static method on `System.Decimal`, | |
| 127 | // described by the types it goes between rather than by the IL | |
| 128 | // line that calls it — the caller needs a token for it, and a | |
| 129 | // rendered call is not something a token can be read out of. | |
| 130 | get_decimal_conversion(source: Type?, target: Type?) -> DECIMAL_CONVERSION? is | |
| 131 | complete_initialization() | |
| 132 | ||
| 133 | if !source? \/ !target? then | |
| 134 | return null | |
| 135 | fi | |
| 136 | ||
| 137 | let decimal_type = _innate_symbol_lookup.get_decimal_type() | |
| 138 | ||
| 139 | let source_is_decimal = source.matches(decimal_type) | |
| 140 | let target_is_decimal = target.matches(decimal_type) | |
| 141 | ||
| 142 | if !source_is_decimal /\ !target_is_decimal then | |
| 143 | return null | |
| 144 | fi | |
| 145 | ||
| 146 | if source_is_decimal /\ target_is_decimal then | |
| 147 | return DECIMAL_CONVERSION(true, "", decimal_type, decimal_type) | |
| 148 | fi | |
| 149 | ||
| 150 | if target_is_decimal then | |
| 151 | if !_decimal_neighbours.contains(source.symbol.qualified_name) then | |
| 152 | return null | |
| 153 | fi | |
| 154 | ||
| 155 | return DECIMAL_CONVERSION( | |
| 156 | false, | |
| 157 | if _decimal_implicit_sources.contains(source.symbol.qualified_name) then | |
| 158 | "op_Implicit" | |
| 159 | else | |
| 160 | "op_Explicit" | |
| 161 | fi, | |
| 162 | source, | |
| 163 | decimal_type) | |
| 164 | fi | |
| 165 | ||
| 166 | if !_decimal_neighbours.contains(target.symbol.qualified_name) then | |
| 167 | return null | |
| 168 | fi | |
| 169 | ||
| 170 | return DECIMAL_CONVERSION(false, "op_Explicit", decimal_type, target) | |
| 171 | si | |
| 172 | ||
| 173 | si | |
| 174 | si |