Appearance
| 1 | namespace IR.Emitter is | |
| 2 | use System.Reflection.Metadata.BlobBuilder | |
| 3 | use System.Reflection.Metadata.EntityHandle | |
| 4 | use System.Reflection.Metadata.Ecma335.InstructionEncoder | |
| 5 | use System.Reflection.Metadata.Ecma335.ControlFlowBuilder | |
| 6 | use System.Reflection.Metadata.Ecma335.LabelHandle | |
| 7 | use System.Reflection.Metadata.ILOpCode | |
| 8 | ||
| 9 | use Semantic.Types.Type | |
| 10 | ||
| 11 | // Instruction-level surface for one method body. Backed by SRM's | |
| 12 | // InstructionEncoder; `flush` hands the encoded body to the owning | |
| 13 | // assembly emitter's method body stream and returns the body offset | |
| 14 | // that AddMethodDefinition takes directly (not a handle). | |
| 15 | // | |
| 16 | // Locals are addressed by name here even though the encoded | |
| 17 | // instructions address them by slot index. Names like `result.0` | |
| 18 | // are invented all over generate-il and the IR values, so taking | |
| 19 | // them as given keeps the slot numbering in one place rather than | |
| 20 | // threading it through every site that declares a local. | |
| 21 | class SRM_METHOD_BODY_EMITTER is | |
| 22 | _encoder: InstructionEncoder | |
| 23 | ||
| 24 | _local_slots: Collections.MAP[string, int] | |
| 25 | _local_types: Collections.LIST[Type] | |
| 26 | ||
| 27 | // IR label to the encoder's own label. A branch can name a | |
| 28 | // label the walk has not reached yet - every forward jump does, | |
| 29 | // and a loop's exit is always one - so the handle is defined on | |
| 30 | // first mention from either side and the encoder patches the | |
| 31 | // offsets in when the body is serialized. | |
| 32 | // | |
| 33 | // Keyed on the label's rendered name, which carries its pass as | |
| 34 | // well as its id. The id counter restarts per pass and per | |
| 35 | // class, so compile-expressions and generate-il both issue an id | |
| 36 | // 0; keying on the id alone merges the two into one handle, and | |
| 37 | // a branch then lands wherever the other pass's label was | |
| 38 | // marked. | |
| 39 | _labels: Collections.MAP[string, LabelHandle] | |
| 40 | ||
| 41 | // Protected-region boundaries. SRM takes four labels per | |
| 42 | // region rather than a nesting, so each boundary has to be | |
| 43 | // marked as the walk passes it and held until both ends are | |
| 44 | // known. These labels have no | |
| 45 | // IR.LABEL behind them - nothing branches to them - so they | |
| 46 | // are numbered here rather than sharing `_labels`. | |
| 47 | _region_labels: Collections.LIST[LabelHandle] | |
| 48 | ||
| 49 | // Source positions for this body, in emission order, or null in | |
| 50 | // a build that was not asked for debug information. | |
| 51 | _sequence_points: Collections.LIST[SRM_SEQUENCE_POINT]? | |
| 52 | ||
| 53 | // Open `@IL.output` regions awaiting their end offset (a stack so | |
| 54 | // nested regions close innermost-first), and the completed ranges. | |
| 55 | _il_output_stack: Collections.LIST[(string, int, int)] | |
| 56 | _il_outputs: Collections.LIST[SRM_IL_OUTPUT_RANGE]? | |
| 57 | ||
| 58 | init() is | |
| 59 | _encoder = InstructionEncoder(BlobBuilder(32), ControlFlowBuilder()) | |
| 60 | ||
| 61 | _local_slots = Collections.MAP[string, int]() | |
| 62 | _local_types = Collections.LIST[Type]() | |
| 63 | _labels = Collections.MAP[string, LabelHandle]() | |
| 64 | _region_labels = Collections.LIST[LabelHandle]() | |
| 65 | _il_output_stack = Collections.LIST[(string, int, int)]() | |
| 66 | si | |
| 67 | ||
| 68 | // Marks the current position and returns a handle on it that | |
| 69 | // `add_catch_region` / `add_finally_region` take. An int rather | |
| 70 | // than the SRM label so the walk that decides where regions | |
| 71 | // begin and end needs no metadata types of its own. | |
| 72 | mark_region_boundary() -> int is | |
| 73 | let handle = _encoder.define_label() | |
| 74 | ||
| 75 | _encoder.mark_label(handle) | |
| 76 | ||
| 77 | _region_labels.add(handle) | |
| 78 | ||
| 79 | return _region_labels.count - 1 | |
| 80 | si | |
| 81 | ||
| 82 | // Regions must be added innermost first: ECMA-335 requires the | |
| 83 | // ordering and the runtime relies on it, but nothing validates | |
| 84 | // it, so getting it wrong yields an assembly that loads and | |
| 85 | // then runs the wrong handler. Each `try` adds its own region | |
| 86 | // as it closes, which puts an inner one ahead of the outer one | |
| 87 | // that contains it. | |
| 88 | add_catch_region( | |
| 89 | try_start: int, | |
| 90 | try_end: int, | |
| 91 | handler_start: int, | |
| 92 | handler_end: int, | |
| 93 | catch_type: EntityHandle | |
| 94 | ) is | |
| 95 | // Only called for a method that has a try/catch, so the | |
| 96 | // encoder was built with a control-flow builder. | |
| 97 | _encoder.control_flow_builder!.add_catch_region( | |
| 98 | _region_labels[try_start], | |
| 99 | _region_labels[try_end], | |
| 100 | _region_labels[handler_start], | |
| 101 | _region_labels[handler_end], | |
| 102 | catch_type) | |
| 103 | si | |
| 104 | ||
| 105 | add_finally_region( | |
| 106 | try_start: int, | |
| 107 | try_end: int, | |
| 108 | handler_start: int, | |
| 109 | handler_end: int | |
| 110 | ) is | |
| 111 | // Only called for a method that has a try/finally, so the | |
| 112 | // encoder was built with a control-flow builder. | |
| 113 | _encoder.control_flow_builder!.add_finally_region( | |
| 114 | _region_labels[try_start], | |
| 115 | _region_labels[try_end], | |
| 116 | _region_labels[handler_start], | |
| 117 | _region_labels[handler_end]) | |
| 118 | si | |
| 119 | ||
| 120 | _label(label: IR.LABEL) -> LabelHandle is | |
| 121 | let key = "{label}" | |
| 122 | ||
| 123 | if _labels.contains_key(key) then | |
| 124 | return _labels[key] | |
| 125 | fi | |
| 126 | ||
| 127 | let handle = _encoder.define_label() | |
| 128 | ||
| 129 | _labels[key] = handle | |
| 130 | ||
| 131 | return handle | |
| 132 | si | |
| 133 | ||
| 134 | mark_label(label: IR.LABEL) is | |
| 135 | _encoder.mark_label(_label(label)) | |
| 136 | si | |
| 137 | ||
| 138 | branch(op_code: ILOpCode, label: IR.LABEL) is | |
| 139 | _encoder.branch(op_code, _label(label)) | |
| 140 | si | |
| 141 | ||
| 142 | // Declaring the same name twice is not an error: at least one | |
| 143 | // value declares its temp on both arms of a branch. The first | |
| 144 | // declaration wins, so the slot a later load resolves to is | |
| 145 | // the one already in use. | |
| 146 | declare_local(name: string, type: Type) -> int is | |
| 147 | if _local_slots.contains_key(name) then | |
| 148 | return _local_slots[name] | |
| 149 | fi | |
| 150 | ||
| 151 | let slot = _local_types.count | |
| 152 | ||
| 153 | _local_types.add(type) | |
| 154 | _local_slots[name] = slot | |
| 155 | ||
| 156 | return slot | |
| 157 | si | |
| 158 | ||
| 159 | // A load of a name that was never declared means the value that | |
| 160 | // declares it has no binary branch yet. Failing here names the | |
| 161 | // local, which is a far shorter path to the culprit than the | |
| 162 | // invalid program it would otherwise produce. | |
| 163 | _slot(name: string) -> int is | |
| 164 | assert _local_slots.contains_key(name) else | |
| 165 | "local '{name}' is loaded but never declared" | |
| 166 | ||
| 167 | return _local_slots[name] | |
| 168 | si | |
| 169 | ||
| 170 | has_locals: bool => _local_types.count > 0 | |
| 171 | local_types: Collections.List[Type] => _local_types | |
| 172 | ||
| 173 | ldloc(name: string) is _encoder.load_local(_slot(name)); si | |
| 174 | stloc(name: string) is _encoder.store_local(_slot(name)); si | |
| 175 | ldloca(name: string) is _encoder.load_local_address(_slot(name)); si | |
| 176 | ||
| 177 | ldarg(index: int) is _encoder.load_argument(index); si | |
| 178 | starg(index: int) is _encoder.store_argument(index); si | |
| 179 | ldarga(index: int) is _encoder.load_argument_address(index); si | |
| 180 | ||
| 181 | ldstr(metadata: SRM_ASSEMBLY_EMITTER, value: string) is | |
| 182 | _encoder.load_string(metadata.get_or_add_user_string(value)) | |
| 183 | si | |
| 184 | ||
| 185 | ldc_i4(value: int) is _encoder.load_constant_i4(value); si | |
| 186 | ldc_i8(value: long) is _encoder.load_constant_i8(value); si | |
| 187 | ldc_r4(value: single) is _encoder.load_constant_r4(value); si | |
| 188 | ldc_r8(value: double) is _encoder.load_constant_r8(value); si | |
| 189 | ldnull() is _encoder.op_code(ILOpCode.LDNULL); si | |
| 190 | ||
| 191 | call(target: EntityHandle) is _encoder.call(target); si | |
| 192 | ||
| 193 | // Prefixes the following call with the type its receiver is | |
| 194 | // reached through, which is how a call on a value whose type is | |
| 195 | // a type parameter dispatches without the caller knowing whether | |
| 196 | // that parameter will be a value or a reference type. | |
| 197 | constrained(type: EntityHandle) is | |
| 198 | _encoder.op_code(ILOpCode.CONSTRAINED) | |
| 199 | _encoder.token(type) | |
| 200 | si | |
| 201 | call_virtual(target: EntityHandle) is _encoder.op_code(ILOpCode.CALLVIRT); _encoder.token(target); si | |
| 202 | new_object(constructor: EntityHandle) is _encoder.op_code(ILOpCode.NEWOBJ); _encoder.token(constructor); si | |
| 203 | ||
| 204 | ldfld(`field: EntityHandle) is _field_instruction(ILOpCode.LDFLD, `field); si | |
| 205 | ldflda(`field: EntityHandle) is _field_instruction(ILOpCode.LDFLDA, `field); si | |
| 206 | stfld(`field: EntityHandle) is _field_instruction(ILOpCode.STFLD, `field); si | |
| 207 | ldsfld(`field: EntityHandle) is _field_instruction(ILOpCode.LDSFLD, `field); si | |
| 208 | ldsflda(`field: EntityHandle) is _field_instruction(ILOpCode.LDSFLDA, `field); si | |
| 209 | stsfld(`field: EntityHandle) is _field_instruction(ILOpCode.STSFLD, `field); si | |
| 210 | ||
| 211 | _field_instruction(op_code: ILOpCode, `field: EntityHandle) is | |
| 212 | _encoder.op_code(op_code) | |
| 213 | _encoder.token(`field) | |
| 214 | si | |
| 215 | ||
| 216 | ldtoken(type: EntityHandle) is _encoder.op_code(ILOpCode.LDTOKEN); _encoder.token(type); si | |
| 217 | ||
| 218 | box(type: EntityHandle) is _encoder.op_code(ILOpCode.BOX); _encoder.token(type); si | |
| 219 | unbox_any(type: EntityHandle) is _encoder.op_code(ILOpCode.UNBOX_ANY); _encoder.token(type); si | |
| 220 | cast_class(type: EntityHandle) is _encoder.op_code(ILOpCode.CASTCLASS); _encoder.token(type); si | |
| 221 | is_instance(type: EntityHandle) is _encoder.op_code(ILOpCode.ISINST); _encoder.token(type); si | |
| 222 | ||
| 223 | new_array(element_type: EntityHandle) is | |
| 224 | _encoder.op_code(ILOpCode.NEWARR) | |
| 225 | _encoder.token(element_type) | |
| 226 | si | |
| 227 | ||
| 228 | store_element(element_type: EntityHandle) is | |
| 229 | _encoder.op_code(ILOpCode.STELEM) | |
| 230 | _encoder.token(element_type) | |
| 231 | si | |
| 232 | ||
| 233 | load_element(element_type: EntityHandle) is | |
| 234 | _encoder.op_code(ILOpCode.LDELEM) | |
| 235 | _encoder.token(element_type) | |
| 236 | si | |
| 237 | ||
| 238 | load_object(type: EntityHandle) is _encoder.op_code(ILOpCode.LDOBJ); _encoder.token(type); si | |
| 239 | store_object(type: EntityHandle) is _encoder.op_code(ILOpCode.STOBJ); _encoder.token(type); si | |
| 240 | ||
| 241 | load_function_pointer(method: EntityHandle) is | |
| 242 | _encoder.op_code(ILOpCode.LDFTN) | |
| 243 | _encoder.token(method) | |
| 244 | si | |
| 245 | ||
| 246 | load_virtual_function_pointer(method: EntityHandle) is | |
| 247 | _encoder.op_code(ILOpCode.LDVIRTFTN) | |
| 248 | _encoder.token(method) | |
| 249 | si | |
| 250 | ||
| 251 | op(op_code: ILOpCode) is _encoder.op_code(op_code); si | |
| 252 | ||
| 253 | ret() is _encoder.op_code(ILOpCode.RET); si | |
| 254 | ||
| 255 | // Note that the instructions emitted from here on belong to a | |
| 256 | // new source position. Called once per statement. | |
| 257 | mark_sequence_point(location: Source.LOCATION) is | |
| 258 | if !_sequence_points? then | |
| 259 | _sequence_points = Collections.LIST[SRM_SEQUENCE_POINT]() | |
| 260 | fi | |
| 261 | ||
| 262 | let sequence_points = _sequence_points | |
| 263 | ||
| 264 | let offset = _encoder.offset | |
| 265 | ||
| 266 | // Two statements can start at the same offset when the | |
| 267 | // first emits nothing. A sequence-point record after the | |
| 268 | // first has to advance the offset - a zero delta is the | |
| 269 | // document-change marker - so the later position replaces | |
| 270 | // the earlier rather than being appended after it. | |
| 271 | if sequence_points.count > 0 /\ | |
| 272 | sequence_points[sequence_points.count - 1].il_offset == offset | |
| 273 | then | |
| 274 | sequence_points.remove_at(sequence_points.count - 1) | |
| 275 | fi | |
| 276 | ||
| 277 | sequence_points.add( | |
| 278 | SRM_SEQUENCE_POINT( | |
| 279 | offset, | |
| 280 | IO.Path.get_full_path(location.file_name), | |
| 281 | location.start_line, | |
| 282 | location.start_column, | |
| 283 | location.end_line, | |
| 284 | // A sequence point's end column is exclusive, and a | |
| 285 | // LOCATION's is not. | |
| 286 | location.end_column + 1)) | |
| 287 | si | |
| 288 | ||
| 289 | // An `@IL.output` pragma opens a region whose emitted instructions | |
| 290 | // the IL test runner extracts. Only the byte-offset range is | |
| 291 | // recorded here; the ranges leave with the method as a custom | |
| 292 | // attribute written by SRM_STRUCTURE_WALK._write_method. | |
| 293 | begin_il_output(path: string, sequence: int) is | |
| 294 | _il_output_stack.add((path, _encoder.offset, sequence)) | |
| 295 | si | |
| 296 | ||
| 297 | end_il_output() is | |
| 298 | let top = _il_output_stack[_il_output_stack.count - 1] | |
| 299 | ||
| 300 | _il_output_stack.remove_at(_il_output_stack.count - 1) | |
| 301 | ||
| 302 | if !_il_outputs? then | |
| 303 | _il_outputs = Collections.LIST[SRM_IL_OUTPUT_RANGE]() | |
| 304 | fi | |
| 305 | ||
| 306 | _il_outputs.add( | |
| 307 | SRM_IL_OUTPUT_RANGE(top.`0, top.`1, _encoder.offset, top.`2)) | |
| 308 | si | |
| 309 | ||
| 310 | il_outputs: Collections.List[SRM_IL_OUTPUT_RANGE]? => _il_outputs | |
| 311 | ||
| 312 | flush(assembly_emitter: SRM_ASSEMBLY_EMITTER) -> int is | |
| 313 | let offset = assembly_emitter.add_method_body(_encoder, self) | |
| 314 | ||
| 315 | if let points = _sequence_points then | |
| 316 | assembly_emitter.set_sequence_points(offset, points) | |
| 317 | fi | |
| 318 | ||
| 319 | return offset | |
| 320 | si | |
| 321 | ||
| 322 | // The instruction bytes encoded so far. The body is not complete | |
| 323 | // until it is flushed, which is what fixes branch offsets, so | |
| 324 | // this is what has been emitted rather than what will be run. | |
| 325 | encoded: ubyte[] => _encoder.code_builder.to_array() | |
| 326 | si | |
| 327 | si |