rpicsim 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,92 +0,0 @@
1
- Preventing call stack overflow
2
- ====
3
-
4
- PIC microcontrollers feature a stack implemented in hardware that keeps track of return addresses for subroutine calls.
5
- Every time a `CALL` instruction is executed, the return address is pushed onto the stack.
6
- Every time a `RETURN` or similar instruction is executed, the return address is popped off of the stack.
7
-
8
- The call stack has a limited depth that depends on the device you are using.
9
- If your program has too many levels of nested subroutines then the call stack could overflow.
10
- Depending on the device, a call stack overflow could cause a reset or incorrect program execution.
11
- Therefore, it is important to avoid a call stack overflow.
12
-
13
- RPicSim can trace all the possible paths of execution for a PIC program in order to calculate what the maximum possible call stack depth is.
14
- You can easily add this to your RSpec tests.
15
- Here is an example:
16
-
17
- !!!ruby
18
- describe "call stack" do
19
- subject(:call_stack_info) do
20
- RPicSim::CallStackInfo.hash_from_program_file(MySim.program_file, [0, 4])
21
- end
22
-
23
- specify "mainline code uses no more than 5 levels" do
24
- call_stack_info[0].max_depth be <= 5
25
- end
26
-
27
- specify "ISR code uses no more than 1 level" do
28
- call_stack_info[4].max_depth.should be <= 1
29
- end
30
- end
31
-
32
- This example is for a PIC10F322 program.
33
- The mainline code's entry point is at address 0, and the first RSpec example makes sure that the maximum depth of subroutine calls in the mainline code is 5.
34
- The firmware uses an interrupt and the interrupt vector is at address 4.
35
- The second RSpec example makes sure that the ISR uses at most one level of the call stack (the ISR itself can only call one subroutine).
36
-
37
- If the two tests above pass, then we can calculate the maximum amount of call stack space that might ever be used. If the mainline code is at its deepest point and it is interrupted by an ISR that happens to reach its deepest point, then the call stack would have:
38
-
39
- * 5 levels used by the mainline code.
40
- * 1 level used by the processor itself to store the address to return to when the interrupt is done.
41
- * 1 level used by the ISR code.
42
-
43
- The means there can be at most 7 things stored on the call stack, which is safely below the PIC10F322's limit of 8.
44
-
45
- Suppose RPicSim tells you that your mainline code could take 5 levels of the stack and you are not sure why.
46
- The {RPicSim::CallStackInfo#worst_case_code_paths_filtered_report} can produce a set of example code paths that demonstrate how the maximum depth could potentially happen.
47
- To see the report, just add a line like this:
48
-
49
- !!!ruby
50
- p call_stack_info[0].worst_case_code_paths_filtered_report
51
-
52
- The report will be a series of code paths that look something like this:
53
-
54
- !!!plain
55
- CodePath:
56
- Instruction(0x0000, GOTO 0x20)
57
- Instruction(0x0024 = start2, CALL 0x40)
58
- Instruction(0x0040 = foo, CALL 0x41)
59
- Instruction(0x0041 = goo, CALL 0x60)
60
- Instruction(0x0060 = hoo, CALL 0x80)
61
- Instruction(0x0080 = ioo, CALL 0x100)
62
- Instruction(0x0100 = joo, CLRF 0x7)
63
-
64
- This example code path shows five CALL instructions that could potentially be nested.
65
-
66
- How it works
67
- ----
68
-
69
- The {RPicSim::ProgramFile} class uses the MPLAB X disassembler to provide a graph with every reachable instruction in the firmware.
70
- The {RPicSim::CallStackInfo} class traverses all possible paths through that graph from a given entry point to calculate the maximum possible call stack depth at every point in the graph.
71
-
72
- Limitations
73
- ----
74
-
75
- The algorithm is pessimistic:
76
-
77
- * It does not try to track the runtime values of any of your program's variables in order to predict which code paths will happen.
78
- * It cannot handle recursive functions because there is no way for it to figure out the maximum level of recursion.
79
- * It does not know when interrupts are enabled.
80
-
81
- All of those things are OK because they can only cause the algorithm to give an answer that is more pessimistic than reality; the reported maximum call stack depth might be higher than what is actually possible.
82
-
83
- However, there are some things that can mess up the algorithm in a bad way and give you incorrect results:
84
-
85
- * If you write to the PC register in order to do a computed jump, the algorithm does not currently detect that and will not correctly consider code paths coming from that instruction.
86
- Be careful about this, because a computed jump might be generated automatically by a C compiler.
87
- * Similarly, it cannot handle jumps on devices that have paged memory. In order to determine where a jump actually goes, it would need to know what page is selected by looking at the history of the program's execution.
88
- * It does not account for the effect of PUSH and POP instructions on the call stack depth.
89
-
90
- This code is not suitable (yet) for any firmware that uses a computed jump, paged program memory, or PUSH or POP instructions.
91
-
92
- This code only checks the hardware call stack; it does not check any kind of data stack that your compiler might use to store the values of local variables.
@@ -1,303 +0,0 @@
1
- require_relative 'search'
2
- require 'set'
3
-
4
- module RPicSim
5
- # This class helps analyze programs to see whether it is possible for them
6
- # to overflow the call stack, which is limited to a small number of levels on
7
- # many PIC microcontrollers.
8
- # It traverses the {Instruction} graph provided by a {ProgramFile}
9
- # from a given root node and for each reachable instruction determines the
10
- # maximum possible call stack depth when that instruction starts executing,
11
- # relative to how deep the call stack depth was when the root instruction
12
- # started.
13
- #
14
- # Here is an example of how you would use this to check the call stack depth
15
- # in a program that has one interrupt vector at address 4 inside an RSpec test.
16
- # Of course, you should adjust the numbers to suit your application:
17
- #
18
- # infos = CallStackInfo.hash_from_program_file(program_file, [0, 4])
19
- # infos[0].max_depth.should <= 5 # main-line code should take at most 5 levels
20
- # infos[4].max_depth.should <= 1 # ISR should take at most 1 stack level
21
- #
22
- # Additionally, it can generate reports of all different ways that the maximum
23
- # call stack depth can be achieved, which can be helpful if you need to reduce
24
- # your maximum stack depth.
25
- class CallStackInfo
26
- # For each of the given root instruction addresses, generates a {CallStackInfo}
27
- # report. Returns the reports in a hash.
28
- #
29
- # @param program_file (ProgramFile)
30
- # @param root_instruction_addresses Array(Integer) The program memory
31
- # addresses of the entry vectors for your program. On a midrange device, these
32
- # are typically 0 for the main-line code and 4 for the interrupt.
33
- # @return [Hash(address => CallStackInfo)]
34
- def self.hash_from_program_file(program_file, root_instruction_addresses)
35
- infos = {}
36
- root_instruction_addresses.each do |addr|
37
- infos[addr] = from_root_instruction(program_file.instruction(addr))
38
- end
39
- infos
40
- end
41
-
42
- # Generates a {CallStackInfo} from the given root instruction.
43
- # This will tell you the maximum value the call stack could get to for a
44
- # program that starts at the given instruction with an empty call stack
45
- # and never gets interrupted.
46
- def self.from_root_instruction(root)
47
- new(root)
48
- end
49
-
50
- # The maximum call stack depth for all the reachable instructions.
51
- # If your program starts executing at the root node and the call stack
52
- # is empty, then (not accounting for interrupts) the call stack will
53
- # never exceed this depth.
54
- #
55
- # A value of 0 means that no subroutine calls a possible; a value of
56
- # 1 means that at most one subroutine call is possible at any given time,
57
- # and so on.
58
- #
59
- # @return (Integer)
60
- attr_reader :max_depth
61
-
62
- # @return (Instruction) The root instruction that this report was generated from.
63
- attr_reader :root
64
-
65
- # Generates a {CallStackInfo} from the given root instruction.
66
- def initialize(root)
67
- @root = root
68
- generate
69
- @max_depth = @max_depths.values.max
70
- end
71
-
72
- private
73
-
74
- def generate
75
- @max_depths = { @root => 0 }
76
- @back_links = Hash.new { [] }
77
- instructions_to_process = [root]
78
- until instructions_to_process.empty?
79
- instruction = instructions_to_process.pop
80
- instruction.transitions.reverse_each do |transition|
81
- ni = transition.next_instruction
82
- prev_depth = @max_depths[ni]
83
- new_depth = @max_depths[instruction] + transition.call_depth_change
84
-
85
- if new_depth > 50
86
- raise "Recursion probably detected. Maximum call depth of #{ni} is at least #{new_depth}."
87
- end
88
-
89
- if prev_depth.nil? || new_depth > prev_depth
90
- @max_depths[ni] = new_depth
91
- instructions_to_process << ni
92
- @back_links[ni] = []
93
- end
94
-
95
- if new_depth == @max_depths[ni]
96
- @back_links[ni] << instruction
97
- end
98
- end
99
- end
100
- end
101
-
102
- public
103
-
104
- # Returns all the {Instruction}s that have the worse case possible call stack depth.
105
- # @return [Array(Instruction)]
106
- def instructions_with_worst_case
107
- @max_depths.select { |instr, depth| depth == @max_depth }.map(&:first).sort
108
- end
109
-
110
- # Returns all the {Instruction}s that are reachable from the given root.
111
- # @return [Array(Instruction)]
112
- def reachable_instructions
113
- @max_depths.keys
114
- end
115
-
116
- # Check the max-depth data hash for consistency.
117
- def double_check!
118
- reachable_instructions.each do |instr|
119
- depth = @max_depths[instr]
120
-
121
- instr.transitions.each do |transition|
122
- next_instruction = transition.next_instruction
123
- if @max_depths[next_instruction] < @max_depths[instr] + transition.call_depth_change
124
- raise 'Call stack info double check failed: %s has max_depth %d and leads (%d) to %s with max_depth %d.' %
125
- [instr, depth, transition.call_depth_change, next_instruction, @max_depths[next_instruction]]
126
- end
127
- end
128
-
129
- end
130
- end
131
-
132
- # Returns an array of {CodePath}s representing all possible ways that the call stack
133
- # could reach the worst-case depth. This will often be a very large amount of data,
134
- # even for a small project.
135
- # @return [Array(CodePath)]
136
- def worst_case_code_paths
137
- instructions_with_worst_case.sort.flat_map do |instruction|
138
- code_paths(instruction)
139
- end
140
- end
141
-
142
- # Returns a filtered version of {#worst_case_code_paths}.
143
- # Filters out any code paths that are just a superset of another code path.
144
- # For each instruction that has a back trace leading to it, it just returns
145
- # the code paths with the smallest number of interesting instructions.
146
- # @return [Array(CodePath)]
147
- def worst_case_code_paths_filtered
148
- all_code_paths = worst_case_code_paths
149
-
150
- # Filter out code path that are just a superset of another code path.
151
- previously_seen_instruction_sequences = Set.new
152
- code_paths = []
153
- all_code_paths.sort_by(&:count).each do |code_path|
154
- seen_before = (1..code_path.instructions.size).any? do |n|
155
- subsequence = code_path.instructions[0, n]
156
- previously_seen_instruction_sequences.include? subsequence
157
- end
158
- if !seen_before
159
- previously_seen_instruction_sequences << code_path.instructions
160
- code_paths << code_path
161
- end
162
- end
163
-
164
- # For each instruction that has a code path leading to it, pick out
165
- # the shortest code path (in terms of interesting instructions).
166
- code_paths = code_paths.group_by { |cp| cp.instructions.last }.map do |instr, paths|
167
- paths.min_by { |cp| cp.interesting_instructions.count }
168
- end
169
-
170
- code_paths
171
- end
172
-
173
- # Returns a nice report string of all the {#worst_case_code_paths_filtered}.
174
- # @return [String]
175
- def worst_case_code_paths_filtered_report
176
- s = ''
177
- worst_case_code_paths_filtered.each do |code_path|
178
- s << code_path.to_s + "\n"
179
- s << "\n"
180
- end
181
- s
182
- end
183
-
184
- # @return [Array(CodePaths)] all the possible code paths that lead to the given instruction.
185
- def code_paths(instruction)
186
- code_paths = []
187
- Search.depth_first_search_simple([[instruction]]) do |instrs|
188
- prev_instrs = @back_links[instrs.first]
189
-
190
- if prev_instrs.empty?
191
- # This must be the root node.
192
- if instrs.first != @root
193
- raise "This instruction is not the root and has no back links: #{instrs}."
194
- end
195
-
196
- code_paths << CodePath.new(instrs)
197
- [] # don't search anything from this node
198
- else
199
- # Investigate all possible code paths that could get to this instruction.
200
- # However, exclude code paths that have the same instruction twice;
201
- # otherwise we get stuck in an infinite loop.
202
- (prev_instrs - instrs).map do |instr|
203
- [instr] + instrs
204
- end
205
- end
206
- end
207
- code_paths
208
- end
209
-
210
- def inspect
211
- "#<#{self.class}:root=#{@root.inspect}>"
212
- end
213
-
214
- # This is a helper class for {CallStackInfo}. It wraps an array of {Instruction} objects
215
- # representing an execution path from one part of the program (usually the entry vector or
216
- # the ISR vector) to another part of the program.
217
- # It has method for reducing this list of instructions by only showing the interesting ones.
218
- class CodePath
219
- include Enumerable
220
-
221
- # An array of {Instruction}s that represents a possible path through the program.
222
- # Each instruction in the list could possibly execute after the previous one.
223
- attr_reader :instructions
224
-
225
- # A new instance that wraps the given instructions.
226
- # @param instructions Array(Instruction) The instructions to wrap.
227
- def initialize(instructions)
228
- @instructions = instructions.freeze
229
- end
230
-
231
- # Iterates over the wrapped instructions by calling <tt>each</tt> on the underlying array.
232
- # Since this class also includes <tt>Enumerable</tt>, it means you can use any of the
233
- # usual methods of Enumerable (e.g. <tt>select</tt>) on this class.
234
- def each(&proc)
235
- instructions.each(&proc)
236
- end
237
-
238
- # Returns the addresses of the underlying instructions.
239
- def addresses
240
- instructions.map(&:address)
241
- end
242
-
243
- # Returns an array of the addresses of the interesting instructions.
244
- def interesting_addresses
245
- interesting_instructions.map(&:address)
246
- end
247
-
248
- # Returns just the interesting instructions, as defined by {#interesting_instruction?}.
249
- #
250
- # @return [Array(Integer)]
251
- def interesting_instructions
252
- @instructions.select.each_with_index do |instruction, index|
253
- next_instruction = @instructions[index + 1]
254
- interesting_instruction?(instruction, next_instruction)
255
- end
256
- end
257
-
258
- # Returns true if the given instruction is interesting. An instruction is
259
- # interesting if you would need to see it in order to understand the path
260
- # program has taken through the code and understand why the call stack
261
- # could reach a certain depth.
262
- #
263
- # * The first and last instructions are interesting.
264
- # * A branch that is taken is interesting.
265
- # * A subroutine call is interesting.
266
- def interesting_instruction?(instruction, next_instruction)
267
- if instruction == @instructions.first || instruction == @instructions.last
268
- return true
269
- end
270
-
271
- transition = instruction.transition_to(next_instruction)
272
-
273
- if transition.call_depth_change >= 1
274
- # This edge represents a function call so it is interesting.
275
- return true
276
- end
277
-
278
- if transition.non_local?
279
- # This edge represents a goto, so that is interesting
280
- # because you need to know which gotos were taken to understand
281
- # a code path. If seeing the skip is annoying we could
282
- # suppress that by adding more information to the edge.
283
- return true
284
- end
285
-
286
- # Everything else is not interesting.
287
-
288
- # We are purposing deciding that skips are not interesting.
289
- # because if you are trying to understand the full code path
290
- # it is obvious whether any given skip was taken or not, as long
291
- # as you know which calls and gotos were taken.
292
-
293
- false
294
- end
295
-
296
- # Returns a multi-line string representing this execution path.
297
- def to_s
298
- "CodePath:\n" +
299
- interesting_instructions.join("\n") + "\n"
300
- end
301
- end
302
- end
303
- end
@@ -1,337 +0,0 @@
1
- module RPicSim
2
- # Instances of this class represent a particular instruction at a particular
3
- # address in program memory. This class takes low-level information about a
4
- # disassembled instruction and produces high-level information about what that
5
- # instruction is and how it behaves.
6
- #
7
- # Instances of this class have links to the other instructions that the instruction
8
- # could lead to, so the instances form a graph. This graph is traversed by
9
- # classes like {CallStackInfo} to get useful information about the firmware.
10
- class Instruction
11
- include Comparable
12
-
13
- # The program memory address of the instruction.
14
- # For PIC18s this will be the byte address.
15
- # For other PIC architectures, it will be the word address.
16
- # @return (Integer)
17
- attr_reader :address
18
-
19
- # The opcode as a capitalized string (e.g. "MOVLW").
20
- # @return (String)
21
- attr_reader :opcode
22
-
23
- # The operands of the instruction as a hash like { "k" => 92 }.
24
- # @return (Hash)
25
- attr_reader :operands
26
-
27
- # The number of program memory address units that this instruction takes.
28
- # The units of this are the same as the units of {#address}.
29
- # @return (Integer)
30
- attr_reader :size
31
-
32
- # A line of assembly language that would represent this instruction.
33
- # For example "GOTO 0x2".
34
- # @return (String)
35
- attr_reader :string
36
-
37
- # Creates a new instruction.
38
- def initialize(mplab_instruction, address, address_increment, instruction_store)
39
- @address = address
40
- @instruction_store = instruction_store
41
- @address_increment = address_increment
42
-
43
- if mplab_instruction == :invalid
44
- @valid = false
45
- @size = @address_increment
46
- @string = '[INVALID]'
47
- return
48
- end
49
-
50
- @valid = true
51
- @opcode = mplab_instruction.opcode
52
- @operands = mplab_instruction.operands
53
- @string = mplab_instruction.instruction_string
54
-
55
- @size = mplab_instruction.compute_size(address_increment)
56
- raise "Invalid size #{@size} for #{inspect}" if ![1, 2, 4].include?(@size)
57
-
58
- properties = Array case mplab_instruction.opcode
59
- when 'ADDFSR'
60
- when 'ADDLW'
61
- when 'ADDWF'
62
- when 'ADDWFC'
63
- when 'ANDLW'
64
- when 'ANDWF'
65
- when 'ASRF'
66
- when 'BC' then [:conditional_relative_branch]
67
- when 'BCF'
68
- when 'BN' then [:conditional_relative_branch]
69
- when 'BNC' then [:conditional_relative_branch]
70
- when 'BNN' then [:conditional_relative_branch]
71
- when 'BNOV' then [:conditional_relative_branch]
72
- when 'BNZ' then [:conditional_relative_branch]
73
- when 'BRA' then [:relative_branch]
74
- when 'BRW' then [:control_ender] # Hard to predict
75
- when 'BSF'
76
- when 'BTG'
77
- when 'BTFSC' then [:conditional_skip]
78
- when 'BTFSS' then [:conditional_skip]
79
- when 'BZ' then [:conditional_relative_branch]
80
- when 'CALL' then [:call]
81
- when 'CALLW' then [:control_ender] # Hard to predict
82
- when 'CPFSEQ' then [:conditional_skip]
83
- when 'CPFSGT' then [:conditional_skip]
84
- when 'CPFSLT' then [:conditional_skip]
85
- when 'CLRF'
86
- when 'CLRW'
87
- when 'CLRWDT'
88
- when 'COMF'
89
- when 'DAW'
90
- when 'DECF'
91
- when 'DECFSZ' then [:conditional_skip]
92
- when 'DCFSNZ' then
93
- when 'GOTO' then [:goto]
94
- when 'INCF'
95
- when 'INCFSZ' then [:conditional_skip]
96
- when 'INFSNZ' then [:conditional_skip]
97
- when 'IORLW'
98
- when 'IORWF'
99
- when 'LFSR'
100
- when 'LSLF'
101
- when 'LSRF'
102
- when 'MOVIW'
103
- when 'MOVWI'
104
- when 'MOVLB'
105
- when 'MOVLP'
106
- when 'MOVLW'
107
- when 'MOVWF'
108
- when 'MOVF'
109
- when 'MOVFF'
110
- when 'MULLW'
111
- when 'MULWF'
112
- when 'NEGF'
113
- when 'NOP'
114
- when 'OPTION'
115
- when 'PUSH'
116
- when 'POP'
117
- when 'RCALL' then [:relative_call]
118
- when 'RESET' then [:control_ender]
119
- when 'RETFIE' then [:control_ender]
120
- when 'RETLW' then [:control_ender]
121
- when 'RETURN' then [:control_ender]
122
- when 'RLCF'
123
- when 'RLF'
124
- when 'RLNCF'
125
- when 'RRCF'
126
- when 'RRF'
127
- when 'RRNCF'
128
- when 'SETF'
129
- when 'SLEEP'
130
- when 'SUBLW'
131
- when 'SUBWF'
132
- when 'SUBWFB'
133
- when 'SWAPF'
134
- when 'TBLRD*'
135
- when 'TBLRD*+'
136
- when 'TBLRD*-'
137
- when 'TBLRD+*'
138
- when 'TBLWT*'
139
- when 'TBLWT*+'
140
- when 'TBLWT*-'
141
- when 'TBLWT+*'
142
- when 'TRIS'
143
- when 'TSTFSZ' then [:conditional_skip]
144
- when 'XORLW'
145
- when 'XORWF'
146
- else
147
- raise "Unrecognized opcode #{mplab_instruction.opcode} " +
148
- "(#{address_description(address)}, operands #{mplab_instruction.operands.inspect})."
149
- end
150
-
151
- modules = {
152
- conditional_skip: ConditionalSkip,
153
- conditional_relative_branch: ConditionalRelativeBranch,
154
- relative_branch: RelativeBranch,
155
- relative_call: RelativeCall,
156
- goto: Goto,
157
- control_ender: ControlEnder,
158
- call: Call,
159
- }
160
-
161
- properties.each do |p|
162
- mod = modules[p]
163
- if !mod
164
- raise ArgumentError, "Invalid property: #{p.inspect}."
165
- end
166
- extend mod
167
- end
168
- end
169
-
170
- # Compares this instruction to another using the addresses. This means you can
171
- # call +.sort+ on an array of instructions to put them in order by address.
172
- def <=>(other)
173
- address <=> other.address
174
- end
175
-
176
- # Human-readable string representation of the instruction.
177
- def to_s
178
- "Instruction(#{@instruction_store.address_description(address)}, #{@string})"
179
- end
180
-
181
- def inspect
182
- "#<#{self.class}:#{@instruction_store.address_description(address)}, #{@string}>"
183
- end
184
-
185
- # Returns info about all the instructions that this instruction could directly lead to
186
- # (not counting interrupts, returns, and not accounting
187
- # at all for what happens after the last word in the main program memory is executed).
188
- # For instructions that pop from the call stack like RETURN and RETFIE, this will be
189
- # the empty array.
190
- # @return [Array(Transition)]
191
- def transitions
192
- @transitions ||= generate_transitions
193
- end
194
-
195
- # Returns the transition from this instruction to the specified instruction
196
- # or nil if no such transition exists.
197
- # @return Transition
198
- def transition_to(instruction)
199
- @transitions.find { |t| t.next_instruction == instruction }
200
- end
201
-
202
- # Returns the addresses of all the instructions this instruction could directly lead to.
203
- # @return [Array(Integer)]
204
- def next_addresses
205
- transitions.map(&:next_address)
206
- end
207
-
208
- # Returns true if this is actually a valid instruction. Invalid
209
- # instructions can occur when disassembling bytes that are not actually
210
- # instructions.
211
- def valid?
212
- @valid
213
- end
214
-
215
- private
216
-
217
- # For certain opcodes, this method gets over-written.
218
- def generate_transitions
219
- [advance(1)]
220
- end
221
-
222
- # Makes a transition representing the default behavior: the microcontroller
223
- # will increment the program counter and execute the next instruction in memory.
224
- def advance(num)
225
- transition(address + num * size)
226
- end
227
-
228
- def transition(addr, attrs = {})
229
- Transition.new(self, addr, @instruction_store, attrs)
230
- end
231
-
232
- private
233
-
234
- # Returns the address indicated by the operand 'k'.
235
- # k is assumed to be a word address and it is assumed to be absolute
236
- # k=0 is word 0 of memory, k=1 is word one.
237
- # We need to multiply by the address increment because on PIC18
238
- # program memory addresses are actually byte-based instead of word-based.
239
- def k_address
240
- operands[:k] * @address_increment
241
- end
242
-
243
- def n_address
244
- address + @address_increment * (operands[:n] + 1)
245
- end
246
-
247
- def relative_k_address
248
- address + @address_increment * (operands[:k] + 1)
249
- end
250
-
251
- def relative_target_address
252
- if operands[:k]
253
- relative_k_address
254
- elsif operands[:n]
255
- n_address
256
- else
257
- raise 'This instruction does not have fields k or n.'
258
- end
259
- end
260
-
261
- ### Modules that modify the behavior of the instruction. ###
262
-
263
- # This module is mixed into any {Instruction} that represents a goto or branch.
264
- module Goto
265
- def generate_transitions
266
- # Assumption: The GOTO instruction's k operand is absolute on all architectures
267
- [transition(k_address, non_local: true)]
268
- end
269
- end
270
-
271
- # This module is mixed into any {Instruction} that represents a conditional skip
272
- # A conditional skip is an instruction that might cause the next instruction to be
273
- # skipped depending on some condition.
274
- module ConditionalSkip
275
- def generate_transitions
276
- [advance(1), advance(2)]
277
- end
278
- end
279
-
280
- # This module is mixed into any {Instruction} that represents a return from a subroutine
281
- # or a RESET instruction.
282
- module ControlEnder
283
- def generate_transitions
284
- []
285
- end
286
- end
287
-
288
- # This module is mixed into any {Instruction} that represents a subroutine call.
289
- module Call
290
- def generate_transitions
291
- [transition(k_address, call_depth_change: 1), advance(1)]
292
- end
293
- end
294
-
295
- module RelativeCall
296
- def generate_transitions
297
- [transition(n_address, call_depth_change: 1), advance(1)]
298
- end
299
- end
300
-
301
- module ConditionalRelativeBranch
302
- def generate_transitions
303
- [transition(n_address, non_local: true), advance(1)]
304
- end
305
- end
306
-
307
- module RelativeBranch
308
- def generate_transitions
309
- [transition(relative_target_address, non_local: true)]
310
- end
311
- end
312
-
313
- class Transition
314
- attr_reader :previous_instruction
315
- attr_reader :next_address
316
-
317
- def initialize(previous_instruction, next_address, instruction_store, attrs)
318
- @previous_instruction = previous_instruction
319
- @next_address = next_address
320
- @instruction_store = instruction_store
321
- @attrs = attrs
322
- end
323
-
324
- def next_instruction
325
- @next_instruction ||= @instruction_store.instruction(next_address)
326
- end
327
-
328
- def non_local?
329
- @attrs.fetch(:non_local, false)
330
- end
331
-
332
- def call_depth_change
333
- @attrs.fetch(:call_depth_change, 0)
334
- end
335
- end
336
- end
337
- end