TryEmitter.h (8056B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef frontend_TryEmitter_h 8 #define frontend_TryEmitter_h 9 10 #include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing 11 12 #include <stdint.h> // uint32_t 13 14 #include "frontend/BytecodeControlStructures.h" // TryFinallyControl 15 #include "frontend/BytecodeOffset.h" // BytecodeOffset 16 #include "frontend/JumpList.h" // JumpList, JumpTarget 17 #include "js/UniquePtr.h" // js::UniquePtr 18 19 namespace js { 20 namespace frontend { 21 22 struct BytecodeEmitter; 23 24 // Class for emitting bytecode for blocks like try-catch-finally. 25 // 26 // Usage: (check for the return value is omitted for simplicity) 27 // 28 // `try { try_block } catch (ex) { catch_block }` 29 // TryEmitter tryCatch(this, TryEmitter::Kind::TryCatch, 30 // TryEmitter::ControlKind::Syntactic); 31 // tryCatch.emitTry(); 32 // emit(try_block); 33 // tryCatch.emitCatch(); 34 // emit(catch_block); // Pending exception is on stack 35 // tryCatch.emitEnd(); 36 // 37 // `try { try_block } finally { finally_block }` 38 // TryEmitter tryCatch(this, TryEmitter::Kind::TryFinally, 39 // TryEmitter::ControlKind::Syntactic); 40 // tryCatch.emitTry(); 41 // emit(try_block); 42 // // finally_pos: The "{" character's position in the source code text. 43 // tryCatch.emitFinally(Some(finally_pos)); 44 // emit(finally_block); 45 // tryCatch.emitEnd(); 46 // 47 // `try { try_block } catch (ex) {catch_block} finally { finally_block }` 48 // TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally, 49 // TryEmitter::ControlKind::Syntactic); 50 // tryCatch.emitTry(); 51 // emit(try_block); 52 // tryCatch.emitCatch(); 53 // emit(catch_block); 54 // tryCatch.emitFinally(Some(finally_pos)); 55 // emit(finally_block); 56 // tryCatch.emitEnd(); 57 // 58 class TryEmitter { 59 public: 60 enum class Kind { TryCatch, TryCatchFinally, TryFinally }; 61 62 // Syntactic try-catch-finally and internally used non-syntactic 63 // try-catch-finally behave differently for 2 points. 64 // 65 // The first one is whether TryFinallyControl is used or not. 66 // See the comment for `controlInfo_`. 67 // 68 // The second one is whether the catch and finally blocks handle the frame's 69 // return value. For syntactic try-catch-finally, the bytecode marked with 70 // "*" are emitted to clear return value with `undefined` before the catch 71 // block and the finally block, and also to save/restore the return value 72 // before/after the finally block. Note that these instructions are not 73 // emitted for noScriptRval scripts that don't track the return value. 74 // 75 // JSOp::Try offsetOf(jumpToEnd) 76 // 77 // try_body... 78 // 79 // JSOp::Goto finally 80 // JSOp::JumpTarget 81 // jumpToEnd: 82 // JSOp::Goto end: 83 // 84 // catch: 85 // JSOp::JumpTarget 86 // * JSOp::Undefined 87 // * JSOp::SetRval 88 // 89 // catch_body... 90 // 91 // JSOp::Goto finally 92 // JSOp::JumpTarget 93 // JSOp::Goto end 94 // 95 // finally: 96 // JSOp::JumpTarget 97 // * JSOp::GetRval 98 // * JSOp::Undefined 99 // * JSOp::SetRval 100 // 101 // finally_body... 102 // 103 // * JSOp::SetRval 104 // JSOp::Nop 105 // 106 // end: 107 // JSOp::JumpTarget 108 // 109 // For syntactic try-catch-finally, Syntactic should be used. 110 // For non-syntactic try-catch-finally, NonSyntactic should be used. 111 // For non-syntactic try-catch-finally for Explicit Resource Management 112 // Disposal kind should be used. 113 enum class ControlKind { 114 Syntactic, 115 NonSyntactic, 116 117 // Disposal kind is exactly same in behaviour of Syntactic kind, it is 118 // used enabling try-finally scope for Explicit Resource Management 119 // Proposal. (https://arai-a.github.io/ecma262-compare/?pr=3000) 120 Disposal, 121 }; 122 123 private: 124 BytecodeEmitter* bce_; 125 Kind kind_; 126 ControlKind controlKind_; 127 128 // Tracks jumps to the finally block for later fixup. 129 // 130 // When a finally block is active, non-local jumps (including 131 // jumps-over-catches) result in a goto being written into the bytecode 132 // stream and fixed-up later. 133 // 134 // For non-syntactic try-catch-finally, all that handling is skipped. 135 // The non-syntactic try-catch-finally must: 136 // * have only one catch block 137 // * have JSOp::Goto at the end of catch-block 138 // * have no non-local-jump 139 // * don't use finally block for normal completion of try-block and 140 // catch-block 141 // 142 // Additionally, a finally block may be emitted for non-syntactic 143 // try-catch-finally, even if the kind is TryCatch, because JSOp::Goto is 144 // not emitted. 145 js::UniquePtr<TryFinallyControl> controlInfo_; 146 147 // The stack depth before emitting JSOp::Try. 148 int depth_; 149 150 // The offset of the JSOp::Try op. 151 BytecodeOffset tryOpOffset_; 152 153 // JSOp::JumpTarget after the entire try-catch-finally block. 154 JumpList catchAndFinallyJump_; 155 156 // The offset of JSOp::Goto at the end of the try block. 157 JumpTarget tryEnd_; 158 159 // The offset of JSOp::JumpTarget at the beginning of the finally block. 160 JumpTarget finallyStart_; 161 162 #ifdef DEBUG 163 // The state of this emitter. 164 // 165 // +-------+ emitTry +-----+ emitCatch +-------+ emitEnd +-----+ 166 // | Start |-------->| Try |-+---------->| Catch |-+->+--------->| End | 167 // +-------+ +-----+ | +-------+ | ^ +-----+ 168 // | | | 169 // | +------------------+ +----+ 170 // | | | 171 // | v emitFinally +---------+ | 172 // +->+------------>| Finally |--+ 173 // +---------+ 174 enum class State { 175 // The initial state. 176 Start, 177 178 // After calling emitTry. 179 Try, 180 181 // After calling emitCatch. 182 Catch, 183 184 // After calling emitFinally. 185 Finally, 186 187 // After calling emitEnd. 188 End 189 }; 190 State state_; 191 #endif 192 193 bool hasCatch() const { 194 return kind_ == Kind::TryCatch || kind_ == Kind::TryCatchFinally; 195 } 196 bool hasFinally() const { 197 return kind_ == Kind::TryCatchFinally || kind_ == Kind::TryFinally; 198 } 199 200 bool requiresControlInfo() const { 201 return controlKind_ == ControlKind::Syntactic || 202 controlKind_ == ControlKind::Disposal; 203 } 204 205 BytecodeOffset offsetAfterTryOp() const { 206 return tryOpOffset_ + BytecodeOffsetDiff(JSOpLength_Try); 207 } 208 209 // Jump to the finally block. After the finally block executes, 210 // fall through to the code following the finally block. 211 [[nodiscard]] bool emitJumpToFinallyWithFallthrough(); 212 213 public: 214 TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind); 215 216 // Returns true if catch and finally blocks should handle the frame's 217 // return value. 218 bool shouldUpdateRval() const; 219 220 #ifdef DEBUG 221 bool hasControlInfo(); 222 #endif 223 224 [[nodiscard]] bool emitTry(); 225 226 enum class ExceptionStack : bool { 227 /** 228 * Push only the pending exception value. 229 */ 230 No, 231 232 /** 233 * Push the pending exception value and its stack. 234 */ 235 Yes, 236 }; 237 238 [[nodiscard]] bool emitCatch(ExceptionStack stack = ExceptionStack::No); 239 240 // If `finallyPos` is specified, it's an offset of the finally block's 241 // "{" character in the source code text, to improve line:column number in 242 // the error reporting. 243 // For non-syntactic try-catch-finally, `finallyPos` can be omitted. 244 [[nodiscard]] bool emitFinally( 245 const mozilla::Maybe<uint32_t>& finallyPos = mozilla::Nothing()); 246 247 [[nodiscard]] bool emitEnd(); 248 249 private: 250 [[nodiscard]] bool emitTryEnd(); 251 [[nodiscard]] bool emitCatchEnd(); 252 [[nodiscard]] bool emitFinallyEnd(); 253 }; 254 255 } /* namespace frontend */ 256 } /* namespace js */ 257 258 #endif /* frontend_TryEmitter_h */